From 7013878c580ebe869fa268b1db3e571b399021ea Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 Aug 2022 14:56:36 +0200 Subject: [PATCH 0001/1428] Small updates --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a9f9b27..038be17 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,15 @@ BUILDING Flowee Pay uses libraries from Flowee, you need to either install the main flowee package via your package manager or compile it before you compile Pay. -The minimum version required for the Flowee libraries is 2021.06.0 +The minimum version required for the Flowee libraries is 2022.07.0 + +For ubuntu getting the latest is a matter of calling: + +``` + sudo add-apt-repository ppa:flowee/ppa + sudo apt update + sudo apt install flowee-libs +``` You need cmake and Qt5. When you have those installed it is just a matter of calling: -- 2.54.0 From 92af251538c95c36a66d48507ff4dc0330821474 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Aug 2022 20:28:57 +0200 Subject: [PATCH 0002/1428] Add special case for testnet block notifications --- NotificationManager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NotificationManager.cpp b/NotificationManager.cpp index 8200a4b..b06b9c3 100644 --- a/NotificationManager.cpp +++ b/NotificationManager.cpp @@ -101,8 +101,10 @@ void NotificationManager::newBlockSeen(int blockHeight) args << QVariant(m_blockNotificationId); // replaces-id args << QString(); // app_icon (not needed since we say which desktop file we are) args << QString(); // body-text - args << tr("BCH block mined %1").arg(blockHeight); // summary text - // args << QString(); // body-text + if (FloweePay::instance()->chain() == P2PNet::MainChain) + args << tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); // summary text + else + args << tr("tBCH (testnet4) block mined: %1").arg(blockHeight); // summary text QStringList actions; // actions actions << "mute" << tr("Mute"); args << actions; -- 2.54.0 From f177b57ca7fb692340f1a79257d3f6d9779a2e30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Sep 2022 00:51:00 +0200 Subject: [PATCH 0003/1428] new version --- CMakeLists.txt | 2 +- main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b69bf2..513226d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) find_package(Qt6 COMPONENTS Core Quick REQUIRED) diff --git a/main.cpp b/main.cpp index 6c3757b..635e0c1 100644 --- a/main.cpp +++ b/main.cpp @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2022.08.0"); + qapp.setApplicationVersion("2022.09.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); srand((quint32) QDateTime::currentMSecsSinceEpoch()); -- 2.54.0 From 4a7809d394794711c07e10fbb9687f5306b07c98 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Sep 2022 00:24:08 +0200 Subject: [PATCH 0004/1428] Android compile docker recipy --- android/docker/Dockerfile | 60 +++++++++++++++++++++ android/docker/build-docker.sh | 12 +++++ android/docker/scripts/aurs.sh | 19 +++++++ android/docker/scripts/buildBoost.sh | 45 ++++++++++++++++ android/docker/scripts/buildOpenSsl.sh | 23 ++++++++ android/docker/scripts/buildQt.sh | 73 ++++++++++++++++++++++++++ android/docker/scripts/copyBack.sh | 9 ++++ 7 files changed, 241 insertions(+) create mode 100644 android/docker/Dockerfile create mode 100755 android/docker/build-docker.sh create mode 100755 android/docker/scripts/aurs.sh create mode 100755 android/docker/scripts/buildBoost.sh create mode 100755 android/docker/scripts/buildOpenSsl.sh create mode 100755 android/docker/scripts/buildQt.sh create mode 100755 android/docker/scripts/copyBack.sh diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile new file mode 100644 index 0000000..c42a97b --- /dev/null +++ b/android/docker/Dockerfile @@ -0,0 +1,60 @@ +ARG QtVersion=v6.3.1 + +# We use a 'cache' feature where we place big downloads and compiled stuff. +# We also use the 'FROM' multiple times to avoid inheriting an image +# which had lots of intermediate stuff installed that we don't need in +# the final image. + +# First get some native stuff built. +FROM archlinux:latest +ARG QtVersion +LABEL maintainer="Tom Zander " +LABEL qtVersion="${QtVersion}" + +# for europe you might want to copy your local mirrorlist +#copy mirrorlist /etc/pacman.d/ +RUN pacman -Sy --noconfirm base-devel \ + cmake \ + ninja \ + git \ + python \ + libjpeg-turbo \ + xcb-util-keysyms \ + xcb-util-cursor \ + libgl \ + fontconfig \ + xdg-utils \ + shared-mime-info \ + xcb-util-wm \ + libxrender \ + libxi \ + sqlite \ + mesa \ + vulkan-headers \ + tslib \ + libinput \ + libxkbcommon-x11 \ + libproxy \ + libcups \ + double-conversion \ + brotli \ + libb2 \ + md4c \ + jdk11-openjdk \ + git \ + && pacman -Sc --noconfirm +add scripts /usr/local/bin +# to avoid lots of downloads every single time the build runs +# consider uncommenting this one and the lines in the build-docker.sh +# as well as removing the bottom `rm` here +#add cache /usr/local/cache/ +RUN useradd builduser -d /home/builds -m -u 1000 -U && \ + mkdir -p /usr/local/cache && \ + git config --global advice.detachedHead false && \ + /usr/local/bin/aurs.sh && \ + /usr/local/bin/buildOpenSsl.sh && \ + /usr/local/bin/buildQt.sh ${QtVersion} && \ + /usr/local/bin/buildBoost.sh && \ + rm -rf ~builduser/* && / + rm -rf /usr/local/cache + diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh new file mode 100755 index 0000000..a1d3c2c --- /dev/null +++ b/android/docker/build-docker.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +cd `dirname $0` +mkdir -p cache +docker build . --tag flowee/buildenv:android + + +# copy the cache out. +# cid=`docker run -d -ti -v cache:/mnt flowee/buildenv:android /bin/bash` +# docker container exec $cid /usr/local/bin/copyBack.sh +# docker container stop $cid +# docker container rm $cid diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh new file mode 100755 index 0000000..0194256 --- /dev/null +++ b/android/docker/scripts/aurs.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# this runs as root. + +for i in android-ndk android-sdk-platform-tools android-sdk-cmdline-tools-latest android-platform +do + cd ~builduser + git clone https://aur.archlinux.org/$i.git + chown builduser:builduser -R $i + cd "$i" + for f in /usr/local/cache/$i*zst; do + ln -s "$f" . + done + for f in /usr/local/cache/*zip; do + ln -s "$f" . + done + su builduser -c makepkg + find . -type f -name '*zst' -exec ln "{}" /usr/local/cache ';' + pacman -U --noconfirm *zst +done diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh new file mode 100755 index 0000000..001b9a7 --- /dev/null +++ b/android/docker/scripts/buildBoost.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +VERSION=1.67.0 + +echo "Based on boost version $VERSION" >> /etc/versions +source /etc/profile +VER2=`echo $VERSION|sed -e 's#\.#_#g'` + +cd /usr/local/cache +if ! test -f boost_$VER2.tar.bz2; then + curl -O https://netix.dl.sourceforge.net/project/boost/boost/$VERSION/boost_$VER2.tar.bz2 +fi + +cd ~builduser +tar xf /usr/local/cache/boost_$VER2.tar.bz2 + +cd boost_$VER2 +export PATH="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" +./bootstrap.sh --without-icu \ + --with-toolset=clang \ + --with-libraries=chrono,filesystem,iostreams,program_options,system,thread \ + --prefix=/opt/android-boost + +echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang++ : \"-std=c++17 -fvisibility=hidden -fPIC\" \"-fPIC\" ;" > user-config.jam + +./b2 \ + --reconfigure \ + --user-config=user-config.jam \ + --without-python \ + architecture=arm \ + address-model=64 \ + binary-format=elf \ + abi=aapcs \ + target-os=android \ + variant=release \ + link=static \ + threading=multi \ + install + +# toolset=clang-arm \ +# runtime-link=shared \ +# -sNO_BZIP2=1 \ +# -sNO_ZLIB=1 \ +# +# debug-symbols=off diff --git a/android/docker/scripts/buildOpenSsl.sh b/android/docker/scripts/buildOpenSsl.sh new file mode 100755 index 0000000..29e3409 --- /dev/null +++ b/android/docker/scripts/buildOpenSsl.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# this runs as root. +VERSION=1.1.1q +echo "Using OpenSSL $VERSION" >> /etc/versions +source /etc/profile +cd /usr/local/cache + +if ! test -f openssl-$VERSION.tar.gz; then + curl -O https://www.openssl.org/source/openssl-$VERSION.tar.gz +fi + +cd ~builduser +export PATH="/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" +tar xf /usr/local/cache/openssl-$VERSION.tar.gz +cd openssl-$VERSION +./Configure shared android-arm64 -D__ANDROID_API__=21 --prefix=/opt/android-ssl + +# don't compress these two into one, 'install' doesn't like -j +make -j`nproc` 2>&1 >/dev/null +make install 2>&1 >/dev/null + +echo "SSL $VERSION DONE" diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh new file mode 100755 index 0000000..96b89d8 --- /dev/null +++ b/android/docker/scripts/buildQt.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +TAG=$1 +if test -z "$TAG"; then + print "Missing required argument 'TAG'" + exit +fi +echo "Based on Qt version $TAG" >> /etc/versions +source /etc/profile + +for i in qtbase qtshadertools qtdeclarative +do + cd /usr/local/cache + if ! test -d $i.git; then + git clone --bare https://code.qt.io/qt/$i.git + fi + cd ~builduser + git clone -l /usr/local/cache/$i.git -b $TAG +done + +### Native build +# QtBase +mkdir -p ~builduser/build-native/qtbase +(cd ~builduser/build-native/qtbase && \ +~builduser/qtbase/configure \ + -prefix /usr/local \ + -no-openssl \ + -nomake examples \ + -no-dbus && \ +ninja install) + +# Others +for i in qtshadertools qtdeclarative +do + cd ~builduser/build-native + mkdir -p $i + cd $i + /usr/local/bin/qt-configure-module ~builduser/$i + ninja install +done + +### Android build +# QtBase + +mkdir -p ~builduser/build/qtbase +cd ~builduser/build/qtbase +~builduser/qtbase/configure \ + -platform android-clang \ + -prefix /opt/android-qt6/ \ + -android-ndk /opt/android-ndk \ + -android-sdk /opt/android-sdk \ + -qt-host-path /usr/local \ + -android-abis arm64-v8a \ + -android-style-assets \ + -static \ + -openssl-linked \ + -- \ + -DOPENSSL_USE_STATIC_LIBS=ON \ + -DOPENSSL_ROOT_DIR=/opt/android-ssl +ninja install + +# qtbase gives +# -- Neither ANDROID_PLATFORM nor ANDROID_NATIVE_API_LEVEL were specified, using API level 23 as default + +# Others +for i in qtshadertools qtdeclarative +do + cd ~builduser/build + mkdir -p $i + cd $i + /opt/android-qt6/bin/qt-configure-module ~builduser/$i + ninja install +done diff --git a/android/docker/scripts/copyBack.sh b/android/docker/scripts/copyBack.sh new file mode 100755 index 0000000..d25526f --- /dev/null +++ b/android/docker/scripts/copyBack.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd /usr/local/cache +for i in * +do + if ! test -a /mnt/$i; then + cp -ar "$i" /mnt/ + fi +done -- 2.54.0 From ce0c709127554fd939c265c20661fb75a1c4ba94 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Sep 2022 11:58:32 +0200 Subject: [PATCH 0005/1428] Add some docs --- android/docker/Dockerfile | 16 ++++----- android/docker/README.md | 53 ++++++++++++++++++++++++++++ android/docker/build-docker.sh | 5 +-- android/docker/cache/.gitignore | 1 + android/docker/scripts/buildBoost.sh | 1 - 5 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 android/docker/README.md create mode 100644 android/docker/cache/.gitignore diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index c42a97b..d688ec2 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -44,17 +44,17 @@ RUN pacman -Sy --noconfirm base-devel \ git \ && pacman -Sc --noconfirm add scripts /usr/local/bin -# to avoid lots of downloads every single time the build runs -# consider uncommenting this one and the lines in the build-docker.sh -# as well as removing the bottom `rm` here -#add cache /usr/local/cache/ +# to enable caching, check the README.md +add cache /usr/local/cache/ RUN useradd builduser -d /home/builds -m -u 1000 -U && \ mkdir -p /usr/local/cache && \ git config --global advice.detachedHead false && \ /usr/local/bin/aurs.sh && \ /usr/local/bin/buildOpenSsl.sh && \ - /usr/local/bin/buildQt.sh ${QtVersion} && \ - /usr/local/bin/buildBoost.sh && \ - rm -rf ~builduser/* && / - rm -rf /usr/local/cache + /usr/local/bin/buildQt.sh ${QtVersion} + +#&& \ +RUN /usr/local/bin/buildBoost.sh && \ + rm -rf ~builduser/* && \ + rm -rf /usr/local/ diff --git a/android/docker/README.md b/android/docker/README.md new file mode 100644 index 0000000..ad2d4f2 --- /dev/null +++ b/android/docker/README.md @@ -0,0 +1,53 @@ +# Docker for Android + +To compile and deploy Flowee Pay for native you need a compiler and some libraries installed, +nothing too complex or strange for software dev. + +Building Flowee Pay for Android is a lot harder, though. Cross-compiler, the Android software +stack and naturally the same libraries but this time compiled for Android. Which are not +readilly available for most cases. + +So, the solution, make a docker image that has all this and every compile or package action +can be done using this docker image. + +In this directory you find the means to create this docker image. The recipe, as it were. + +Its trivial if you just want to use it. +Basically, you make sure the docker infrastructure is running and then you run the shell script. +Wait for (quite) a while and your docker image is done, ready for use. + +# Versions + +The image has in /etc/versions all the versions of the main software we put in there. + +Boost is a bit older since the Android libc seems to be older too. +The latest boost (1.79) gave me linking errors for the 'statx' symbol. + +For OpenSSL, Qt and other other libs we try to use the latest release. + +# Caching + +If you are still here, reading, it implies you want to alter the build image recipe and maybe fix +or tweak things. + +There is a pretty good chance that you'll end up running 'docker build' multiple times and +to avoid downloading all the software every time, I made a 'caching' concept. + +Enable caching and repeat runs skip a lot of work and slow downloads, +but the result is a significantly larger image. +So, good during development of the image, like a debug build. + +To enable this; + +1. edit the build-docker.sh to enable the last 4 lines and allow it to copy the +downloaded stuff from the built image into the cache. + +2. comment out the 'rm -rf /usr/local' at the bottom of the Dockerfile + +3. remove comment (enable) the line in the Dockerfile: + add cache /usr/local/cache/ + +Please note that should the build fail, the cached items will not be recovered +from that build, so better make sure you run though the stable build once to get +the cache filled up. + diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index a1d3c2c..761704e 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -1,9 +1,10 @@ #!/bin/bash +QtVersion=v6.3.1 + cd `dirname $0` mkdir -p cache -docker build . --tag flowee/buildenv:android - +docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion # copy the cache out. # cid=`docker run -d -ti -v cache:/mnt flowee/buildenv:android /bin/bash` diff --git a/android/docker/cache/.gitignore b/android/docker/cache/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/android/docker/cache/.gitignore @@ -0,0 +1 @@ +* diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index 001b9a7..d48599a 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -26,7 +26,6 @@ echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64 ./b2 \ --reconfigure \ --user-config=user-config.jam \ - --without-python \ architecture=arm \ address-model=64 \ binary-format=elf \ -- 2.54.0 From b3c1e74c8c120f15de8ba3d850eb1a2ec995c153 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Sep 2022 17:53:53 +0200 Subject: [PATCH 0006/1428] Improvements for android building. The build now no longer uses the root user for built files. --- CMakeLists.txt | 42 +++++++++++++++++++++----- android/docker/Dockerfile | 44 +++++++++++++--------------- android/docker/scripts/createRootPwd | 22 ++++++++++++++ 3 files changed, 77 insertions(+), 31 deletions(-) create mode 100755 android/docker/scripts/createRootPwd diff --git a/CMakeLists.txt b/CMakeLists.txt index 513226d..da99a50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.12) project(flowee_pay) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -26,15 +26,41 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) find_package(Qt6 COMPONENTS Core Quick REQUIRED) -find_package(Qt6 COMPONENTS DBus LinguistTools) +if (ANDROID) + # boost 'find_package' doesn't like cross-compiled setups. + # we end up hardcoding the paths. (see floweepay/android/docker/) + # unfortunatly this doesn't do any sort of checks + # or detection. But since we build in a pre-designed docker + # we'll probably be Ok. + set(Boost_FOUND TRUE) + set(Boost_INCLUDE_DIRS /opt/android-boost/include) + set(Boost_LIBRARY_DIRS /opt/android-boost/lib) + + set(Boost_LIBRARIES + /opt/android-boost/lib/libboost_filesystem.a + /opt/android-boost/lib/libboost_system.a + /opt/android-boost/lib/libboost_chrono.a + /opt/android-boost/lib/libboost_iostreams.a + /opt/android-boost/lib/libboost_program_options.a + /opt/android-boost/lib/libboost_thread.a + ) + set(Boost_FILESYSTEM_FOUND ON) + set(Boost_SYSTEM_FOUND ON) + set(Boost_chrono_FOUND ON) + set(Boost_iostreams_FOUND ON) + set(Boost_program_options_FOUND ON) + set(Boost_thread_FOUND ON) + set(Boost_VERSION_STRING 1.67.0) +else () + find_package(Qt6 COMPONENTS DBus LinguistTools) + find_package(Boost 1.67.0 + REQUIRED + filesystem chrono thread + ) +endif() find_package(QREncode REQUIRED) find_package(OpenSSL REQUIRED) -message ("Using OpenSSL ${OPENSSL_VERSION}") -find_package(Flowee REQUIRED flowee_p2p) -find_package(Boost 1.67.0 - REQUIRED - filesystem chrono thread -) +find_package(flowee REQUIRED flowee_p2p) option(local_qml "Allow local QML loading" OFF) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index d688ec2..17ca848 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,19 +1,16 @@ ARG QtVersion=v6.3.1 -# We use a 'cache' feature where we place big downloads and compiled stuff. -# We also use the 'FROM' multiple times to avoid inheriting an image -# which had lots of intermediate stuff installed that we don't need in -# the final image. - -# First get some native stuff built. FROM archlinux:latest ARG QtVersion LABEL maintainer="Tom Zander " -LABEL qtVersion="${QtVersion}" +LABEL qtversion="${QtVersion}" +ENTRYPOINT ["su", "-", "builduser", "-c"] +add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -#copy mirrorlist /etc/pacman.d/ -RUN pacman -Sy --noconfirm base-devel \ +copy mirrorlist /etc/pacman.d/ +RUN useradd builduser -d /home/builds -m -u 1000 -U \ + && pacman -Sy --noconfirm base-devel \ cmake \ ninja \ git \ @@ -42,19 +39,20 @@ RUN pacman -Sy --noconfirm base-devel \ md4c \ jdk11-openjdk \ git \ - && pacman -Sc --noconfirm -add scripts /usr/local/bin + && pacman -Sc --noconfirm \ + && rm -rf /var/cache/pacman/pkg/* \ + && /usr/local/bin/createRootPwd + # to enable caching, check the README.md add cache /usr/local/cache/ -RUN useradd builduser -d /home/builds -m -u 1000 -U && \ - mkdir -p /usr/local/cache && \ - git config --global advice.detachedHead false && \ - /usr/local/bin/aurs.sh && \ - /usr/local/bin/buildOpenSsl.sh && \ - /usr/local/bin/buildQt.sh ${QtVersion} - -#&& \ -RUN /usr/local/bin/buildBoost.sh && \ - rm -rf ~builduser/* && \ - rm -rf /usr/local/ - +RUN mkdir -p /usr/local/cache \ + && git config --global advice.detachedHead false \ + && /usr/local/bin/aurs.sh \ + && rm -rf ~builduser/* \ + && /usr/local/bin/buildOpenSsl.sh \ + && rm -rf ~builduser/* \ + && /usr/local/bin/buildQt.sh ${QtVersion} \ + && rm -rf ~builduser/* \ + && /usr/local/bin/buildBoost.sh \ + && rm -rf ~builduser/* \ + && rm -rf /usr/local/cache diff --git a/android/docker/scripts/createRootPwd b/android/docker/scripts/createRootPwd new file mode 100755 index 0000000..3b117a3 --- /dev/null +++ b/android/docker/scripts/createRootPwd @@ -0,0 +1,22 @@ +#!/usr/bin/perl + +# This script reads the system password backing store +# takes out the line that forbids root from having any password +# and replaces it with a proper password allowing us to +# use 'su' in our image and become root. + +open(SOURCE, "/etc/shadow") || die "Failed to open shadow file"; +foreach $line () { + if (not $line=~/^root:/) { + push @lines, $line; + } +} +close SOURCE; + +open (OUT, ">", "/etc/shadow") || die "Can't write to shadow file"; +print (OUT "root:\$6\$WRwfPMlr3QKlTNrn\$/vBG5lRy4SGl0hA13AjR5JV/TUJN71nV15/ow1mm1WuJ7KVPy6COdeOVZPM8lW1TykaC7V04lIfOmlKdZENhY1:19251::::::\n"); +foreach $line (@lines) { + print (OUT $line); +} + +close OUT; -- 2.54.0 From 7853004d682ae24bd9c0d4f2566d405f09516612 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Sep 2022 18:54:00 +0200 Subject: [PATCH 0007/1428] Make compiling apps optional This adds cmake options to make it easy to enable/disable apps from building. --- CMakeLists.txt | 224 ++++++++++++++++++---------------- android/cmake/FindBoost.cmake | 36 ++++++ 2 files changed, 155 insertions(+), 105 deletions(-) create mode 100644 android/cmake/FindBoost.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index da99a50..8f167ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.13) project(flowee_pay) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -26,41 +26,16 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) find_package(Qt6 COMPONENTS Core Quick REQUIRED) +find_package(flowee REQUIRED flowee_p2p) +find_package(OpenSSL REQUIRED) if (ANDROID) - # boost 'find_package' doesn't like cross-compiled setups. - # we end up hardcoding the paths. (see floweepay/android/docker/) - # unfortunatly this doesn't do any sort of checks - # or detection. But since we build in a pre-designed docker - # we'll probably be Ok. - set(Boost_FOUND TRUE) - set(Boost_INCLUDE_DIRS /opt/android-boost/include) - set(Boost_LIBRARY_DIRS /opt/android-boost/lib) - - set(Boost_LIBRARIES - /opt/android-boost/lib/libboost_filesystem.a - /opt/android-boost/lib/libboost_system.a - /opt/android-boost/lib/libboost_chrono.a - /opt/android-boost/lib/libboost_iostreams.a - /opt/android-boost/lib/libboost_program_options.a - /opt/android-boost/lib/libboost_thread.a - ) - set(Boost_FILESYSTEM_FOUND ON) - set(Boost_SYSTEM_FOUND ON) - set(Boost_chrono_FOUND ON) - set(Boost_iostreams_FOUND ON) - set(Boost_program_options_FOUND ON) - set(Boost_thread_FOUND ON) - set(Boost_VERSION_STRING 1.67.0) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + ${CMAKE_SOURCE_DIR}/android/cmake) else () find_package(Qt6 COMPONENTS DBus LinguistTools) - find_package(Boost 1.67.0 - REQUIRED - filesystem chrono thread - ) endif() +find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) find_package(QREncode REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(flowee REQUIRED flowee_p2p) option(local_qml "Allow local QML loading" OFF) @@ -105,88 +80,116 @@ target_link_libraries(pay_lib #### Translations -set (TS_FILES_DESKTOP - translations/floweepay-desktop_en.ts - translations/floweepay-desktop_nl.ts - translations/floweepay-desktop_pl.ts -) -qt6_add_translation(qmFiles1 ${TS_FILES_DESKTOP}) -set (TS_FILES_COMMON - translations/floweepay-common_en.ts - translations/floweepay-common_nl.ts - translations/floweepay-common_pl.ts -) -qt6_add_translation(qmFiles2 ${TS_FILES_COMMON}) -set (TS_FILES_MOBILE - translations/floweepay-mobile_nl.ts - translations/floweepay-mobile_pl.ts -) -qt6_add_translation(qmFiles3 ${TS_FILES_MOBILE}) +if(NOT ANDROID) + # Linguist is too big a dependency, just use the native-build to get the qm files + set (TS_FILES_DESKTOP + translations/floweepay-desktop_en.ts + translations/floweepay-desktop_nl.ts + translations/floweepay-desktop_pl.ts + ) + qt6_add_translation(qmFiles1 ${TS_FILES_DESKTOP}) + set (TS_FILES_COMMON + translations/floweepay-common_en.ts + translations/floweepay-common_nl.ts + translations/floweepay-common_pl.ts + ) + qt6_add_translation(qmFiles2 ${TS_FILES_COMMON}) + set (TS_FILES_MOBILE + translations/floweepay-mobile_nl.ts + translations/floweepay-mobile_pl.ts + ) + qt6_add_translation(qmFiles3 ${TS_FILES_MOBILE}) -# notice that for this custom target we require CMake 3.13 minimum. But since -# only the release manager will need to call this, we leave the project cmake -# minimum to be lower. -add_custom_target(i18n - COMMAND lupdate desktop/qml.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts - COMMAND lupdate ${PAY_SOURCES} -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts - COMMAND lupdate mobile/qml.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT - "Updating internationalization (i18n) translation files" -) + add_custom_target(i18n + COMMAND lupdate desktop/qml.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts + COMMAND lupdate ${PAY_SOURCES} -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts + COMMAND lupdate mobile/qml.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT + "Updating internationalization (i18n) translation files" + ) +endif() ###### Pay executable -# The qm files are generated in the build tree, but the qrc file is inside the -# source directory and the path to resources are relative to the location of -# the qrc file itself. We use configure_file() to copy the qrc file in the build -# directory such that it can find the qm translations files. The qrc file is -# copied if it doesn't exist in the destination or if it is modified. -configure_file(translations/desktop-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) -if (local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/desktop) +if (NOT ANDROID) + set (NOT_ANDROID TRUE) + message("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") +endif() +option (BUILD_DESKTOP_PAY + "Build the desktop (dev) client of Pay" + ${NOT_ANDROID}) + +if(BUILD_DESKTOP_PAY) + # The qm files are generated in the build tree, but the qrc file is inside the + # source directory and the path to resources are relative to the location of + # the qrc file itself. We use configure_file() to copy the qrc file in the build + # directory such that it can find the qm translations files. The qrc file is + # copied if it doesn't exist in the destination or if it is modified. + configure_file(translations/desktop-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) + if (local_qml) + set (QML_PATH ${CMAKE_SOURCE_DIR}/desktop) + endif() + configure_file(qml_path_helper.cpp.in desktop/qml_path_helper.cpp) + add_executable(pay + main.cpp + desktop/qml_path_helper.cpp + desktop/qml.qrc + desktop-i18n.qrc + ) + target_link_libraries(pay pay_lib) + install(TARGETS pay DESTINATION bin) endif() -configure_file(qml_path_helper.cpp.in desktop/qml_path_helper.cpp) -add_executable(pay - main.cpp - desktop/qml_path_helper.cpp - desktop/qml.qrc - desktop-i18n.qrc -) -target_link_libraries(pay pay_lib) -install(TARGETS pay DESTINATION bin) ###### Pay-mobile executable +option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) -configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) -if (local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/mobile) +if(BUILD_MOBILE_PAY) + configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) + if (local_qml) + set (QML_PATH ${CMAKE_SOURCE_DIR}/mobile) + endif() + configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) + if(ANDROID) + # the dependency of linguist is pretty steep for cross-compilation + # so lets accept the lack of compiled translations here. + # A build that wants them should copy them from a native build. + + if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qml") + set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) + endif() + endif() + add_executable(pay_mobile + main.cpp + mobile/qml_path_helper.cpp + mobile/qml.qrc + ${MOBILE_PAY_I18N_QRC} + ) + target_link_libraries(pay_mobile pay_lib) + install(TARGETS pay_mobile DESTINATION bin) endif() -configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) -add_executable(pay_mobile - main.cpp - mobile/qml_path_helper.cpp - mobile/qml.qrc - mobile-i18n.qrc -) -target_link_libraries(pay_mobile pay_lib) -install(TARGETS pay_mobile DESTINATION bin) ###### blockheaders-meta-extractor executable -add_executable(blockheaders-meta-extractor - MetaExtractor.cpp -) -target_link_libraries(blockheaders-meta-extractor - flowee_p2p - flowee_utils - ${OPENSSL_LIBRARIES} - ${Boost_LIBRARIES} -) -install(TARGETS blockheaders-meta-extractor DESTINATION bin) +option (BUILD_PAY_TOOLS + "Build the tools Pay" + ${NOT_ANDROID}) + +if(BUILD_PAY_TOOLS) + add_executable(blockheaders-meta-extractor + MetaExtractor.cpp + ) + target_link_libraries(blockheaders-meta-extractor + flowee_p2p + flowee_utils + ${OPENSSL_LIBRARIES} + ${Boost_LIBRARIES} + ) + install(TARGETS blockheaders-meta-extractor DESTINATION bin) +endif() install(PROGRAMS desktop/org.flowee.pay.desktop DESTINATION share/applications) set (ICONIN desktop/icons/hicolor/) @@ -202,19 +205,30 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/data/blockheaders) install(FILES ${CMAKE_SOURCE_DIR}/data/blockheaders DESTINATION share/floweepay) endif() -add_subdirectory(testing) +if (NOT ANDROID) + add_subdirectory(testing) +endif() # Report ------ message("") message("Configuration results:") message("----------------------") -if (${Qt6DBus_FOUND}) - message("Found optional lib: QtDBus") -else () - message("Missing QtDBus, skipping support for desktop notifications") -endif () +if (${BUILD_DESKTOP_PAY}) + message ("-> Building Desktop-Pay...") + if (${Qt6DBus_FOUND}) + message(" Found optional lib: QtDBus") + else () + message(" Missing QtDBus, skipping support for desktop notifications") + endif () +endif() +if (${BUILD_MOBILE_PAY}) + message ("-> Building Pay for mobile") +endif() +if (${BUILD_PAY_TOOLS}) + message ("-> Building Pay tools") +endif() if (${local_qml}) - message("Using QML from source-dir. DO NOT DISTRIBUTE THIS BINARY!") + message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") endif () message("") diff --git a/android/cmake/FindBoost.cmake b/android/cmake/FindBoost.cmake new file mode 100644 index 0000000..ce1c58b --- /dev/null +++ b/android/cmake/FindBoost.cmake @@ -0,0 +1,36 @@ +# For some unknown reason Boost doesn't get found using the +# cmake shipped 'find_package' when we use cross-compilation. + +# So we use a bit of a hack here as we assume the build +# is being done using the docker, so we can just do some +# basic checks and give up if that fails. + + +if (NOT EXISTS "/opt/android-boost/include/boost/filesystem/path.hpp") + message(FATAL_ERROR "Missing boost headers") +endif() +if (NOT EXISTS "/opt/android-boost/lib/libboost_filesystem.a") + message(FATAL_ERROR "Missing boost static libs") +endif() + +set(Boost_FOUND TRUE) +set(Boost_INCLUDE_DIRS /opt/android-boost/include) +set(Boost_LIBRARY_DIRS /opt/android-boost/lib) + +set(Boost_LIBRARIES + ${Boost_LIBRARY_DIRS}/libboost_filesystem.a + ${Boost_LIBRARY_DIRS}/libboost_system.a + ${Boost_LIBRARY_DIRS}/libboost_chrono.a + ${Boost_LIBRARY_DIRS}/libboost_iostreams.a + ${Boost_LIBRARY_DIRS}/libboost_program_options.a + ${Boost_LIBRARY_DIRS}/libboost_thread.a +) +set(Boost_FILESYSTEM_FOUND ON) +set(Boost_SYSTEM_FOUND ON) +set(Boost_chrono_FOUND ON) +set(Boost_iostreams_FOUND ON) +set(Boost_program_options_FOUND ON) +set(Boost_thread_FOUND ON) +set(Boost_VERSION_STRING 1.67.0) + +include_directories(${Boost_INCLUDE_DIRS}) -- 2.54.0 From 702062fe01d5f0f5cf4cbd0d79466b29c1ecb511 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Sep 2022 18:59:08 +0200 Subject: [PATCH 0008/1428] Make NotificationManager build without dbus found --- NotificationManager.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/NotificationManager.cpp b/NotificationManager.cpp index b06b9c3..7b46d03 100644 --- a/NotificationManager.cpp +++ b/NotificationManager.cpp @@ -20,8 +20,10 @@ #include "PriceDataProvider.h" #include +#if QT_DBUS_LIB #include #include +#endif #include constexpr const char *MUTE = "notificationNewblockMute"; @@ -32,6 +34,7 @@ NotificationManager::NotificationManager(QObject *parent) { setCollation(true); +#if QT_DBUS_LIB // We use the notification spec (v 1.2) // https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html m_newBlockHints["desktop-entry"] = "org.flowee.pay"; @@ -40,7 +43,6 @@ NotificationManager::NotificationManager(QObject *parent) m_walletUpdateHints["desktop-entry"] = "org.flowee.pay"; m_walletUpdateHints["urgency"] = 1; // normal -#if QT_DBUS_LIB // setup slots for DBUS-signals. Essentially remote callbacks from the deskop notifications app QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", "NotificationClosed", this, SLOT(notificationClosed(uint,uint))); @@ -119,12 +121,15 @@ void NotificationManager::newBlockSeen(int blockHeight) void NotificationManager::newBlockNotificationShown(uint id) { +#if QT_DBUS_LIB m_blockNotificationId = id; logDebug() << "new block notification id:" << m_blockNotificationId; +#endif } void NotificationManager::notificationClosed(uint32_t id, uint32_t reason) { +#if QT_DBUS_LIB logDebug() << " something got closed" << id << reason; if (m_blockNotificationId == id) { m_blockNotificationId = 0; @@ -133,6 +138,7 @@ void NotificationManager::notificationClosed(uint32_t id, uint32_t reason) m_newFundsNotificationId = 0; flushCollate(); } +#endif } void NotificationManager::actionInvoked(uint, const QString &actionKey) @@ -219,8 +225,10 @@ void NotificationManager::walletUpdated() void NotificationManager::walletUpdateNotificationShown(uint id) { +#if QT_DBUS_LIB m_newFundsNotificationId = id; m_openingNewFundsNotification = false; +#endif } #if QT_DBUS_LIB -- 2.54.0 From 0be7088b9725c5222e657c4972c3a1818c0ed35a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Sep 2022 20:49:15 +0200 Subject: [PATCH 0009/1428] Add QR-encode lib to android build --- android/docker/Dockerfile | 2 ++ android/docker/scripts/buildQrEncode.sh | 25 +++++++++++++++++++++ cmake/FindQREncode.cmake | 30 +++++++++++++++---------- 3 files changed, 45 insertions(+), 12 deletions(-) create mode 100755 android/docker/scripts/buildQrEncode.sh diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 17ca848..5be2e4b 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -55,4 +55,6 @@ RUN mkdir -p /usr/local/cache \ && rm -rf ~builduser/* \ && /usr/local/bin/buildBoost.sh \ && rm -rf ~builduser/* \ + && /usr/local/bin/buildQrEncode.sh \ + && rm -rf ~builduser/* \ && rm -rf /usr/local/cache diff --git a/android/docker/scripts/buildQrEncode.sh b/android/docker/scripts/buildQrEncode.sh new file mode 100755 index 0000000..454e283 --- /dev/null +++ b/android/docker/scripts/buildQrEncode.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +VERSION=4.1.1 + +echo "Based on qrencode version $VERSION" >> /etc/versions +source /etc/profile + +cd /usr/local/cache +if ! test -f qrencode-${VERSION}.tar.bz2; then + curl -O https://fukuchi.org/works/qrencode/qrencode-${VERSION}.tar.bz2 +fi + +cd ~builduser +tar xf /usr/local/cache/qrencode-${VERSION}.tar.bz2 + +cd qrencode-$VERSION +mkdir build +cd build +cmake -DCMAKE_TOOLCHAIN_FILE=/opt/android-qt6/lib/cmake/Qt6/qt.toolchain.cmake \ + -DCMAKE_INSTALL_PREFIX=/opt/android-qrencode \ + -DWITHOUT_PNG=ON \ + -G Ninja \ + .. + +ninja install diff --git a/cmake/FindQREncode.cmake b/cmake/FindQREncode.cmake index dde94d1..8e7538d 100644 --- a/cmake/FindQREncode.cmake +++ b/cmake/FindQREncode.cmake @@ -24,24 +24,29 @@ find_brew_prefix(BREW_HINT qrencode) find_package(PkgConfig) pkg_check_modules(PC_QREncode QUIET libqrencode) -find_path(QREncode_INCLUDE_DIR - NAMES qrencode.h - HINTS ${BREW_HINT} - PATHS ${PC_QREncode_INCLUDE_DIRS} -) +if (ANDROID) + if (EXISTS "/opt/android-qrencode/include/qrencode.h") + set (QREncode_INCLUDE_DIR "/opt/android-qrencode/include") + set (QREncode_FOUND TRUE) + set (QREncode_LIBRARIES "/opt/android-qrencode/lib/libqrencode.a") + endif() +else() + find_path(QREncode_INCLUDE_DIR + NAMES qrencode.h + HINTS ${BREW_HINT} + PATHS ${PC_QREncode_INCLUDE_DIRS} + ) +endif() set(QREncode_INCLUDE_DIRS "${QREncode_INCLUDE_DIR}") mark_as_advanced(QREncode_INCLUDE_DIR) -# TODO: extract a version number. -# For now qrencode does not provide an easy way to extract a version number. - include(ExternalLibraryHelper) find_component(QREncode qrencode - NAMES qrencode - HINTS ${BREW_HINT} - PATHS ${PC_QREncode_LIBRARY_DIRS} - INCLUDE_DIRS ${QREncode_INCLUDE_DIRS} + NAMES qrencode + HINTS ${BREW_HINT} + PATHS ${PC_QREncode_LIBRARY_DIR} + INCLUDE_DIRS ${QREncode_INCLUDE_DIR} ) include(FindPackageHandleStandardArgs) @@ -50,3 +55,4 @@ find_package_handle_standard_args(QREncode QREncode_INCLUDE_DIR HANDLE_COMPONENTS ) +include_directories(${QREncode_INCLUDE_DIR}) -- 2.54.0 From 0139bd2c9a0f6c4b84c95e79e1e8588383a4c83c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Sep 2022 22:58:45 +0200 Subject: [PATCH 0010/1428] Don't have cache by default --- android/docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 5be2e4b..4cf6be2 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,7 +8,7 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -copy mirrorlist /etc/pacman.d/ +#copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Sy --noconfirm base-devel \ cmake \ @@ -44,7 +44,7 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && /usr/local/bin/createRootPwd # to enable caching, check the README.md -add cache /usr/local/cache/ +#add cache /usr/local/cache/ RUN mkdir -p /usr/local/cache \ && git config --global advice.detachedHead false \ && /usr/local/bin/aurs.sh \ -- 2.54.0 From 9c022871444e82c4752c55ce5f675613ae326214 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 17 Sep 2022 11:14:55 +0200 Subject: [PATCH 0011/1428] Build and link --- android/build-pay.sh | 94 +++++++++++++++++++++++++++++++ android/docker/scripts/buildQt.sh | 1 - 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100755 android/build-pay.sh diff --git a/android/build-pay.sh b/android/build-pay.sh new file mode 100755 index 0000000..ecbfb84 --- /dev/null +++ b/android/build-pay.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# This file is part of the Flowee project +# Copyright (C) 2022 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +_thehub_dir_="$1" +_docker_name_="$2" + +if test -d .bin; then + .bin/doBuild + exit +fi + +if test -z "$_thehub_dir_"; then + echo "Usage:" + echo " build-android [DOCKER_NAME]" + echo "" + echo "Start this client in your builddir" + echo "hub-builddir is the dir where the android build of hub is." + echo "docker-name (optional) is the image name to do the compile in" + exit +fi + + +# check if the provided dir is really an android HUB-libs dir +_thehub_dir_=`realpath $_thehub_dir_` +_ok=0 +if test -f $_thehub_dir_/lib/libflowee_p2p.a; then + if grep -q OS_ANDROID $_thehub_dir_/build.ninja; then + _ok=1 + fi +fi + +if test "$_ok" -eq 0; then + echo "Invalid or not compiled HUB build dir." + exit +fi + +if test -z "$_docker_name_"; then + _docker_name_="flowee/buildenv-android:v6.3.1" +fi + +floweePaySrcDir=`dirname $0`/.. + +if ! test -f .config; then + cat << HERE > .config +cd /home/builds/build + +if ! test -f build.ninja; then + cmake \\ + -DCMAKE_TOOLCHAIN_FILE=/opt/android-qt6/lib/cmake/Qt6/qt.toolchain.cmake \\ + -DANDROID_SDK_ROOT=/opt/android-sdk \\ + -DANDROID_NDK_ROOT=/opt/android-ndk \\ + -Dflowee_DIR=/home/builds/floweelibs/cmake \\ + -DOPENSSL_ROOT_DIR=/opt/android-ssl \\ + -DOPENSSL_CRYPTO_LIBRARY=/opt/android-ssl/lib/libcrypto.a \\ + -DOPENSSL_SSL_LIBRARY=/opt/android-ssl/lib/libssl.a \\ + -DOPENSSL_INCLUDE_DIR=/opt/android-ssl/include/ \\ + -G Ninja \\ + -DCMAKE_INSTALL_PREFIX=\`pwd\` \\ + /home/builds/src +fi + +ninja install +HERE + chmod 755 .config +fi + +if ! test -d .bin; then + mkdir .bin +cat << HERE > .bin/doBuild +docker run --rm -ti\ + --volume=`pwd`:/home/builds/build \ + --volume=$floweePaySrcDir:/home/builds/src \ + --volume=$_thehub_dir_:/home/builds/floweelibs \ + $_docker_name_ \ + build/.config +HERE + chmod 700 .bin/doBuild +fi + +.bin/doBuild diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 96b89d8..cf8d8f7 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -52,7 +52,6 @@ cd ~builduser/build/qtbase -qt-host-path /usr/local \ -android-abis arm64-v8a \ -android-style-assets \ - -static \ -openssl-linked \ -- \ -DOPENSSL_USE_STATIC_LIBS=ON \ -- 2.54.0 From 8faa7c05442a7fd84ffdcc9cbcc49da7e5083d77 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Sep 2022 11:49:10 +0200 Subject: [PATCH 0012/1428] Install packages in one go. This avoids any dependency issues. --- android/docker/scripts/aurs.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index 0194256..d402c11 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -15,5 +15,6 @@ do done su builduser -c makepkg find . -type f -name '*zst' -exec ln "{}" /usr/local/cache ';' - pacman -U --noconfirm *zst done + +pacman -U --noconfirm /usr/local/cache/*zst -- 2.54.0 From aee01c2cfcea39553e780fb34242b400b85f6507 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Sep 2022 12:27:27 +0200 Subject: [PATCH 0013/1428] Detect mismatching user-id --- android/docker/build-docker.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 761704e..4ef06d9 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -1,5 +1,19 @@ #!/bin/bash +if test "$1" != "force"; then + if test `id -u` != "1000"; + then + echo "The docker file assumes you compile using a user with UID=1000" + echo "Your current user-id is not 1000, and thus the generated image" + echo "won't work for you. You may want to adjust the Docker file and" + echo "set your user-id in the 'useradd' line." + echo -n "Your current users ID is: " + id -u + echo "If you are ready to build the iamge, pass in 'force' to this build script" + exit + fi +fi + QtVersion=v6.3.1 cd `dirname $0` @@ -7,7 +21,7 @@ mkdir -p cache docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion # copy the cache out. -# cid=`docker run -d -ti -v cache:/mnt flowee/buildenv:android /bin/bash` -# docker container exec $cid /usr/local/bin/copyBack.sh -# docker container stop $cid -# docker container rm $cid +cid=`docker run -d -ti -v cache:/mnt flowee/buildenv:android /bin/bash` +docker container exec $cid /usr/local/bin/copyBack.sh +docker container stop $cid +docker container rm $cid -- 2.54.0 From cc6ed22ea671f922103ed0823c50b7b04d7722cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Sep 2022 12:47:01 +0200 Subject: [PATCH 0014/1428] Add more docs --- android/docker/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/android/docker/README.md b/android/docker/README.md index ad2d4f2..0e43379 100644 --- a/android/docker/README.md +++ b/android/docker/README.md @@ -51,3 +51,19 @@ Please note that should the build fail, the cached items will not be recovered from that build, so better make sure you run though the stable build once to get the cache filled up. +# Space requirements + +The resulting image, without caching, is some 4.5GB. You will need maybe double that +during the build. +If you turn on caching then the downloads will go down, but the image size will go up +to some 7GB. + +# User-ID + +Due to the way that Docker works, the user-id is relevant for the build. For most people +this will work out of the box, but please check you have user-id '1000' otherwise you'll +get a failure on build regarding "Unable to (re)create". +You can check your user-id with `id -u`. If it is not 1000, you may need to (re)create the +docker image with altered Dockerfile. +The 'useradd' line in the `Dockerfile` specifically has the value '1000', you can change +to what your users `id` is and then build the image. -- 2.54.0 From bfca0eb670e8855faa55a9e840a2569704fcf208 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 26 Sep 2022 16:22:27 +0200 Subject: [PATCH 0015/1428] Fix copyback concept --- android/docker/README.md | 8 ++------ android/docker/build-docker.sh | 8 ++------ android/docker/scripts/copyBack.sh | 6 ++++-- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/android/docker/README.md b/android/docker/README.md index 0e43379..5e3ce27 100644 --- a/android/docker/README.md +++ b/android/docker/README.md @@ -39,13 +39,9 @@ So, good during development of the image, like a debug build. To enable this; -1. edit the build-docker.sh to enable the last 4 lines and allow it to copy the -downloaded stuff from the built image into the cache. - -2. comment out the 'rm -rf /usr/local' at the bottom of the Dockerfile - -3. remove comment (enable) the line in the Dockerfile: +1. remove comment (enable) the line in the Dockerfile: add cache /usr/local/cache/ +2. comment out the 'rm -rf /usr/local' at the bottom of the Dockerfile Please note that should the build fail, the cached items will not be recovered from that build, so better make sure you run though the stable build once to get diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 4ef06d9..a7ab57e 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -17,11 +17,7 @@ fi QtVersion=v6.3.1 cd `dirname $0` -mkdir -p cache docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion -# copy the cache out. -cid=`docker run -d -ti -v cache:/mnt flowee/buildenv:android /bin/bash` -docker container exec $cid /usr/local/bin/copyBack.sh -docker container stop $cid -docker container rm $cid +# copy newly downloaded items to the cache (does nothing by default) +docker run --rm -ti -v `pwd`:/mnt flowee/buildenv-android:$QtVersion /usr/local/bin/copyBack.sh diff --git a/android/docker/scripts/copyBack.sh b/android/docker/scripts/copyBack.sh index d25526f..34485d8 100755 --- a/android/docker/scripts/copyBack.sh +++ b/android/docker/scripts/copyBack.sh @@ -1,9 +1,11 @@ #!/bin/bash +# this runs as a normal user. + cd /usr/local/cache for i in * do - if ! test -a /mnt/$i; then - cp -ar "$i" /mnt/ + if ! test -a /mnt/cache/$i; then + cp -ar "$i" /mnt/cache/ fi done -- 2.54.0 From 01e169e41b38d3d98998648eb68e5e60a3069b9e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 26 Sep 2022 16:23:21 +0200 Subject: [PATCH 0016/1428] Fixlets from linter detection. --- testing/wallet/TestWallet.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index 18f3ae6..539c537 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -789,7 +789,7 @@ void TestWallet::testEncryption2() } QString txHash = QString::fromStdString(theTx.createHash().ToString()); const QString baseName("%1/wallet-1111/%2/%3"); - QString txFile = baseName.arg(m_dir).arg(txHash.left(2)).arg(txHash.mid(2)); + QString txFile = baseName.arg(m_dir).arg(txHash.left(2), txHash.mid(2)); QVERIFY(QFile::exists(txFile)); { @@ -803,8 +803,6 @@ void TestWallet::testEncryption2() QCOMPARE(wallet->encryptionSeed(), 0); wallet->setEncryption(Wallet::FullyEncrypted, PWD); QVERIFY(wallet->encryptionSeed() != 0); - seed = wallet->encryptionSeed(); - QVERIFY(wallet->walletSecrets().empty()); try { Tx txCopy = wallet->loadTx(theTx.createHash(), pool); @@ -860,7 +858,7 @@ void TestWallet::testEncryption2() // the name if it was saved without obfuscation auto hash = QString::fromStdString(newTx.createHash().ToString()); - txFile = baseName.arg(m_dir).arg(hash.left(2)).arg(hash.mid(2)); + txFile = baseName.arg(m_dir).arg(hash.left(2), hash.mid(2)); QVERIFY(QFile::exists(txFile) == false); auto savedTx = wallet->loadTx(newTx.createHash(), pool); -- 2.54.0 From a9989244dc131fb890078f7c9c68cd6e8d46c558 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Oct 2022 14:12:18 +0200 Subject: [PATCH 0017/1428] Latest Qt --- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 4cf6be2..54ab2d0 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.3.1 +ARG QtVersion=v6.4.0 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index a7ab57e..ade8d63 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.3.1 +QtVersion=v6.4.0 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion -- 2.54.0 From f36d075c646698a8a17b71c4462b23f4194fbbcd Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Oct 2022 22:45:02 +0200 Subject: [PATCH 0018/1428] Make unit test pass again. --- Wallet.cpp | 2 +- Wallet_p.h | 2 -- testing/wallet/TestWallet.cpp | 5 +++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Wallet.cpp b/Wallet.cpp index 7e4f9dd..8a22dd5 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -1626,7 +1626,7 @@ void Wallet::loadWallet() QMutexLocker locker(&m_lock); auto dataSize = boost::filesystem::file_size(m_basedir / "wallet.dat"); Streaming::BufferPool pool(dataSize); - in.read(pool.begin(), dataSize); + in.read(pool.data(), dataSize); if (m_encryptionLevel == FullyEncrypted) { if (!m_haveEncryptionKey) return; diff --git a/Wallet_p.h b/Wallet_p.h index dd4b10e..9c6c9c8 100644 --- a/Wallet_p.h +++ b/Wallet_p.h @@ -83,8 +83,6 @@ enum PrivateSaveTags { }; enum WalletDataSaveTags { - // Separator = 0 - // Index = 1 LastSynchedBlock = 2, WalletName, // deprecated. We no longer save the name in the wallet.dat (since that one can get encrypted) diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index 539c537..ebe83be 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -793,7 +792,7 @@ void TestWallet::testEncryption2() QVERIFY(QFile::exists(txFile)); { - auto wallet = openWallet(seed); + auto wallet = openWallet(/* seed = */ 0); // not encrypted yet auto secrets = wallet->walletSecrets(); QCOMPARE(secrets.size(), (size_t)10); for (auto i = secrets.begin(); i != secrets.end(); ++i) { @@ -808,6 +807,8 @@ void TestWallet::testEncryption2() Tx txCopy = wallet->loadTx(theTx.createHash(), pool); QFAIL("Loading from an encrypted wallet shoud fail"); } catch (...) {} + + seed = wallet->encryptionSeed(); } // a wallet that has been fully encrypted should have encrypted the -- 2.54.0 From 416ba24da86ba1fb8eaaac1231845ee9c03d8e02 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:21:24 +0200 Subject: [PATCH 0019/1428] Use palette instead of hardcoded colors. --- desktop/widgets/TextField.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/widgets/TextField.qml b/desktop/widgets/TextField.qml index 7787329..7707ef5 100644 --- a/desktop/widgets/TextField.qml +++ b/desktop/widgets/TextField.qml @@ -32,7 +32,7 @@ QQC2.TextField { implicitWidth: 140 color: { if (root.enabled) - return Pay.useDarkSkin ? "#2c2f33" : "#e9e8e7"; + return root.palette.base; return "#00000000"; } border.color: { -- 2.54.0 From 59fff48810d1c11eac69e603c1c9018d6363ec15 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:21:36 +0200 Subject: [PATCH 0020/1428] whitespace --- desktop/SendTransactionPane.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/desktop/SendTransactionPane.qml b/desktop/SendTransactionPane.qml index 793b7e5..6852457 100644 --- a/desktop/SendTransactionPane.qml +++ b/desktop/SendTransactionPane.qml @@ -308,7 +308,6 @@ Item { color: mainWindow.floweeGreen y: height + 2 - Label { id: title anchors.horizontalCenter: parent.horizontalCenter -- 2.54.0 From 1dfba73e4cb52365c9368ef15562d2021c2571cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:38:44 +0200 Subject: [PATCH 0021/1428] Fix height. --- desktop/widgets/WarningLabel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/widgets/WarningLabel.qml b/desktop/widgets/WarningLabel.qml index 1f6d385..4860072 100644 --- a/desktop/widgets/WarningLabel.qml +++ b/desktop/widgets/WarningLabel.qml @@ -26,7 +26,7 @@ Item { visible: warningText.text !== "" height: Math.max(warningIcon.height, warningText.height) implicitWidth: warningIcon.width + warningText.implicitWidth - implicitHeight: warningIcon.height + warningText.implicitHeight + implicitHeight: Math.max(warningIcon.height, warningText.implicitHeight) Image { id: warningIcon source: "qrc:/emblem-warning.svg" -- 2.54.0 From 7ea7f5868b53d500b5789b5d9126f0f589ed9716 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:39:43 +0200 Subject: [PATCH 0022/1428] Move more into the flickable This makes the details screen usable on smalller screens. --- desktop/AccountDetails.qml | 166 ++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/desktop/AccountDetails.qml b/desktop/AccountDetails.qml index dca66b1..d794e98 100644 --- a/desktop/AccountDetails.qml +++ b/desktop/AccountDetails.qml @@ -80,65 +80,9 @@ Item { Layout.fillWidth: true focus: true } - Label { - id: encLabel - text: qsTr("Encryption") + ":" - visible: encStatus.visible - } - WalletEncryptionStatus { - id: encStatus - Layout.fillWidth: true - account: root.account - } - - Label { - id: pwdLabel - text: qsTr("Password") + ":" - visible: encStatus.visible - } - Flowee.TextField { - id: passwordField - onAccepted: decryptButton.clicked() - enabled: !root.account.isDecrypted - Layout.fillWidth: true - echoMode: TextInput.Password - visible: pwdLabel.visible - } - Item { - visible: pwdLabel.visible - Layout.fillWidth: true - Layout.columnSpan: 2 - implicitHeight: Math.max(decryptWarning.implicitHeight, decryptButton.implicitHeight) - - Flowee.WarningLabel { - id: decryptWarning - anchors.left: parent.left - anchors.leftMargin: 20 - anchors.right: decryptButton.left - anchors.rightMargin: 10 - } - - Flowee.Button { - id: decryptButton - anchors.right: parent.right - text: qsTr("Open") - enabled: passwordField.text.length > 3 - onClicked: { - var rc = root.account.decrypt(passwordField.text); - if (rc) { - // decrypt went Ok - decryptWarning.text = "" - passwordField.text = "" - } - else { - decryptWarning.text = qsTr("Invalid PIN") - passwordField.forceActiveFocus(); - } - } - } - } } + Flickable { id: scrollablePage anchors.top: basicProperties.bottom @@ -150,13 +94,14 @@ Item { contentHeight: detailsPane.height + 10 + addressesList.height + 10 - ColumnLayout { + GridLayout { id: detailsPane - spacing: 10 width: parent.width - 20 x: 10 + columns: 2 Label { + Layout.columnSpan: 2 text: { var height = root.account.lastBlockSynched if (height < 1) @@ -168,6 +113,7 @@ Item { } } Label { + Layout.columnSpan: 2 id: walletType visible: root.account.isDecrypted || root.account.needsPinToPay font.italic: true @@ -182,53 +128,85 @@ Item { return qsTr("This wallet is a simple multiple-address wallet.") } } - RowLayout { - width: parent.width - Label { - id: xpubLabel - // at the moment I don't see a point if making this translatable. Let me know if that should change! - text: "xpub" + ":" - visible: root.account.isHDWallet - } - Flowee.LabelWithClipboard { - Layout.fillWidth: true - visible: xpubLabel.visible - text: root.account.xpub - clipboardText: text - menuText: qsTr("Copy") - } - } - - /* TODO, features to add; Label { - text: qsTr("Security:") - Layout.columnSpan: 3 - font.italic: true + id: xpubLabel + // at the moment I don't see a point if making this translatable. Let me know if that should change! + text: "xpub" + ":" + visible: root.account.isHDWallet + } + Flowee.LabelWithClipboard { + Layout.fillWidth: true + visible: xpubLabel.visible + text: root.account.xpub + clipboardText: text + menuText: qsTr("Copy") } - Flowee.CheckBox { - id: schnorr - checked: true - text: qsTr("Use Schnorr signatures"); + Label { + id: encLabel + text: qsTr("Encryption") + ":" + visible: encStatus.visible } - Flowee.CheckBox { - id: syncOnStart - checked: false - text: qsTr("Sync on Start"); + WalletEncryptionStatus { + id: encStatus + Layout.fillWidth: true + account: root.account } - Flowee.CheckBox { - id: useIndexServer - checked: false - text: qsTr("Use Indexing Server"); + + Label { + id: pwdLabel + text: qsTr("Password") + ":" + visible: encStatus.visible + } + Flowee.TextField { + id: passwordField + onAccepted: decryptButton.clicked() + enabled: !root.account.isDecrypted + Layout.fillWidth: true + echoMode: TextInput.Password + visible: pwdLabel.visible + } + Item { + visible: pwdLabel.visible + Layout.fillWidth: true + Layout.columnSpan: 2 + implicitHeight: Math.max(decryptWarning.implicitHeight, decryptButton.implicitHeight) + + Flowee.WarningLabel { + id: decryptWarning + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.right: decryptButton.left + anchors.rightMargin: 10 + anchors.baseline: decryptButton.baseline + } + + Flowee.Button { + id: decryptButton + anchors.right: parent.right + text: decryptWarning.implicitHeight// qsTr("Open") + enabled: passwordField.text.length > 3 + onClicked: { + var rc = root.account.decrypt(passwordField.text); + if (rc) { + // decrypt went Ok + decryptWarning.text = "" + passwordField.text = "" + } + else { + decryptWarning.text = qsTr("Invalid PIN") + passwordField.forceActiveFocus(); + } + } + } } - */ } Flowee.GroupBox { id: addressesList width: parent.width anchors.top: detailsPane.bottom - anchors.topMargin: 20 + anchors.topMargin: 10 title: qsTr("Address List") collapsed: !root.account.isSingleAddressAccount visible: root.account.isDecrypted || !root.account.needsPinToOpen -- 2.54.0 From ada9ba635f9cb6b4b3f3150dd28575d14d4d1564 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:42:59 +0200 Subject: [PATCH 0023/1428] Fix height of flickable. --- desktop/AccountDetails.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/AccountDetails.qml b/desktop/AccountDetails.qml index d794e98..af8cac4 100644 --- a/desktop/AccountDetails.qml +++ b/desktop/AccountDetails.qml @@ -92,7 +92,7 @@ Item { anchors.margins: 10 clip: true - contentHeight: detailsPane.height + 10 + addressesList.height + 10 + contentHeight: detailsPane.height + 10 + addressesList.height + 10 + hdDetails.height + 10 GridLayout { id: detailsPane -- 2.54.0 From 3851ef0a254efa6434ee5d7ea4901c08fafe193d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 21 Oct 2022 16:58:04 +0200 Subject: [PATCH 0024/1428] UX: make rest of screen more discoverable When the user selects a big button, move the screen down to make clear that this was just a big-ass-tab-header, not an actual action button. --- desktop/CardTypeSelector.qml | 2 +- desktop/NewAccountPane.qml | 13 +++++++++++-- desktop/WalletEncryption.qml | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/desktop/CardTypeSelector.qml b/desktop/CardTypeSelector.qml index 6cb9e68..0813b10 100644 --- a/desktop/CardTypeSelector.qml +++ b/desktop/CardTypeSelector.qml @@ -78,6 +78,6 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: optionsRow.selectedKey = parent.key + onClicked: root.clicked() } } diff --git a/desktop/NewAccountPane.qml b/desktop/NewAccountPane.qml index 7dabde8..a61d5d1 100644 --- a/desktop/NewAccountPane.qml +++ b/desktop/NewAccountPane.qml @@ -1,5 +1,5 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,13 +78,21 @@ FocusScope { width: Math.min(contentArea.width - 30, 900) // smaller is OK, wider not property int selectorWidth: (width - spacing * 2) / 3; - property int selectedKey: 1 + + function cardClicked(key) { + if (selectedKey !== key) + selectedKey = key; + else // move scroll area. + contentArea.flick(0, -1000); + } + CardTypeSelector { id: accountTypeBasic key: 0 title: qsTr("Basic") width: parent.selectorWidth + onClicked: parent.cardClicked(key); features: [ qsTr("Private keys based", "Property of a wallet"), @@ -97,6 +105,7 @@ FocusScope { key: 1 title: qsTr("HD wallet") width: parent.selectorWidth + onClicked: parent.cardClicked(key); features: [ qsTr("Seed-phrase based", "Context: wallet type"), diff --git a/desktop/WalletEncryption.qml b/desktop/WalletEncryption.qml index 4314c44..383063f 100644 --- a/desktop/WalletEncryption.qml +++ b/desktop/WalletEncryption.qml @@ -76,11 +76,19 @@ FocusScope { property int selectorWidth: (width - spacing * 2) / 2; property int selectedKey: 0 + function cardClicked(key) { + if (selectedKey !== key) + selectedKey = key; + else // move scroll area. + contentArea.flick(0, -1000); + } + CardTypeSelector { id: pinToPay key: 0 title: qsTr("Pin to Pay") width: parent.selectorWidth + onClicked: parent.cardClicked(key); features: [ qsTr("Protect your funds", "pin to pay"), @@ -93,6 +101,7 @@ FocusScope { key: 1 title: qsTr("Pin to Open") width: parent.selectorWidth + onClicked: parent.cardClicked(key); features: [ qsTr("Protect your entire wallet", "pin to open"), -- 2.54.0 From 9c0a45b6d145a7ddeb1dc4601bd39383d7aff23c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Oct 2022 11:48:17 +0200 Subject: [PATCH 0025/1428] Remove debug --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f167ec..1890238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,6 @@ endif() if (NOT ANDROID) set (NOT_ANDROID TRUE) - message("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") endif() option (BUILD_DESKTOP_PAY "Build the desktop (dev) client of Pay" -- 2.54.0 From 7074d3f98cacf8a03494cfb01e876939eb5da545 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Oct 2022 15:27:08 +0200 Subject: [PATCH 0026/1428] UX fixlets --- desktop/SendTransactionPane.qml | 2 +- desktop/widgets/Dialog.qml | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/desktop/SendTransactionPane.qml b/desktop/SendTransactionPane.qml index 6852457..f5c368c 100644 --- a/desktop/SendTransactionPane.qml +++ b/desktop/SendTransactionPane.qml @@ -793,7 +793,7 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: locked - onClicked: { + onClicked: (mouse)=> { // make it easy for the user to close a menu with either mouse // button without instantly triggering another action. if (coinsListView.menuIsOpen) { diff --git a/desktop/widgets/Dialog.qml b/desktop/widgets/Dialog.qml index a1e1448..50031d4 100644 --- a/desktop/widgets/Dialog.qml +++ b/desktop/widgets/Dialog.qml @@ -16,8 +16,8 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick.Controls +import QtQuick.Layouts import "." as Flowee; Popup { @@ -78,6 +78,7 @@ Popup { } Label { id: mainTextLabel + // this next line will always create a binding loop. But its harmless, so ignore that comment. width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere } @@ -88,7 +89,8 @@ Popup { Flowee.DialogButtonBox { id: buttons standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - width: parent.width + anchors.right: parent.right + anchors.rightMargin: 10 onAccepted: { root.accepted(); root.close() -- 2.54.0 From 5eb8ef1dec0573f3d23a5d8dd1e6f5cd9b60042b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 4 Nov 2022 11:10:06 +0100 Subject: [PATCH 0027/1428] Tweak categories --- desktop/org.flowee.pay.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/org.flowee.pay.desktop b/desktop/org.flowee.pay.desktop index 6955476..3da2ce3 100644 --- a/desktop/org.flowee.pay.desktop +++ b/desktop/org.flowee.pay.desktop @@ -7,7 +7,7 @@ X-GNOME-FullName=Bitcoin Cash Payment Solution Comment=Transact on the Bitcoin Cash network, payments and more Keywords=wallet;bitcoin;bitcoincash;financial; Type=Application -Categories=Qt;KDE;Office; +Categories=Qt;Office;Finance MimeType=; Exec=pay Terminal=false -- 2.54.0 From a2530957f0bf5fe2e722abea241850b4ade40741 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 7 Nov 2022 14:31:38 +0100 Subject: [PATCH 0028/1428] update for Qt6 --- README.md | 32 ++++++++++++++++++++------------ Wallet.cpp | 3 +++ android/docker/Dockerfile | 8 ++++---- android/docker/scripts/aurs.sh | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 038be17..d4731d3 100644 --- a/README.md +++ b/README.md @@ -34,30 +34,39 @@ The minimum version required for the Flowee libraries is 2022.07.0 For ubuntu getting the latest is a matter of calling: -``` +``` sh sudo add-apt-repository ppa:flowee/ppa sudo apt update sudo apt install flowee-libs ``` -You need cmake and Qt5. When you have those installed it is just a matter -of calling: +Additionally you will want to have some development packages installed; +``` sh + sudo apt install build-essential libboost-all-dev libssl-dev cmake \ + qt6-tools-dev-tools qt6-tools-dev libqrencode-dev ``` + +After installing that succesfull, it is just a matter of calling: + +``` sh mkdir build cd build cmake .. make install ``` -We depend on the libraries shipped in 'theHub', also from Flowee. -If you compile theHub yourself you may want to export the -following variable in case the build wasn't found in the 'cmake' line above: +### Manually compiling flowee-libs: - export CMAKE_PREFIX_PATH=/path/to/the/thehub-build - -Followed with again the call to cmake and make like above. +We depend on the libraries shipped in 'theHub' git repo, also from Flowee. +If you compile that yourself, you need to make sure you `make install` it. +The Flowee Pay buildsystem may not find this if the install directory is not the +same as the packaged. In that case you may want to replace the cmake call +above with this slightly more complex one: +``` sh + cmake -DCMAKE_PREFIX_PATH=/path/to/the/thehub-installed-dir .. +``` ## DEVS @@ -74,9 +83,8 @@ following workflow: make install ``` -The executables will be in `floweepay/build/bin/` and by passing the `local_qml` -additional -cmake option the app will renember that it should fetch the QML files from +The executables will be in `floweepay/build/bin/` and by adding the `local_qml` +cmake option the build will bake in the path to your QML files. On your local harddrive. This allows you to change the QML files and simply restart the app without recompile. diff --git a/Wallet.cpp b/Wallet.cpp index 8a22dd5..d0b9c4a 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -656,6 +656,9 @@ void Wallet::setTransactionComment(const Tx &transaction, const QString &comment emit startDelayedSave(); } } + else { + logCritical() << "Comment added to not known transaction"; + } } std::map Wallet::walletSecrets() const diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 54ab2d0..5f93537 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,8 +8,8 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -#copy mirrorlist /etc/pacman.d/ -RUN useradd builduser -d /home/builds -m -u 1000 -U \ +copy mirrorlist /etc/pacman.d/ +RUN useradd builduser -d /home/builds -m -u 1100 -U \ && pacman -Sy --noconfirm base-devel \ cmake \ ninja \ @@ -44,7 +44,7 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && /usr/local/bin/createRootPwd # to enable caching, check the README.md -#add cache /usr/local/cache/ +add cache /usr/local/cache/ RUN mkdir -p /usr/local/cache \ && git config --global advice.detachedHead false \ && /usr/local/bin/aurs.sh \ @@ -57,4 +57,4 @@ RUN mkdir -p /usr/local/cache \ && rm -rf ~builduser/* \ && /usr/local/bin/buildQrEncode.sh \ && rm -rf ~builduser/* \ - && rm -rf /usr/local/cache + #&& rm -rf /usr/local/cache diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index d402c11..281be6d 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -1,7 +1,7 @@ #!/bin/bash # this runs as root. -for i in android-ndk android-sdk-platform-tools android-sdk-cmdline-tools-latest android-platform +for i in android-ndk android-sdk-platform-tools android-sdk-cmdline-tools-latest android-platform android-sdk-build-tools do cd ~builduser git clone https://aur.archlinux.org/$i.git -- 2.54.0 From 007b7321127becf6182a5df3e213910ad345544e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 7 Nov 2022 15:52:12 +0100 Subject: [PATCH 0029/1428] Add missing dependency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4731d3..c81f2fc 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Additionally you will want to have some development packages installed; ``` sh sudo apt install build-essential libboost-all-dev libssl-dev cmake \ - qt6-tools-dev-tools qt6-tools-dev libqrencode-dev + qt6-tools-dev-tools qt6-tools-dev qt6-declarative-dev libqrencode-dev ``` After installing that succesfull, it is just a matter of calling: -- 2.54.0 From bf6230f83deb584830e94c5796632007e0b8544b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 15:49:24 +0100 Subject: [PATCH 0030/1428] Add another missing dependency --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c81f2fc..7f23e44 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,9 @@ Additionally you will want to have some development packages installed; ``` sh sudo apt install build-essential libboost-all-dev libssl-dev cmake \ - qt6-tools-dev-tools qt6-tools-dev qt6-declarative-dev libqrencode-dev + qt6-tools-dev-tools qt6-tools-dev qt6-declarative-dev libqrencode-dev \ + libqt6svg6-dev + ``` After installing that succesfull, it is just a matter of calling: -- 2.54.0 From 78a5a49e7b4fd77a1dd70ac101ce05dcda923c52 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 15:49:53 +0100 Subject: [PATCH 0031/1428] remove no longer needed line. --- main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.cpp b/main.cpp index 635e0c1..e932c28 100644 --- a/main.cpp +++ b/main.cpp @@ -71,8 +71,6 @@ int main(int argc, char *argv[]) qapp.setApplicationVersion("2022.09.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); - srand((quint32) QDateTime::currentMSecsSinceEpoch()); - qmlRegisterType("Flowee.org.pay", 1, 0, "Bitcoin"); qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); -- 2.54.0 From 5cec6fee76f9eb952c9451b3625626939ac2ba75 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 15:50:04 +0100 Subject: [PATCH 0032/1428] Add various Android pieces of support This adds the AndroidManifest and icons in order to make sure and APK will have the proper name and the icons. --- CMakeLists.txt | 75 ++++++++++++--------- android/AndroidManifest.xml | 38 +++++++++++ android/res/drawable-hdpi/icon.png | Bin 0 -> 2918 bytes android/res/drawable-hdpi/logo.png | Bin 0 -> 705 bytes android/res/drawable-hdpi/logo_port.png | Bin 0 -> 705 bytes android/res/drawable-ldpi/icon.png | Bin 0 -> 868 bytes android/res/drawable-ldpi/logo.png | Bin 0 -> 491 bytes android/res/drawable-ldpi/logo_port.png | Bin 0 -> 491 bytes android/res/drawable-mdpi/icon.png | Bin 0 -> 1142 bytes android/res/drawable-mdpi/logo.png | Bin 0 -> 577 bytes android/res/drawable-mdpi/logo_port.png | Bin 0 -> 577 bytes android/res/drawable-xhdpi/icon.png | Bin 0 -> 4252 bytes android/res/drawable-xhdpi/logo.png | Bin 0 -> 866 bytes android/res/drawable-xhdpi/logo_port.png | Bin 0 -> 866 bytes android/res/drawable-xxhdpi/icon.png | Bin 0 -> 3437 bytes android/res/drawable-xxhdpi/logo.png | Bin 0 -> 1247 bytes android/res/drawable-xxhdpi/logo_port.png | Bin 0 -> 1247 bytes android/res/drawable-xxxhdpi/icon.png | Bin 0 -> 5187 bytes android/res/drawable-xxxhdpi/logo.png | Bin 0 -> 1728 bytes android/res/drawable-xxxhdpi/logo_port.png | Bin 0 -> 1728 bytes android/res/drawable/splashscreen.xml | 11 +++ 21 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 android/AndroidManifest.xml create mode 100644 android/res/drawable-hdpi/icon.png create mode 100644 android/res/drawable-hdpi/logo.png create mode 100644 android/res/drawable-hdpi/logo_port.png create mode 100644 android/res/drawable-ldpi/icon.png create mode 100644 android/res/drawable-ldpi/logo.png create mode 100644 android/res/drawable-ldpi/logo_port.png create mode 100644 android/res/drawable-mdpi/icon.png create mode 100644 android/res/drawable-mdpi/logo.png create mode 100644 android/res/drawable-mdpi/logo_port.png create mode 100644 android/res/drawable-xhdpi/icon.png create mode 100644 android/res/drawable-xhdpi/logo.png create mode 100644 android/res/drawable-xhdpi/logo_port.png create mode 100644 android/res/drawable-xxhdpi/icon.png create mode 100644 android/res/drawable-xxhdpi/logo.png create mode 100644 android/res/drawable-xxhdpi/logo_port.png create mode 100644 android/res/drawable-xxxhdpi/icon.png create mode 100644 android/res/drawable-xxxhdpi/logo.png create mode 100644 android/res/drawable-xxxhdpi/logo_port.png create mode 100644 android/res/drawable/splashscreen.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 1890238..1ecd686 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -cmake_minimum_required(VERSION 3.13) -project(flowee_pay) +cmake_minimum_required(VERSION 3.19) +project(flowee_pay VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -25,7 +25,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) -find_package(Qt6 COMPONENTS Core Quick REQUIRED) +find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED) find_package(flowee REQUIRED flowee_p2p) find_package(OpenSSL REQUIRED) if (ANDROID) @@ -78,27 +78,24 @@ target_link_libraries(pay_lib ${Boost_LIBRARIES} Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${QREncode_LIBRARIES}) -#### Translations +###### Translations if(NOT ANDROID) - # Linguist is too big a dependency, just use the native-build to get the qm files - set (TS_FILES_DESKTOP + # Skip for Android: Linguist is too big a dependency + # We check lower if they have magically appeared: just use the native-build + # and copy the resulting qm files to your android build. + set (TS_FILES translations/floweepay-desktop_en.ts translations/floweepay-desktop_nl.ts translations/floweepay-desktop_pl.ts - ) - qt6_add_translation(qmFiles1 ${TS_FILES_DESKTOP}) - set (TS_FILES_COMMON + translations/floweepay-common_en.ts translations/floweepay-common_nl.ts translations/floweepay-common_pl.ts - ) - qt6_add_translation(qmFiles2 ${TS_FILES_COMMON}) - set (TS_FILES_MOBILE translations/floweepay-mobile_nl.ts translations/floweepay-mobile_pl.ts ) - qt6_add_translation(qmFiles3 ${TS_FILES_MOBILE}) + qt6_add_translation(qmFiles ${TS_FILES}) add_custom_target(i18n COMMAND lupdate desktop/qml.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts @@ -137,7 +134,7 @@ if(BUILD_DESKTOP_PAY) desktop/qml.qrc desktop-i18n.qrc ) - target_link_libraries(pay pay_lib) + target_link_libraries(pay pay_lib Qt6::Svg) install(TARGETS pay DESTINATION bin) endif() @@ -145,28 +142,43 @@ endif() ###### Pay-mobile executable option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) -if(BUILD_MOBILE_PAY) - configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) - if (local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/mobile) - endif() +if (ANDROID AND BUILD_MOBILE_PAY) configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) - if(ANDROID) - # the dependency of linguist is pretty steep for cross-compilation - # so lets accept the lack of compiled translations here. - # A build that wants them should copy them from a native build. - - if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qml") - set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) - endif() + if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") + message ("Found translation files, adding to build binary") + set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) + configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) endif() - add_executable(pay_mobile + + qt6_add_executable(pay_mobile main.cpp mobile/qml_path_helper.cpp mobile/qml.qrc ${MOBILE_PAY_I18N_QRC} ) - target_link_libraries(pay_mobile pay_lib) + target_link_libraries(pay_mobile pay_lib Qt6::Svg) + set_target_properties(pay_mobile PROPERTIES + QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android + ) + + qt6_android_generate_deployment_settings(pay_mobile) + qt_android_add_apk_target(pay_mobile) +endif () + +if(NOT ANDROID AND BUILD_MOBILE_PAY) + if(local_qml) + set (QML_PATH ${CMAKE_SOURCE_DIR}/mobile) + endif() + configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) + configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) + + add_executable(pay_mobile + main.cpp + mobile/qml_path_helper.cpp + mobile/qml.qrc + mobile-i18n.qrc + ) + target_link_libraries(pay_mobile pay_lib Qt6::Svg) install(TARGETS pay_mobile DESTINATION bin) endif() @@ -177,7 +189,7 @@ option (BUILD_PAY_TOOLS "Build the tools Pay" ${NOT_ANDROID}) -if(BUILD_PAY_TOOLS) +if (BUILD_PAY_TOOLS) add_executable(blockheaders-meta-extractor MetaExtractor.cpp ) @@ -222,6 +234,9 @@ if (${BUILD_DESKTOP_PAY}) endif() if (${BUILD_MOBILE_PAY}) message ("-> Building Pay for mobile") + if (ANDROID AND DEFINED MOBILE_PAY_I18N_QRC) + message (" Found translation files, including in package") + endif () endif() if (${BUILD_PAY_TOOLS}) message ("-> Building Pay tools") diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 0000000..ed4d27a --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3aff42c47bdd631518fd9a9089fe75dcc422c7 GIT binary patch literal 2918 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84rT@hh9qO>QU(TQh5(-sR|XI;W?)ETV3^6k zaE5{5KZ7v?gRwD#aTA~Jbeb{4Ok;*K z#ti>K_81!*8>bl?&onkZV{H5%WNn(Uaax*j+DzlLGsbEEL9Uo-Y&T6i3~B$3)BdNW{hyik|4iEd|1&{hXFL-WIx}a2{Qn;mHXtvh%>+66%uM6| zpr8U-oHi3=^qHAy|3M)Kl1`fm0%vB<{0|DqGsYlm&&-^8=FH49|7ZSZnEBs$=Kr*r z|7Xtpe`ecP#mP)# zkPl|gJOeWNKPZaM7=t_n;-5Kl=Kq=h3}^lupZTA5=Ksty|IeKH{~r{A;OGar0vw~D z_yk81$Wh>M2SqP9C_!!nhYl#V!5#)V8SDp8-9^MO69J~L_p4<@J`~CZ`YuBIk^c@bd=(^H8@$c-( zzh_Op*EuoOxm(_#&(L?OPsnor;1vogeP$Y+u7+J!TAczy9R;)2t$uZU`Lm;n`V(rq zc0B&^=E2ujCN?wUrY`EezgbqVx4ZY?Yc`WY1_lPs0*}aI1_r((Aj~*bn@^g7frH1> z#W5tJb!mic)onL{quX!4iDS8)Fl)ml-7{_lf;$#Uz2w-&Ey;Uvg1g788?AP$1oZ_) z%NDim@q4y+ChNf^-JV^K_{+YXoo#M^TJ4EM5})BRiTY=y!nbGKme?(E`;YPZ*&)84 zKK_qm|I6+Y>!a#%A>6U6L}@?AJnMt)u`lnP+@1IL^lqUd?YmL;@4ec-HtArKg5j&w zoeS5eMc%8omN~EgTlf9$OjfQ(kNNj^#{Ik)KmXkG=NrY>ad)j~Vbi-5D0zO5>~{V= z{@+)NiC^1lZxy@2pv8NCht=c;9rL{4d-KmMKRNt@QT?xMaE3Mz=1Sts^kC8!YDf^CfYZ)W!_PMJ)LNA-)pxwvo}olEYx)NE%UcLW*$$OCPxk@^ECzubvim*5dY=S3V9G z6+Jt*WG$>Ju{CnN62|?y#DD(2D_3oK=k`>*ah$&3TgRURvu}4R2pTwOscd+0c}~px z*~d?pvZQQd_2JOLfQA| zrbkBwbb~r42(8F7w$yMi$tf=r|CFXWf2y;mZ1##Hv72AGg~cycJbGyLkTp_Vct2myyExMB@wLVLjvW!j4a_{q;r3*Lf2fs-_zNIfl z`O;admk|TBt|-^Oh1oO1SEavYy(qnp`_u2&uV25{`Ch*KGF6#ek6mw@7Mrc>WZ&*ThezUx z^lO&y!k_-SRk};p?JVMXqtA2nOJ~7{FSE~UuWGv2WcGH#g}sLymF;#f>d$}veCjEE zUEbCoCvAiGMIP?bu0Fq#sW#xD?}6`+RGp1|l_o8esyfWRaCvn6S<~PkE%V|l2lkv| z;CRkv#N~a+bWzMd1-ZYkeQvKvIDE`{z0xV|l(~yOn_ias8Ts#QU`O`zU(-IFU9)UK z>gp{LO7&|y=J79%V01rtqWH(v33WFdJ?ht-WhplM&Ohy+wWHbkmhWG^76@cdW2tjI zB9gVZAAwdDe0r_#a}YitYa@Yw3spxn2}sdb6;wY;@& zuSu0Te&IRYI)C$`X;0qr-t;fL?eJ9BNHA)n+Vu5Pb5*CbUfV6s&26#EyzfIGdz^up z`!~jGYrb_UuRN>9u5@bOYlrhDAvb#@w%_5rxAo0RtKKW0YK2zY|I+9*tGPbat0U;u zrrIT8UaFnM**Qf3!xB zsrTZ)m%Go4-(RVsr|v1lBdA@HETS>jB|1ksd%M}z-t_raudNQ}?436El7*DovTaIT zSJzkVH2sm-$#F_x*CD1X-v%9%=F|gsr-eGNxf|8XU-IEm)Pv)7&kWv*xA6uX6x<|a zw9;L6_HD0Cni=zAj`SFtUy+nw=Hq?Kl}-Bu|81Tt6RJNd{MOm&((>LjZk@6IgXA1u zH#4n`D(8dVh^$RmtX6ALU27ne%g3uRy>tiXvc=YNs~S3_9-o-B;$G3+`;8}F^Zz~F zy2O3;NnS6}O+~MG*7xho;|rH5oRoV1$<7-*w`!PObmq?TIg~8E>C6evqc(eAy$rM1 znXEO>*(}`T@ZQR8?_W)nG-Z#y$G@^RSK`rD@!JcVuAEsbQ#tuYhatz*i$||H?|s1+ zX0iD?M~~#3I-9I}x3(M2f0+B=(9<9FnhV7gxTYD;_Ds2U@y@N8U3$B6@)Ga+r}yvw z!C=)tNq5$Y*9uoZtX5pPXj(=`j@;jZlcv{a&TV_E@GyCkQdDl++4i#rIX1OX$rJ1i z3vRsPZ+|)a-^(+J%K2;dGRkbZ<}DJaXBL`xBX!cFJZf)9iZ-HYG7m*{M-!oLulGB%#&f_|8Cv%;2rl--d|ja`#rw`+uayEcADv%Xc%r zJvz*RUxY44MSl>M{X5|W^Y*Pa;vZuqBwWh$O6I8j{K;MMVngASF2;>t%e*$qZ}3XJ z!4;Jvw8bSPOL5uZI4N<)M`u#o&9k!g>TTS9%Up;E4&}M_GG~YXn_tToYZxoVAO7Z< zS=2jq{xPk}ZQK=Y=hhizzy33`)B61Wm6aDZ$=7Loxzb{pJSVbi)6>2c1$vCmlh3Gb z3Y^0==TXOr6WU)K_FRiPc5VJsBmbJXnp+X<>c3MD`0scrKXYH^o|Uqt;-L1jr>mdK II;Vst0E+y6*8l(j literal 0 HcmV?d00001 diff --git a/android/res/drawable-hdpi/logo.png b/android/res/drawable-hdpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..49f852fe68bc3be63f0fbb68052e7f74228b1531 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0y~yU;twdW(Ed^51)!xGB7Y~4DbnY{C58X3Fdo+pd(rmXy?anJX4%H!rG4YO3_=s;pdIcXD-Setl={8`^xNpy%^T#Utj~%;qe0SA} zTQ^U9x^=#O!uj)eZnRFgapTebZ8;B4FMrrI@!`W4kFJzH{_*x@|D=~M-+h=e`NM}V zKW0w;@#ELu^9lc+Z}>lV%K!iW85kJSrll=oU|_H=3Gxg6Pc~p!c*I(cfq{Xuz$3Dl zfr0M`2s2LA=96Y%V9fV)aSVxQeS7V;UyFeN+k-U2HtvfNrjuT{y%X4cM0K|5-0$^< zigRwg+%hF-arUqHi_2$x|eVXaDo>lang&oXT}KBm7W7swq|lHin#O*y&#*@Y)}CE^xO z{{3+0ovp`%8&#%;SZS8d$++ORYWvNt-*1_QP2Mzfh411WE^1mzd&To7&Ggv3!(-W| zNxsLgvIS|bDbOsMsdH#@O_Ig$2&q5WOKLXfE!o!h;DG9cQz7e@Cg&S*`gPy-GQtISf_LCf+VlSlbn%JXsRBRHf zx}@*EbB_vTHU9>;R6aiSUrQ<9?c>?GM`oMOdv!dnCM_=|IEBak|FQS{?dmpM6Th)U z-chP_eDe6T#O)R5HTj;bz5KQFsN$rFJt->6KiN;tm@%V%Zoc~emR`%EXUPl<3=E#G KelF{r5}E*`r**{u literal 0 HcmV?d00001 diff --git a/android/res/drawable-hdpi/logo_port.png b/android/res/drawable-hdpi/logo_port.png new file mode 100644 index 0000000000000000000000000000000000000000..49f852fe68bc3be63f0fbb68052e7f74228b1531 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0y~yU;twdW(Ed^51)!xGB7Y~4DbnY{C58X3Fdo+pd(rmXy?anJX4%H!rG4YO3_=s;pdIcXD-Setl={8`^xNpy%^T#Utj~%;qe0SA} zTQ^U9x^=#O!uj)eZnRFgapTebZ8;B4FMrrI@!`W4kFJzH{_*x@|D=~M-+h=e`NM}V zKW0w;@#ELu^9lc+Z}>lV%K!iW85kJSrll=oU|_H=3Gxg6Pc~p!c*I(cfq{Xuz$3Dl zfr0M`2s2LA=96Y%V9fV)aSVxQeS7V;UyFeN+k-U2HtvfNrjuT{y%X4cM0K|5-0$^< zigRwg+%hF-arUqHi_2$x|eVXaDo>lang&oXT}KBm7W7swq|lHin#O*y&#*@Y)}CE^xO z{{3+0ovp`%8&#%;SZS8d$++ORYWvNt-*1_QP2Mzfh411WE^1mzd&To7&Ggv3!(-W| zNxsLgvIS|bDbOsMsdH#@O_Ig$2&q5WOKLXfE!o!h;DG9cQz7e@Cg&S*`gPy-GQtISf_LCf+VlSlbn%JXsRBRHf zx}@*EbB_vTHU9>;R6aiSUrQ<9?c>?GM`oMOdv!dnCM_=|IEBak|FQS{?dmpM6Th)U z-chP_eDe6T#O)R5HTj;bz5KQFsN$rFJt->6KiN;tm@%V%Zoc~emR`%EXUPl<3=E#G KelF{r5}E*`r**{u literal 0 HcmV?d00001 diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3fa338907700e8307a0bb9889b1ff6865fc8c1f7 GIT binary patch literal 868 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4rT@h2A3sW#~2tGHwXBHxN-}0@QZYsTF&D*>QnY|9*dd`t(Cn%K>6}iql&z>mTZhoN-Zov*-;f|KWo98^+JL~Bl5!r5aoynj6eA~YN zX5XVN!m?eOx>F7wx^ELd-7SB%Tiz@_u}%ru-pcCTu?cIH#V+h%U|`@Z@Q5sCVBk9f z!i-b3`J@>b7{fhX978O6uU@zsbT~kO?SZb?oB$5L3rUTS^p0`{-I=R!@a|IGfGrLc zDQ3P^Ufwfas=hgT^5&oWK4r`gf?xjm!@Pk>v18GS1dfBoCOUJD2u=(v*&?USmL%EM z*<4{G{Nl)x6jguE)W~n@2c~DfESfj*YjRlF_FS;sSsC$DOXGpXx(yx24sZnY zoS$^bs8oj4V)Y&-;ax(jE`(05+|Sq`)swZ5FLJM0hQkF8U$x-kZnF&63v6$BE?@L? z5I_I*cYX)fcK1aUJaP}7e0g)nvfJp% z(Wh6pO5cgl5u5w^y|zQPZ+zp`wB37^@9w>uSO0;fDenIdmtE<1-aeIY3U7b>c5aFM gB$e7j@vHTJ-AE{rx4&4!z`(%Z>FVdQ&MBb@08uHV4FCWD literal 0 HcmV?d00001 diff --git a/android/res/drawable-ldpi/logo.png b/android/res/drawable-ldpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3da5feac3597702b43648dce233886b6739c5f GIT binary patch literal 491 zcmeAS@N?(olHy`uVBq!ia0y~yV9*3%4rT@hhWYzG9ARK!*cjjw;>s=1VII}*soUx4 zHQA?nYRr_`DYjiHfh{Si^DA_8&WT?fCAh z6Sr=j_;l-h{e<)9@7!peaO1|K``dCJoL>I0YvRL)FCJYfef;C?%l=6(U%vY=W%7p) zUw+J-{Nu;3zvmPFJ>T$u?v(%k|1&T!q)kg(#=yW}T@vIM{GV*Vu<(er90LObXMsm# zF#`kN5fEmas?8_Oz`!um)5S5wqWA4ZU#s=1VII}*soUx4 zHQA?nYRr_`DYjiHfh{Si^DA_8&WT?fCAh z6Sr=j_;l-h{e<)9@7!peaO1|K``dCJoL>I0YvRL)FCJYfef;C?%l=6(U%vY=W%7p) zUw+J-{Nu;3zvmPFJ>T$u?v(%k|1&T!q)kg(#=yW}T@vIM{GV*Vu<(er90LObXMsm# zF#`kN5fEmas?8_Oz`!um)5S5wqWA4ZU#n2Y*{_x?uKY#w%JJ078>^OD$(T^X$E30?%i*$B&AN=#@ z@0P7M%&cbp{rfK?Yhzu*p6}m(scB6rtK79~_boo*&I=cwCZ?=gy!3oz%` zcJIFN@86%le}1WHO>y;HuyFDDgNsZ5Ue5b@Azw+U&Dnka(&ZNp&d>gNCQ(VLrEOO4 z{#*TfZ}cl_cBW@;>YH%v;Nr5sm-ByKEJ$6jwC~XtX|3J^2ksm`eDB}CzrTO~R#Kak zoVNbguRnT5(+(eb@cZ|l)Qk;>kK9*~>FAH{J6ttkZEnAxS(lF6MDKt_1x4F@0+uSt zblmHl^ndP@-?JxYx%Kc0cYONuZQX_|y-~fdCr%O)>9}$8rL0o_w&DpEnw{6LztGT` zyehl@_pHe(vK?#IUXoGl_cHEUpWDwP(DCEPuiblYyL&IraOr+EVbb3@lYh>fEG5yw zBh(RCKfkr-P*dxH*o3vp8j}oEJ8L#?TJ+-Jf~Wf>ReF|gIQQn)r&m8ds_Rb4U%#U7 z;bw7_?oFGoY3WT>H0@I~=`*vOX=*uBK(s4q&Xbc23=EtF9+AZi417mGm~pB$pELsl z<9$yT#}JRs~J(;=hv6FpH;-@sF)}iXlNk7{z+A( z(ZOq$Bb$r&sV3Cjrfw z4UDnr;v0;@+1ShtPTyrVopI6Bz@Vb(?T1fk9~fVMNmW>K@VxvQg9b$bA9lej2LyP+ zBn}Bkv9UZ9Y%z6dGIcR@InLmiBa_JcF@cwpr&D3&k`xnO&6N%lruFGeVZ1is8Zkoq^GN{eqeAr;{1PBSdC%tO2&Gh%Ylb)l{##wW1jWmn6JZzN^UdWXvehN+kI-g zm{ZoDJ{QP)*n|1Dp2Cg0t!h@K`~q+8w)-B|P+($UkbCg(EQ@e&76StVgQu&X%Q~lo FCII0O8wLOX literal 0 HcmV?d00001 diff --git a/android/res/drawable-mdpi/logo.png b/android/res/drawable-mdpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..90abfc81fc9202526dc50e86e2c1d3128301ab77 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU@!+^4rT@hhWV^YB@7G<8v}eoT)720%%l1}bvr%1 zCi_%RjhQk##kMOYuq7pRUgnC0+0Bb8lA0>Lx+*J|*PUG5nP1;oJGm>QyQ^!%geP04 zpWi(rwr9r7ol8@Dmo7W7Y)<|1?T6RAKCv;UcjLwryJwW`Del{|=lrqC{$t0k9p7Db z;?~U*pKhJ6pK$*Cog1waZrpfue_PIj)5{-rO?>$9#iJ{wkAJ*<*+1#!%Xc59O#blU z%a56pfBg9M_k6;?=Ntado$~+xe+CAIv}tL}7#J9=OM?7@|C0?E79O#dV_;z5Ebxdd zW?X?_wfUqO7#QArx;Tb-biTbD%iUxk!j`a+W7h-^#a|5{^meV-p!4nDfBR{9 zw`^|v`E?)ou-D2+{(C7;(?V6|2_c&Ezw^&r6y$2yvvsM$=hobfOLw3(GSRNf29yD#UTJo=>Pc!}EMj@QP%w@)EF2T7(8A5T-G@yGywpmUqJr= literal 0 HcmV?d00001 diff --git a/android/res/drawable-mdpi/logo_port.png b/android/res/drawable-mdpi/logo_port.png new file mode 100644 index 0000000000000000000000000000000000000000..90abfc81fc9202526dc50e86e2c1d3128301ab77 GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0y~yU@!+^4rT@hhWV^YB@7G<8v}eoT)720%%l1}bvr%1 zCi_%RjhQk##kMOYuq7pRUgnC0+0Bb8lA0>Lx+*J|*PUG5nP1;oJGm>QyQ^!%geP04 zpWi(rwr9r7ol8@Dmo7W7Y)<|1?T6RAKCv;UcjLwryJwW`Del{|=lrqC{$t0k9p7Db z;?~U*pKhJ6pK$*Cog1waZrpfue_PIj)5{-rO?>$9#iJ{wkAJ*<*+1#!%Xc59O#blU z%a56pfBg9M_k6;?=Ntado$~+xe+CAIv}tL}7#J9=OM?7@|C0?E79O#dV_;z5Ebxdd zW?X?_wfUqO7#QArx;Tb-biTbD%iUxk!j`a+W7h-^#a|5{^meV-p!4nDfBR{9 zw`^|v`E?)ou-D2+{(C7;(?V6|2_c&Ezw^&r6y$2yvvsM$=hobfOLw3(GSRNf29yD#UTJo=>Pc!}EMj@QP%w@)EF2T7(8A5T-G@yGywpmUqJr= literal 0 HcmV?d00001 diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..82d813464caf62b1bda0b4bc3f8176dfba6cf660 GIT binary patch literal 4252 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4rT@hhO2JvTnr4%3;{kNt_&bx%)pSwz%Y}6 z;S2-Ae+FX)24iCe<1_~2nGD8f7>xgeB-4x;($W~xW-_FmVMzNAQajU_VP+b`%$W=` z&oIpV57K(ZnBhzs!pJDj_A7n7d=rm)7nZ^uf zj2Zre>@hYrHcm4(o@s1+#@P5j$l5exW5CjQ`Ix{(r{!|Nk_QmyFXuj-HtYa@K#4 z4?s4hrGd;klVI{?Gi+F!R6h%>QXK z|IeKH|IEz)|IdIT$@mN?8fKmW1@wPVsDr{T4Wtzm9RER)kY;=aOh08P5DSKJ!2A%>S8Z{+~JX|34@K!O;(L1vo}Q z@d=J3kfXri4vJoIP=eeD4joWzgFOs#GT0BG$UkEY3aT?RK?Z}3{{J5oOaK3a{Ph3- ze?!x0x9_~%wfj1sa7S9knwPIWMntcamF(~{?P>_>>5uNM_V01j>k=31P*R%^9<}_? z;oGOrJmeSbh_&m!-ZJse?8*PbU#{ds)zkX zK9}zIZzt|Az8iX%K_h;LcejcMlkw%)t5@$bn=2j{DDE&k`omwwd}Hs<-i_TG&;CDB zIJrja&)>br9q(Of(px5!HDPhUq?%v5`xeVQXR|xp^Ju3Ccc1r((0`>HpM~{1=~X$c ziCr737rS!qx`6%1H`dw5YQLT?`ub&6#Jg$c6_`1 z_*>!c=Vhz&#Url&E9_n`w|)2ByuET=>q2xoIuEzqNqAczan0lnpL(s0ea^AhXAf7X zzW?s5x8d{Uy1F$-9_a1!n}5IH=IK0v;45a*p@t_I^(JiJW%o0=yw?6+fA}XQc6nx9 znQyy|EW+N`0So%*-?;nAj#uUDAmq;$RRXV6-f823T`5$D7EIhP76ziQYCo;lKRiRDg^A65UK0Y+@^$Q%-Ql zis@EdeQ$9vQEp*+iRpyCI>Rk$uqM%nf`R!hNP|acYn*oUb}F|e}nwjRZkxW zG$+JLyZ(}i-_>z%VkFDU^A8SJ%3KMvJ@9kefd>ZbC-|+X@)h>>xu1UBS9pSJ)8XrZ zIukC4`{pd_)i*^49UMbyT~=@@<;LzW{uVH`7=@<1Nx&H2s zFNZE02rlbBEX>~Id~a36hucYi+ZlHWm8%WTK5*ILfW`r@;M<58A`F89^brt1C^ zFC2WY>LbA-B3M;XJ}Xgf*6qLlp8tCu7XN*&>l#Dm!i0@%a<#J*?s==Mu76^#z48fj z!36F$*EPInXR@fLm@BmIVqge!(wWb;Nu=u5;r1U3RSz5rKB}}mUaT=-cD*w1qzAGc z$4w8jO_6_>Z5_g{T>I;>C;D5064%XFUgr9xW*Q}pDBh6{;v-Nm+*Z9LNW z=bE?Ju6@^4tnP&u^BB1{eVVmrTI;fXsl|e|!n7(zm)r?i{1Q28GF`tUsrO z>$Scp`4y+meZ}AXpu_Y#Y1vK@Tb7$Cskh&#R$Q#W{>5!!M2Mf^wi=HOb20>DlS#p zQ@?1Mh^p}%{!sWs=X`(p>%vdxx@}j-l=U?p3zanZGvB_hCR%hk+wxbrU%86;pXKM} zocj3l%jw6}Mci>R#j?*2wOtqIeIYTkl6B@h71k4>57*BYR9)j!Sn}M5eRr2#q@l&F zV9{NF(+(f@_MGXVkUA@_-QBZh^UI5eY+j^volfghV^G-oLDl4kEZe0mw{FaFS+25l zit>?$x>YZfb=N$#F2AUAWmeJW^@Rn7(hBd_)Ey99`jRcsCQ*Pn=G!@@bH&{c&+A%* z<(58sdODe74MP`~qvY5F0+BNx9l1iWa zKhSi172|{miIpJ@i&}PSbzGE+{dsEcez`?kf3D$ZIM{u>vC3*?(1Esm?XV!-VEe0I zs!lF4`TZvHdY$=|j4cU|vhHu@IJbKL>e>75ZnC*<(<)`y#kwsfS@5}gl&j{6NjrP5 z{b67^-k`&^tY>RhPT_%_4faTPTK6f!FO@X z|FA1F?zQ;N44%0`@$!mrt-eIZS=)E)(v+9UVD#3DmXVg#R!ohxozhXPz-N>`|4oi^ zx5QV?@*Ds5vz|#`$fmE`aWI8(i$=<|pRa<~FOm2%D`d?NbKRoewQ);C!~Qa+mTuX# zYH>`p%mKAB;cLrN&35}d^HF+h&Ao&1q>Q(Isl>t!O)uRqa~?hLDJ8{bpnwRa2eJd6^&czK(p|5z+HuRoLhEGg*&2o5pVVq`IMf>(T(< zQm&M_r{13GIpoiMC27*7P4hO1>6y+y-hTJ+51Ui>oqI3;xf>G1Y$S3%v7+)?eZ|T( zi4P_8ZH%}>g6=-qBB@gH&Rlx6xp8^!=I;kCh{R42JD(nUo@*jcl*Ydt?IhFfk)BI` zyUq_idt5rlCiL_2Jdw23S&0o(w=*Ap>ab^-hyOO)>1%JQTWyQIv*K9(j45U}JagW* zq_i}78d%TuwpN}y*Y20h41t?J*^f=WvW(g6nT~g1vYYMXz`4hnn0q4Ab-xKLuV%Gr zJi;&b)vC`=P3%tpx(U)j{nOH3OG$p4C2-Dg#WhaJEZs=)9hw0-%+YlM+$NuTZ&x{` z)|{(;`Rq|i-oaDfnOB@S`F&Nd_qk=QtdEvtzBQTJ*uPDFPpsdEMb%w$K8&~8^3(+< zU)yFAq}Uzs|E`Br%fzguU$2Lx9Fr~&pX=8A)#09&diny@(;1U4e~CMLYIWpY$$%YU zbF{rvqs+MbjyMFfy!7`I58DvdZTxMi-mi{7CC*|iSKh4kXAmsB*1GrbfvO!gspXR*0u|2~hdq)2Exf-I=(v2C`rK~93 z*t%0(xUi`?vO!qukob<9llJJRNNF}5Y_>R|o8a2>i$O)(=JtuIXN&lzsP{hFQhO@< zVN%{VO}2X$c||ErpQPBOUGMxpbiDkm7vGs9EKcDEtY_ZgbZXYpc@6_M>X9 z+kR-ROTBv3@9LKAY_m*R3l^*RhDv;|oOSDZ-2X4E6W>SgEc<=n*~2BxHdTLkr%E@) z1ytPn{8l?AszCqp_WuU&Vt79;YiTXr!u!C&eoc$7p36L*RqwYgGMiY#aQp2v{dZ;& z9XD>gP(HxDWKPOXlb;{NPQ-1v%~!mTZ=v3%UBapjULI#{E&^ zA$tNigP$;Vv`HRYbW*`LuW@6Cd+^E-TdPg^hnMVBd9qK*Xk}RY9^sDA&8zxFME#{p zCcU+dTzf*(XR)T{p2_oX6#w{Xv)t3J=+is39osgh_3Y^u;Cf#dW;XYR*!rB)YuXPi z&!~<5y7#}@uIwi76H9t;97}b5=U{YJ_mE0qo?7^|ZETg^U!(=@ggw;w$ySi9t+Vfa zRD}O0HJ*3c8yMIRpA~3$(7lwE!EoC>@uuw|j7yr{WF4DyZTGUIEq>d+-Z6Z0YK7wj2W^inh3Zc9AVh|1%n_T3F*fdCx4+D1)b~pUXO@ GgeCx@CJ9pj literal 0 HcmV?d00001 diff --git a/android/res/drawable-xhdpi/logo.png b/android/res/drawable-xhdpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb8f63c56cdde86e5c3e789e7b3b23d831cc461 GIT binary patch literal 866 zcmeAS@N?(olHy`uVBq!ia0y~yV8{bu4rT@hhL5e+PBJhsYz*)Tape~1Fpui@)a~^2 zn(R|OHD=1}6x*(pz?PKMd6_E~W;ZXYNNTF|>Z+_rsUApYRvN`q3w;x{f`ozYZ-i;ei?4D7!r?_v=p7X~l`;Q&Fc6@i$ ziCZ^Me7beMe!}_lcW$&!xN+mr{cSl9PA`AhHSyuY7mu!#KK}9cW&fm?FW-HbGWo-Y zFF$5Z{_*42-}4Foo^SX+cgp|&{}~t<(x#;?V_;ygE(!7r{!cbwSa`%*j)8%Jv%n*= zn1O-s2naJy)#j6CU|Ak_CV8=eyakT;`yF(W5JJH@|pX*!f z1T7^hG6XGS;(bdxPp-V4@AtLil`*oLJ-f2|#=gY8lQ;F9Wqfw(L*?`Hq2^C7l+9cI zbVYl6NlfDwE7kOh55@)a-zs)zTRvL4*sOT(w<+cRepx>+tt>87`FZ+_rsUApYRvN`q3w;x{f`ozYZ-i;ei?4D7!r?_v=p7X~l`;Q&Fc6@i$ ziCZ^Me7beMe!}_lcW$&!xN+mr{cSl9PA`AhHSyuY7mu!#KK}9cW&fm?FW-HbGWo-Y zFF$5Z{_*42-}4Foo^SX+cgp|&{}~t<(x#;?V_;ygE(!7r{!cbwSa`%*j)8%Jv%n*= zn1O-s2naJy)#j6CU|Ak_CV8=eyakT;`yF(W5JJH@|pX*!f z1T7^hG6XGS;(bdxPp-V4@AtLil`*oLJ-f2|#=gY8lQ;F9Wqfw(L*?`Hq2^C7l+9cI zbVYl6NlfDwE7kOh55@)a-zs)zTRvL4*sOT(w<+cRepx>+tt>87`F4A0#j?Ob| z85nrwJzX3_D(1YsTOBYpRgV3````0^Ti(fE)YQl^K!o4!5MNL@GG{rH+|YtEjG`KXt&b845=V^&x7^(TBCo@yz~x&3?JZ}v9Hj`q(s z&kKInru}FxzW2HMp5(qg)1Oz~d(NRY|GTKbBbAM7%Nt5^L=+CSFuBYKXy6d$5=eAn zRPwo!yO()C7te*H8!F0q++})SS9#@}xR+d5Q9ak~VEp_o2Tnh!S>LvXp}IfoqxZg5 zN8e8KSzW%XSL>jB{9BX7-?qIe;hlF!NTJ@|w9Q_q<-!F9p1!ZHIXMPRO*V-}FCRWl z&AO9U9jV5p`!wd3&WUwS-{v{BZ#RD-a;Ey9RTayuxp~3U8u!dr{Gob(@zJ-_mOZh) z+jb#aCTwkQ_Mg40R*8vp`4?-?ezmEl#^%A}$Bp;)HXHt#_~O|0{CjJhqJC+viI~~CNXX9p>$3v>fcYA> zYBr+Hp2Eo&*2i;{SntJqbs`+rKN5yB}7mZDR;z`l3SZn@0b$$1?fP2Eh1&5-! ze!+Pn8Lj5$ldBNp z7VlgBVN0^2BBRG`W7*xo^N;N?yu0H{iWO69yX41@w>Eb4zdxRu-tjuqBf>y)I->UM7EhTm1#L{4{evdI@{jcOB%-eGf z9hBv`54~RB+oEx4Zdmx@iz`0f+uhmmCO4yS_iwfp`8}Nf?;44!1)pa1crGd?*784R z){RGI-+3=w+U|DXcDLJw-A0ZA1`ienoS9$!|MmSe<}WUr7jt@KHIaUrD8d^N<<1fr9VDe zb67*?)vkwytgeik&V?jj*&#dQ&vX5W6AR_atmiBLTwh|vJ5T2RmlKu#LC^Nxk`A0` zyTf#q23J=0Ys2Jg<})@wFwl$Jb#$#+=Dl)uFN3;6d23?7GplsoKT_=|V37PPU`2iT zxh$Ru+qZLFp7;26V#OmDIW?a@OfP?X@3^MQzwM9was%yDX)o`ee=ke*TK#!_c;fdx z*CZCq$gp>D`B9wd5h(KH`TZknrpQ{031`e$RI=cpD`-y~@+G{QWT(h8f1zTvl_1KTFmx-?Bw2U(fPbO#6rM zprnKBtLqz7Hr~JI?poS@@PdJtBGZ8bjeBQwGdUcaXd@zewP2;|kK22VWCBAulZeov<eo!7AKD zUW~gAt9AT1nauDfdgF!z?@w-O67rR8_*QKzA-K%BV1s<|+)bOb7(^DhO;S(_zb-3U zwfC}VN~Gh~RSXWl=bke9I#VKJ>zluIN00I{F3Mh@cVvP1#0dgNmtMQN+MFSvVb_!s z9{D_ycP8)bQ}K-0{7s0#fp@Cl2RWV$$z4lyxfZNqzhF1*zWUkU&+p6U$}}Z>yxp0Z z<#?Yr=|{1E(RyE#JfE=6tG}&8o3b+@OAqm~-{Y#0y*jD;cITM7(YA4VnEXU5-G0QWhMUeX$uS-k}kCSul!t0yu?_ZzO)Z|rDudx2)>pcDde*T1)6})+n+$Xc5(?yZeuRbuRS_WTlgXIm%1W#3)b$1#5R z{=H%Sdhsh+*W*sjIvb`FJCCn>X{P(eXl9c&>KopQD*y3mIy`yf<3(3Ic*B)bi*3v1 zyp^%N`&VdgJ)izPbE&^QiZb7y9sKsrx_R%*qi6R$-?n?hJh=nz!CyE&2t=Dsju&8g zVagQde@%I#x$du*c~hru-&k~Y-4~mw%U*g@Mc68NG zn+CRoR{~rUR%>=OZB8%VSn%;7d*175482_sulrOqWM5maRxWE>#d_Xm_uYlLcZC+@ z&HFR!;tJM1wry?+F^X6Q*n#Z^+O!2YF?^i0ZvyLVmy&i4!@!+eRC*s$Pt`>PWY@4_3ac8?}%)a_J51xMe zeI>m*-7LCL$5=l5dRoGNMmv6o9jXp5pQpxXY+M}AzfE0fZ5gY){4$2E(^Nby>K+-1 z=~y*sTTWfE{`vN4lcSkRZgcJVv6Iz0PE_#G^E)fooLiJ|b6r)+{TDWutzujQMXp?p z42X&8Y;Nb?n0;OHPsNHus<}Jgn*FO`-C%IFXMLK~!qDyiLbkn|z&bzm($Qk?HDS{0 zVl>^NZbjB?+$mscnl@$X=iiH_KHITXUgK?3Q|AA5hcBh*wzXK7S!u7Dvf&xK_BHvp zHr_Ah-U@yBC4KF()fP*0f7ImW+Gb^+U9o13V_ux)w>R32Z=U@(++MCySMBNOwvJVw zfBVC)hE^rt^Zrc<%bvLL?GDz{msLY%-rX}Gx~+`aeJ3J{ zm&o~l6%`OuICT5Mh2@!BwNGeIF0^nz*1K!l4eLu%xpq@Fes5xO*!9`jN=oXQnvaG< z3ny!b!9oT>HOw}&hC@r|uW2Fe&fCv9FD|VMIh?g|vD>Q~%JZZgk8qs#V5zeH?`yV6 z=kbJPTyKA_=Bo(4KW)v4eRKJ@p8aWg+N`_9>gLwZelfnP9Tg`;n&jmE`CXroD!K1f zPG9xae-|}-aNCO4Nn}c1F-;9i$=k~}_0~@v&oaraPuKoGcK_Rvq=_HCP80s{(as=1VII}*soUx4 zHQA?nYRr_`DYjiHfh{Si^DA_8&WT?fCAh z6Sr=j_;l-h{e<)9@7!peaO1|K``dCJoL>I0YvRL)FCJYfef;C?%l=6(U%vY=W%7p) zUw+J-{Nu;3zvmPFJ>T$u?v(%k|1&T!q)kg(#=yW}T@vIM{GV*Vu<(er90LObXMsm# zF#`kN5fEmas?8_Oz`)$=>EaktG3V`_i+Q&UMA#nmtP#EHWKpRTe)ep|A&-xUof0T#zOXr-;19rGJjwBi2F|} zS%0c#aZz#ACwbfNhb}JPbi>3~ZQ7BCIcxh~di{avw7>H8D&TCLZsi;sjX zNl%sP_L6%Uy7C)S@iA#z?WsxD%R9cTJ+tGM$i#h9eHI^o5^-<-q&N9}cr6=U3c&X;D-X#7s^14b`)0Heh7VW&~ z6KQq*&wPKa=O0CPW+a+~ddce5TX$xEn4^Dv?IxLIvz~`*&(1X7^y}#gW7Yffjz9LP z^17JqW;q$4^znA1-5O*t=H>{7Fj(`%;N7l)Kx z?K$gJ)L(bREtOW1)i_zr_N#dE$%_+Mg;?w8+{!wy=g6~C7|MPE3%zhs4 zld+EzHFtizv~<#q@Ey6~OszipPL<_?r(83pOuQ;Ne{edhQ~Ebno{LEE%H}T*miZ*r(HpIpQgSHi`0z_3OcdqMrGhU zyS%LRIyD;S=XFYI-1I!L@Y9=va~mh^t(&8{b$Pds=1VII}*soUx4 zHQA?nYRr_`DYjiHfh{Si^DA_8&WT?fCAh z6Sr=j_;l-h{e<)9@7!peaO1|K``dCJoL>I0YvRL)FCJYfef;C?%l=6(U%vY=W%7p) zUw+J-{Nu;3zvmPFJ>T$u?v(%k|1&T!q)kg(#=yW}T@vIM{GV*Vu<(er90LObXMsm# zF#`kN5fEmas?8_Oz`)$=>EaktG3V`_i+Q&UMA#nmtP#EHWKpRTe)ep|A&-xUof0T#zOXr-;19rGJjwBi2F|} zS%0c#aZz#ACwbfNhb}JPbi>3~ZQ7BCIcxh~di{avw7>H8D&TCLZsi;sjX zNl%sP_L6%Uy7C)S@iA#z?WsxD%R9cTJ+tGM$i#h9eHI^o5^-<-q&N9}cr6=U3c&X;D-X#7s^14b`)0Heh7VW&~ z6KQq*&wPKa=O0CPW+a+~ddce5TX$xEn4^Dv?IxLIvz~`*&(1X7^y}#gW7Yffjz9LP z^17JqW;q$4^znA1-5O*t=H>{7Fj(`%;N7l)Kx z?K$gJ)L(bREtOW1)i_zr_N#dE$%_+Mg;?w8+{!wy=g6~C7|MPE3%zhs4 zld+EzHFtizv~<#q@Ey6~OszipPL<_?r(83pOuQ;Ne{edhQ~Ebno{LEE%H}T*miZ*r(HpIpQgSHi`0z_3OcdqMrGhU zyS%LRIyD;S=XFYI-1I!L@Y9=va~mh^t(&8{b$Pd3_*8t*cliYI14-?iy0XBj({-ZRBb+K z1_t4;o-U3d6?5L+t*jA!y7&0U^Ji|JKUpL+X+lejX^Vq_j-y!fmWV*XTE&dL>$lw~ zYQ6n!Tbce^@0HhHt=;>mEW|@XJ-C( zXIYY?cxKKVgZ|9A8Wh%NnA%G~n8TZkbs6BqurFV7*L zSN4(Glg^}X!FxP3++Kf_xES#Mw8Xr%6DOtAcqbdK$PQeY`RZk%PkK#bT8?y1UfPy{pL_b{zT>a$cKQY`?7zCP*2UmE|NW4wM_(@V^@|Qe z!^5`$MVDsS6@1FHfBlAM;cKf#2lcsg6SH3Ls@T71=B{00eDZlu%IjA+Toy`Z@te=K zV8@BmzQt{ZxlxR+i#K_Al|3r6EVO#NVpYt%FX3|i+zjvA)88y~URaVO_pqR4-8znL z@zSCX4-Cz6{xt00Q`NHg_!--dx&C*UACzg|v9a!Eoj=nf^CicmNq1JPQnLP&dM(dG6c{&95_s z8dfh`Vwis~;@KHF&%8XVvfXEUvW}#Bg^F(4eEG-s*2y!?Oy^IJyLn!JzMi^($-d~6 z3I?%zhCN5Smj#$;v2VYfS@Q9a(;Po`Rn=xmn+k^Tp0f|npU;1Gb-$@ZRC^+$hQzZc ztK(;$(Jl7=@OyGV=+PC6T95wxYnQh#lV`G5v4mZY?CHA70~PCaY^L3_o*TIM(}tT*9H({g z_a8rcXXj(aA4h|4-`u%dbnSHKLsfgPpPM=NIJ0-ULE4#wn?GIe?7Zvu?Ck6V&h|ef zg4Ow7lv?h+Rh&O}{`uzb=Tok%*b^77b7X0emF$$MqMN?lGiu{6jrqZyUU-plMs2Ma ze_g$pvfGm{tDF>jQa0b5VU()U|1U#~^{}2Di@0b*zpsDt%f0gxK0N5@UgyEF*+Qml z-Oe-<{yX(=UF)|WXOu8Ia`5{8D7V7Gd3Rn4avp!VrTA~@;))*!GsVB|m18~dc;m;L z)?PF1_B!ZQxE}gc5p$>Rc7)iyz=q2gH#dJ_{hl`6`{}o6_YXH&Rn_B51e5}KWnMf# z*D5^2U|)K9+c{~5&{j|3KX-N($S|`nlf2}hcxK5et)oYOo|$hx>&q8z`3>i#8TyZ( zdv0evZPKKJZKeSnn|H?W*?r9qH>iL2fV-^qoODBWdRoEdIfb9TO?y#(>GI^rn&&Q) zCY`fa`njk2+xK^KJ-xi{?>`~b{qo8+v%uiXKB1w;rlxCM78{0bms49b`}3kBoVi~E znRsizU)r{?q@wDmLh@-J+pbBU%J0ar@3dSgf9}=9#_O-&ecs*b>iYA}{t}z`-_Mp6 z=M|r0xU+f5go}$G-~W2CR5tcHXX~PnkdV!FhpTnsH}>~NTRpxv?R0P7p-JkKdG)rk zR($W7uNODt_?iCxlv7unjM!9?H?J(@Z@HWIc=C3?XS2P{<`%EH5Xn4Y=EFzltkY-R z+-Ce@m!FB$;%(b{b))$|U-I4^@%4OE)7xiq4_htkKEE*pVI$*f8$a|hrQJ7}wrJ5C z{&dF9`)j{U5z*JTt5+`0uYS8`{(Mu0`+vW&7#a821BvpU&bfT`EhgV?#QC=_kUiVZ+7`k&YzHp&nHcJ z^5}KEuI=me^f`>r?pVx8Jj`}v>3%Uj_lWK982bO$Xy>n6XS&z!;&-$B%w?O3n5DOy z6nvkwl1J9+!WXO1(8OC)5`E@wn%z5F%K!hH#s4MjuAJ>z-9Bl`lVdrtv58@8ZXB!q zzCQJ9)Xkr3m#m9>eEu|l!S~wyP37}{rq{Rzo;d0G@K*M@In!TlVVDv#@#Km_K9}`_ zoECDd-?ORNUH-Pq&v|AhJI`&MlM(X#Yp8qW1NPOH6(4rI{0b`EbYxxP-o2bVZ*ujn zMF%3umeLnY!1qwnR)+)z>bUmvadWV0R23)2Hc zS|=)B*3`BAYddW+qlU!0)L)^@zH?8zUoYj8UG==3fB(sKpMLnaXbD~ZG`0Q5<6>(L z7As%ggAI%)dnD#H{#90QoXmLQ{P*C98JST}FWi0q|EKWl$ld#nUa4~V|M`u4`KGeB z)yu9u{9L)C=#s;yBLCRO^LQHUbx(c~oS%K|&C{9pXaDx@`nU2`#gyZ6_gzX$C$Ie4 zqa$%(!Jgo|HKGb<-zVI9;uE~vJ~Wiq=EGs$WmENk?bnTtu6Soz{C(n@H6fS&EDyU9 z$av}UU7vkMx36@~udn0Y{r$-MhwU%s&#TC?Z|4i09afjMs^8G{WX0aQ=XZuMY`^VT zw(b5Fuh*}mE56lh`D(|<*v*<{I`8jax67xt-wZj{e_G+}{lA-fxbA*ly=1xi%&?c& za;I+D7I(;M@u8r3@sYmC$*VJ;K7YRa;n%YA@1d?Q<^I0>IOFpdbD6$RSJr>Kd_#j# z!1nS@$+X|!ye^;m{(G-_{HLH;zo|E`r_KzEEG<=TZ0LC^ZY$2y_Vc*3dxzWENMLB?#<}FwO7i$OH-6FBz(I7N7>AzQyiOSFYb-5gVI0Yxdd5*n3Zl=E=NUzo_K=`V2PT%PXwzIc~T+CwS(Ln$j)p zD^?r--?nq*VLNLpr;?HrN7wJDc(}ps!jW~KW#^wd{x)oPd`DpCg6w1A%d!=}%#iu( z`l!$ z%6YC%L7}X>@9w!h1mOtdJ&J3@VzPN(LK}BQzaZk6lO8yN0 z36c4a7u`lMCjqVfG-ZZlV|7BV$u z^{YOApzoE#$(@p_w}0#w@mj!K^PlCLblkQbe`*Rp2CSQDdF_hTs_dwY94k}1v+FL{ z<}Z@Gys&(_=IT@3+_P;oXSUAY%@t~zUw?6n#O|`I!I3x1KBX7&D*U>0K*v1(&z}%3 z?#;fj(-$pOJ@HL;tBKiMsYUwyhg{~LI{UHU`gE@yd0~ov%XgkulU<+p^g!dT%XMn$ z3>VWk8O7fDx>QK4X4jQ1iTvsUw4}qA&L=7C-)qW+=I<$Gs@q?= z*v>yA`*p(QXI@MPFNY^jWa!wTE&D$0W$pE#Ff*b4<9j$~`d=uW+trm8w#KIBSh^5{ zV0e6t&6MhQ^UgLfKI1R7Qxxj_bS(Pmmhw42>#Fv1Hmq{!;=aSTVP!(Ns0gc5;M^H} zZxuP(e(#$4DE?;7glW%`E*&x1_oX%H)G4>4rM(Fa&Qp2+T<<@9{#WE7_5<&m5+%8s zFNdtt57CraG3i;b$5)LTAuF=_1YcabSz>YQwy5E%S;--0&Msp2UI#mWy2~Ro8O{ zvx%pxyZFRe-{Y^}?w(#&HeLSN_46_|&!2NjGVJGmCUQHXW@>ZcPpxxudt}zG;;Nh9 zn|D98o~_*Oxq?B(I`LwPboaWl2~UFbUvey3 z#l;$F_HOsV)@AJxJH4F}RN48%KAze9J2St3Uit2pN})9j#qO*BJUFtUA;a$oD7Ck~OAp*6ij#k4~2E`51Q9wN_v89n)DE+4*Tt zeuTzwew5}`-nxZl=l{m*6%YTPlX+97$XUpC!n5~$;LXI~A2BPtIw$jWTV&_*7QL_4 z|9oe@7U#D3KhEb{EacMEX5aq%bA`(Cl14rDUF$C2ka%#gJ?{B={SZOR|8M^ldv!1z z2@dSz4So4E-dlymx4ZC9h4JaSjnkfL_Y3fSuiMf6l<9)prHL0E|Aw2a+L-Qdy8q8r zw|~E0DK;JSu0AwhfRV%X@pkX9?Z+;tGX4!x5lVf#>w87b&9ZxYPVBaQK1qNvgX4(C ziJtSN2mZ~HFywUDfBB9~!qKj^f4+VHeRK2m9fj{W1sl4odRHDkTkoxLK{VNSsUG7C z_eFudd#n44?*Dzip*Y>t_v|sf-LnN4BeuFfHV!_!{`p?1-d_ve{Qc|d?LA?t_V!J0 zPTmcXd1bKl^(+C#io*5h%KKa;W~bkabm)G$#(Kp?rO#i~wnX?WU#xs&TW*|q5cj0_cNwJq&=8y}lKwA;Jxwaki(6_xAVqECEy)XJzdQKQj8-Makh$5+|b zm9MTpuj{?&(J9=ZYq+exqu_G|J705q&bE?QI`^d#^S74N3w5r0T~gL&S+{4VZ}h5) z?{^p5{GH8QxYv8S0b|Fp2c4#ZpDv54@oh{t&Y1S#+=g@aPbu%&;H@lSxWuEdb5UUL zt5<5LzF+(J?Uk06R@TiOEBmHh`(eMpX9feqn-$k8-^@?kXUwg}vn~Ep$jvC9=X1L> z`u7Pg-E?z-TUWPO_tA{}_e+*7YyI>|EAHRxLMiEH8QWW@e#^7y$24>bX)z@7{woYn zU)CO3r0XM*w13IF3*EO5*Zlv=;U2BGXRU#o!3{@)8=l$O?7qH^TH4Y#_H1qBojr@A zRcXuC(!l8H7UD^qq6{zi4zBuM(r4PfBfITbZuqW(oBDbzrfa3nui^c8+0@YK*gdm% z*GpvO=4?8C!6^5XUCybM54TpWsb0~!?)8x*!z4pl-^&+;zWi5w$iyIUIk8-}=3%Rb z{@adC8M`J=sU|U*ZBo`S4ErLyCMEiJwN-(*S9lG6I*Zf&XZkS ze}3D<35-8hT)*Fb{XYNXcbuPJc4{3q3i%T!`n+9Peaj5L)Z`x9P{V6A3xaPK{`=JO zV?_)90Z!k!AC6cX{`xKGt|C@x?iDeM#esAjdpUW^_V7Sq9*MY$>J8n?GIl?F`pum6 z9b?aDUc)_iHt2e_D)W4)@l+_Zs-7Zon`!Ejs-Ae8)!GsZ?Q-te^!*LpXg+(TrZTVo z_S2GSvv;5TP_*_4pTMQXEhUei^KiZtG@02Wvpnng+F2Vo7I2lR)^&Hs^gWRx!=dDO zjuJhw-LFD&@{T<(mx!y_5xjV-SxUvuzh}4QPR)oq^P%YQLk&?;)3)i^s-S_ed)Ipf n7OnRX_aHs|$#CMW|I7z&vPjKT48G04z`)??>gTe~DWM4f!@v(- literal 0 HcmV?d00001 diff --git a/android/res/drawable-xxxhdpi/logo.png b/android/res/drawable-xxxhdpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..753b4a63ebd3e5c2b512f7dee31f1d918c98cae6 GIT binary patch literal 1728 zcmeAS@N?(olHy`uVBq!ia0y~yV7LRq9Lx+13_30Ln;94w)&=;4xN-}0m`C+{>UMg1 zP4=mt8Z%{fifva)U`tBsyv!8~vzr%HBsEofbyZd_uRFQAGrzvGc5+upcURYj2~W08 zKfilMY|o6DJC~;RE?stD*_`_2+YhgKePUxy@5YTMcF!o=Q{1;_&-r7O{l|`7JHET> z#I2hrKHWNBKjHlOJ2zS<+_>@R{Ci@A-s(&o}&^JLUiX|5G0v+Rnhhz**oCSxIW=q!k@f7?fdFQXIP(>>8SO+%B(NSE34e8+u6)z z)w4P4pxp`4qg6>Or(K!)s&mfVxc&du{#4$6HCsC7Sa$Hle0zmCWUzcJ*wXyt7g>e9g>Rk|*!{nUZ{Ku7ck$o$xgr+wBZz@i_Ip zdVk_!ia=3o;Toat?hoPTM2y^v%ob|i+V9&JG^BGN7Oo=-!YARZM<4N86rXFA9S+o46pPG5;WuQrCaHe`p=Zr-QJ*V|Vt2r+%GFj|Zv}4-WpS4XHoA%|; ze^#oz{qy4@7VAf!Z2weP?#|DB7L+F~Blag<_x{0s?^jJS%}aqah8@7_6GSa$rpPut`* zzD9BJeU-}kimmIWUVH27*~cfM|8>$Iz9jKHt)ed`&vwQI9rn`FE=;uC#a!Jt{fcSd zHn$ZgVgsEwwjWl#BCZghu;Ajv#Z5(hn4B)ePtRa1J}R1 zu%Kf>@=nP|9g}3PI?JaSw(R9zZyQzQqQ9+Q!^JKC;8a~V@4gk&f2+B&c(4AM$*F6( z$oK7G)sJa+-~JDtti0&qyYnvo51d{%%#!*1@@)ON9h07}IN5vql;!KEB2POyHfD#c zk9v37TsVGC#oL`z?xe4MK96aq*`6#*wWv1T<6nvsPa7&v@q1GD_xO?dWvs@@r~RI+ z>pi_8f9^C8O!%Odwv!C^)rmQ{CuXokzJn3Lmc<70X3ZRJZ{56h~p|7ZB| z1@zf6e&5urVY8F1dcV_T;WxLe^ZsUOneUi=cKYd?U%n|7E&4m{xanoLqP;JZ|D8#* zdl8W*Vt!ZiwoBfwlip{l`=0XbURK|;=!fH;<3CeQ&x={N{ml8BH3iz~J6_~3411c~ z{WQ-^bh~c(y_0kE^e3L;&AQuCCVT4W@~50ntNOp@RqAohzI$@2`D)ImcC+q$THmzd z(#$(I-v!LPbKu3a=f?d{&TV5o`Q%y4owoYY*ps$%Y{ONqE8qE4^Qt>0HGG1{yS^#a zUtHGhx*DVMy>rUxzY|v9bkmJ(sKXvWx7Y6*RI$t!s_Q|cRg8A z_&K#bp8L~_=9~vU*Szflm#A(}D$_dsB#(2u9pPH0R zZ}pqZO7jJjrRC!!%(tD^&2qs;Bq<=RMlHyZz6%^_~0g*e+?? z9$T|=?jHshUW6*FTFDa3U+250@cRS}vxjf$)0jY|EE0ZT|DXBzy2nx6++w02VNX{- Jmvv4FO#q&xWX=Ep literal 0 HcmV?d00001 diff --git a/android/res/drawable-xxxhdpi/logo_port.png b/android/res/drawable-xxxhdpi/logo_port.png new file mode 100644 index 0000000000000000000000000000000000000000..753b4a63ebd3e5c2b512f7dee31f1d918c98cae6 GIT binary patch literal 1728 zcmeAS@N?(olHy`uVBq!ia0y~yV7LRq9Lx+13_30Ln;94w)&=;4xN-}0m`C+{>UMg1 zP4=mt8Z%{fifva)U`tBsyv!8~vzr%HBsEofbyZd_uRFQAGrzvGc5+upcURYj2~W08 zKfilMY|o6DJC~;RE?stD*_`_2+YhgKePUxy@5YTMcF!o=Q{1;_&-r7O{l|`7JHET> z#I2hrKHWNBKjHlOJ2zS<+_>@R{Ci@A-s(&o}&^JLUiX|5G0v+Rnhhz**oCSxIW=q!k@f7?fdFQXIP(>>8SO+%B(NSE34e8+u6)z z)w4P4pxp`4qg6>Or(K!)s&mfVxc&du{#4$6HCsC7Sa$Hle0zmCWUzcJ*wXyt7g>e9g>Rk|*!{nUZ{Ku7ck$o$xgr+wBZz@i_Ip zdVk_!ia=3o;Toat?hoPTM2y^v%ob|i+V9&JG^BGN7Oo=-!YARZM<4N86rXFA9S+o46pPG5;WuQrCaHe`p=Zr-QJ*V|Vt2r+%GFj|Zv}4-WpS4XHoA%|; ze^#oz{qy4@7VAf!Z2weP?#|DB7L+F~Blag<_x{0s?^jJS%}aqah8@7_6GSa$rpPut`* zzD9BJeU-}kimmIWUVH27*~cfM|8>$Iz9jKHt)ed`&vwQI9rn`FE=;uC#a!Jt{fcSd zHn$ZgVgsEwwjWl#BCZghu;Ajv#Z5(hn4B)ePtRa1J}R1 zu%Kf>@=nP|9g}3PI?JaSw(R9zZyQzQqQ9+Q!^JKC;8a~V@4gk&f2+B&c(4AM$*F6( z$oK7G)sJa+-~JDtti0&qyYnvo51d{%%#!*1@@)ON9h07}IN5vql;!KEB2POyHfD#c zk9v37TsVGC#oL`z?xe4MK96aq*`6#*wWv1T<6nvsPa7&v@q1GD_xO?dWvs@@r~RI+ z>pi_8f9^C8O!%Odwv!C^)rmQ{CuXokzJn3Lmc<70X3ZRJZ{56h~p|7ZB| z1@zf6e&5urVY8F1dcV_T;WxLe^ZsUOneUi=cKYd?U%n|7E&4m{xanoLqP;JZ|D8#* zdl8W*Vt!ZiwoBfwlip{l`=0XbURK|;=!fH;<3CeQ&x={N{ml8BH3iz~J6_~3411c~ z{WQ-^bh~c(y_0kE^e3L;&AQuCCVT4W@~50ntNOp@RqAohzI$@2`D)ImcC+q$THmzd z(#$(I-v!LPbKu3a=f?d{&TV5o`Q%y4owoYY*ps$%Y{ONqE8qE4^Qt>0HGG1{yS^#a zUtHGhx*DVMy>rUxzY|v9bkmJ(sKXvWx7Y6*RI$t!s_Q|cRg8A z_&K#bp8L~_=9~vU*Szflm#A(}D$_dsB#(2u9pPH0R zZ}pqZO7jJjrRC!!%(tD^&2qs;Bq<=RMlHyZz6%^_~0g*e+?? z9$T|=?jHshUW6*FTFDa3U+250@cRS}vxjf$)0jY|EE0ZT|DXBzy2nx6++w02VNX{- Jmvv4FO#q&xWX=Ep literal 0 HcmV?d00001 diff --git a/android/res/drawable/splashscreen.xml b/android/res/drawable/splashscreen.xml new file mode 100644 index 0000000..9861a2b --- /dev/null +++ b/android/res/drawable/splashscreen.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + -- 2.54.0 From 53b7f1ce98170f53ba881946f2ff5d3c4281b1fc Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 16:41:39 +0100 Subject: [PATCH 0033/1428] Aim for low-level info first. --- mobile/PeersOverview.qml | 95 ++++++++++++++++++++++++++++++++++++++++ mobile/main.qml | 29 ++++++------ mobile/qml.qrc | 1 + 3 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 mobile/PeersOverview.qml diff --git a/mobile/PeersOverview.qml b/mobile/PeersOverview.qml new file mode 100644 index 0000000..7833f84 --- /dev/null +++ b/mobile/PeersOverview.qml @@ -0,0 +1,95 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + Label { + id: chainHeightLabel + text: "Headers synced height: " + Pay.chainHeight + } + + ListView { + id: listView + model: net.peers + clip: true + ScrollBar.vertical: ScrollBar { } + + width: parent.width + anchors.top: chainHeightLabel.bottom + anchors.bottom: parent.bottom + focus: true + delegate: Rectangle { + width: listView.width + height: peerPane.height + 12 + color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base + ColumnLayout { + id: peerPane + width: listView.width - 20 + x: 10 + y: 6 + + Label { + text: modelData.userAgent + } + Label { + text: qsTr("Address", "network address (IP)") + ": " + modelData.address + } + RowLayout { + height: secondRow.height + Label { + id: secondRow + text: qsTr("Start-height: %1").arg(modelData.startHeight) + } + Label { + text: qsTr("ban-score: %1").arg(modelData.banScore) + } + } + Label { + id : accountStatus + font.bold: true + text: { + var id = modelData.segmentId; + if (id === 0) { + // not peered yet. + if (modelData.services.Length === 0) + return qsTr("initializing connection") + if (!modelData.headersReceived) + return qsTr("Verifying peer") + return qsTr("Assigning...") + } + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + if (accounts[i].id === id) + return qsTr("Peer for wallet: %1").arg(accounts[i].name); + } + return qsTr("Peer for initial wallet"); + } + visible: text !== "" + } + Flow { + Repeater { + model: modelData.services + delegate: Label { text: modelData } + } + } + } + } + } +} diff --git a/mobile/main.qml b/mobile/main.qml index 1d750fd..09408a9 100644 --- a/mobile/main.qml +++ b/mobile/main.qml @@ -15,8 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts ApplicationWindow { id: mainWindow @@ -31,21 +32,17 @@ ApplicationWindow { property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" - Rectangle { - focus: true - id: header - color: Pay.useDarkSkin ? "#232629" : mainWindow.floweeBlue + Loader { + id: content anchors.fill: parent + source: mainWindow.isLoading ? "" : "PeersOverview.qml" + } - Image { - id: appLogo - x: 10 - y: 10 - smooth: true - source: "qrc:/FloweePay-light.svg" - // ratio: 77 / 449 - height: 50 - width: height * 449 / 77 - } + Text { + color: "white" + text: "Loading" + anchors.centerIn: parent + font.pointSize: 40 + visible: mainWindow.isLoading } } diff --git a/mobile/qml.qrc b/mobile/qml.qrc index 66b7a57..8e01367 100644 --- a/mobile/qml.qrc +++ b/mobile/qml.qrc @@ -2,5 +2,6 @@ ../images/FloweePay-light.svg main.qml + PeersOverview.qml -- 2.54.0 From 5b6f9c4ebf1f7fb9a2963151c1b6aea9b561ff7f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 16:42:34 +0100 Subject: [PATCH 0034/1428] Update docker to include Svg lib --- android/build-pay.sh | 16 ++++++++++------ android/docker/build-docker.sh | 2 +- android/docker/scripts/buildQt.sh | 11 ++++++++--- android/docker/scripts/qtbase-cmake-macros.patch | 10 ++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 android/docker/scripts/qtbase-cmake-macros.patch diff --git a/android/build-pay.sh b/android/build-pay.sh index ecbfb84..0f8531f 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -16,7 +16,7 @@ # along with this program. If not, see . _thehub_dir_="$1" -_docker_name_="$2" +_pay_native_name_="$2" if test -d .bin; then .bin/doBuild @@ -25,11 +25,11 @@ fi if test -z "$_thehub_dir_"; then echo "Usage:" - echo " build-android [DOCKER_NAME]" + echo " build-android [PAY_NATIVCE_builddir]" echo "" echo "Start this client in your builddir" - echo "hub-builddir is the dir where the android build of hub is." - echo "docker-name (optional) is the image name to do the compile in" + echo "HUB-builddir is the dir where the android build of flowe-thehub is." + echo "Pay_NATIVE-builddir for a native build of flowee-pay (optional)." exit fi @@ -44,12 +44,16 @@ if test -f $_thehub_dir_/lib/libflowee_p2p.a; then fi if test "$_ok" -eq 0; then - echo "Invalid or not compiled HUB build dir." + echo "Invalid or not compiled for Android HUB build dir." exit fi if test -z "$_docker_name_"; then - _docker_name_="flowee/buildenv-android:v6.3.1" + _docker_name_="flowee/buildenv-android:v6.4.0" +fi + +if test -d "$_pay_native_name_"; then + cp -f "$_pay_native_name_"/*qm . fi floweePaySrcDir=`dirname $0`/.. diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index ade8d63..cfdb452 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -9,7 +9,7 @@ if test "$1" != "force"; then echo "set your user-id in the 'useradd' line." echo -n "Your current users ID is: " id -u - echo "If you are ready to build the iamge, pass in 'force' to this build script" + echo "If you are ready to build the image, pass in 'force' to this build script" exit fi fi diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index cf8d8f7..beb5843 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -8,7 +8,7 @@ fi echo "Based on Qt version $TAG" >> /etc/versions source /etc/profile -for i in qtbase qtshadertools qtdeclarative +for i in qtbase qtshadertools qtdeclarative qtsvg do cd /usr/local/cache if ! test -d $i.git; then @@ -30,7 +30,7 @@ mkdir -p ~builduser/build-native/qtbase ninja install) # Others -for i in qtshadertools qtdeclarative +for i in qtshadertools qtdeclarative qtsvg do cd ~builduser/build-native mkdir -p $i @@ -62,7 +62,7 @@ ninja install # -- Neither ANDROID_PLATFORM nor ANDROID_NATIVE_API_LEVEL were specified, using API level 23 as default # Others -for i in qtshadertools qtdeclarative +for i in qtshadertools qtdeclarative qtsvg do cd ~builduser/build mkdir -p $i @@ -70,3 +70,8 @@ do /opt/android-qt6/bin/qt-configure-module ~builduser/$i ninja install done + +cd /opt/android-qt6/ +patch -p0 < /usr/local/bin/qtbase-cmake-macros.patch + + diff --git a/android/docker/scripts/qtbase-cmake-macros.patch b/android/docker/scripts/qtbase-cmake-macros.patch new file mode 100644 index 0000000..9ea8bda --- /dev/null +++ b/android/docker/scripts/qtbase-cmake-macros.patch @@ -0,0 +1,10 @@ +--- lib/cmake/Qt6Core/Qt6CoreMacros.cmake 2022-11-10 16:08:46.845156002 +0100 ++++ lib/cmake/Qt6Core/Qt6CoreMacros.cmake 2022-11-10 16:09:12.735517356 +0100 +@@ -548,7 +548,6 @@ + cmake_parse_arguments(PARSE_ARGV 1 arg "MANUAL_FINALIZATION" "" "") + + _qt_internal_create_executable("${target}" ${arg_UNPARSED_ARGUMENTS}) +- target_link_libraries("${target}" PRIVATE Qt6::Core) + + if(arg_MANUAL_FINALIZATION) + # Caller says they will call qt6_finalize_target() themselves later -- 2.54.0 From 98c60ff1bafbad75523e03db0d9b1dbef62a70c7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 17:05:27 +0100 Subject: [PATCH 0035/1428] Add ifdefs for Android --- main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index e932c28..62d1dd5 100644 --- a/main.cpp +++ b/main.cpp @@ -75,12 +75,14 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); + QCommandLineParser parser; + QCommandLineOption connect(QStringList() << "connect", "Connect to HOST", "HOST"); +#ifndef ANDROID parser.setApplicationDescription("Flowee pay"); QCommandLineOption debug(QStringList() << "debug", "Use debug level logging"); QCommandLineOption verbose(QStringList() << "verbose" << "v", "Be more verbose"); QCommandLineOption quiet(QStringList() << "quiet" << "q", "Be quiet, only errors are shown"); - QCommandLineOption connect(QStringList() << "connect", "Connect to HOST", "HOST"); QCommandLineOption testnet4(QStringList() << "testnet4", "Use testnet4"); parser.addHelpOption(); parser.addOption(debug); @@ -98,7 +100,6 @@ int main(int argc, char *argv[]) parser.addOption(headers); #endif parser.process(qapp); - auto *logger = Log::Manager::instance(); logger->clearChannels(); Log::Verbosity v = Log::WarningLevel; @@ -113,6 +114,7 @@ int main(int argc, char *argv[]) v = Log::FatalLevel; logger->clearLogLevels(v); logger->addConsoleChannel(); +#endif static const char* languagePacks[] = { "floweepay-desktop", @@ -131,17 +133,19 @@ int main(int argc, char *argv[]) // select chain first (before we create the FloweePay singleton) auto chain = P2PNet::MainChain; +#ifndef ANDROID if (parser.isSet(testnet4)) chain = P2PNet::Testnet4Chain; - FloweePay::selectChain(chain); if (parser.isSet(offline)) FloweePay::instance()->setOffline(true); +#endif + FloweePay::selectChain(chain); std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. // lets try by default to open the path /usr/share/floweepay/* blockheaders.reset(new QFile(QString("/usr/share/floweepay/") + (chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); -#ifndef NDEBUG +#if !defined(NDEBUG) && !defined(ANDROID) // override only available in debug mode if (parser.isSet(headers)) { QFileInfo info(parser.value(headers)); -- 2.54.0 From fdfda0f5e8301662829e72ec5ededa26fa1e12d4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 20:15:37 +0100 Subject: [PATCH 0036/1428] Make caching be default off again --- android/docker/Dockerfile | 8 ++++---- android/docker/README.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 5f93537..243bddb 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,8 +8,8 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -copy mirrorlist /etc/pacman.d/ -RUN useradd builduser -d /home/builds -m -u 1100 -U \ +# copy mirrorlist /etc/pacman.d/ +RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Sy --noconfirm base-devel \ cmake \ ninja \ @@ -44,7 +44,7 @@ RUN useradd builduser -d /home/builds -m -u 1100 -U \ && /usr/local/bin/createRootPwd # to enable caching, check the README.md -add cache /usr/local/cache/ +#add cache /usr/local/cache/ RUN mkdir -p /usr/local/cache \ && git config --global advice.detachedHead false \ && /usr/local/bin/aurs.sh \ @@ -57,4 +57,4 @@ RUN mkdir -p /usr/local/cache \ && rm -rf ~builduser/* \ && /usr/local/bin/buildQrEncode.sh \ && rm -rf ~builduser/* \ - #&& rm -rf /usr/local/cache + && rm -rf /usr/local/cache diff --git a/android/docker/README.md b/android/docker/README.md index 5e3ce27..38aca65 100644 --- a/android/docker/README.md +++ b/android/docker/README.md @@ -51,8 +51,8 @@ the cache filled up. The resulting image, without caching, is some 4.5GB. You will need maybe double that during the build. -If you turn on caching then the downloads will go down, but the image size will go up -to some 7GB. +If you need to do the build of the image repeatedly you might want to turn on caching, +then the downloads will go down, but the image size will go up to some 7GB. # User-ID -- 2.54.0 From 2fea2fa81603d773ce543df3d1c57867cbcc3dcd Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Nov 2022 23:44:18 +0100 Subject: [PATCH 0037/1428] Fixlets in docker build and use less disk space --- android/docker/Dockerfile | 4 +++- android/docker/README.md | 4 ++-- android/docker/scripts/buildQt.sh | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 243bddb..d6bfd17 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -10,7 +10,8 @@ add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist # copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ - && pacman -Sy --noconfirm base-devel \ + && pacman -Suy --noconfirm --noprogressbar \ + && pacman -Sy --noconfirm --noprogressbar --needed base-devel \ cmake \ ninja \ git \ @@ -58,3 +59,4 @@ RUN mkdir -p /usr/local/cache \ && /usr/local/bin/buildQrEncode.sh \ && rm -rf ~builduser/* \ && rm -rf /usr/local/cache + diff --git a/android/docker/README.md b/android/docker/README.md index 38aca65..5e3ce27 100644 --- a/android/docker/README.md +++ b/android/docker/README.md @@ -51,8 +51,8 @@ the cache filled up. The resulting image, without caching, is some 4.5GB. You will need maybe double that during the build. -If you need to do the build of the image repeatedly you might want to turn on caching, -then the downloads will go down, but the image size will go up to some 7GB. +If you turn on caching then the downloads will go down, but the image size will go up +to some 7GB. # User-ID diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index beb5843..b5ac539 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -58,8 +58,8 @@ cd ~builduser/build/qtbase -DOPENSSL_ROOT_DIR=/opt/android-ssl ninja install -# qtbase gives -# -- Neither ANDROID_PLATFORM nor ANDROID_NATIVE_API_LEVEL were specified, using API level 23 as default +# free up some 6GB +rm -rf ~builduser/qtbase/ ~builduser/build/qtbase/ ~builduser/build-native/qtbase/ # Others for i in qtshadertools qtdeclarative qtsvg -- 2.54.0 From 7a6b1eda0a4b5ec5ec8fa4ef010d52d89484ad35 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Nov 2022 19:26:42 +0100 Subject: [PATCH 0038/1428] Quit on errors To avoid having a blank screen. --- main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 62d1dd5..08a26e9 100644 --- a/main.cpp +++ b/main.cpp @@ -100,6 +100,7 @@ int main(int argc, char *argv[]) parser.addOption(headers); #endif parser.process(qapp); + auto *logger = Log::Manager::instance(); logger->clearChannels(); Log::Verbosity v = Log::WarningLevel; @@ -180,8 +181,10 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "TransactionInfo"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); QQmlApplicationEngine engine; - engine.addImageProvider(QLatin1String("qr"), new QRCreator()); + // quit on error in the QMLs + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &qapp, QCoreApplication::quit, Qt::QueuedConnection); + engine.addImageProvider(QLatin1String("qr"), new QRCreator()); engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); handleLocalQml(engine); -- 2.54.0 From 05cc8cdb64fcab31758d1b012ce547a0b111b0b0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Nov 2022 19:27:10 +0100 Subject: [PATCH 0039/1428] fix typo in comment --- FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 09956d1..fbbcafb 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -106,7 +106,7 @@ FloweePay::FloweePay() m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); - // Update expected chain-height ever 5 minutes + // Update expected chain-height every 5 minutes QTimer *timer = new QTimer(this); timer->setTimerType(Qt::VeryCoarseTimer); timer->start(5 * 60 * 1000); -- 2.54.0 From a9667827296c81be292989bda9835f7918c10a9c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Nov 2022 19:30:46 +0100 Subject: [PATCH 0040/1428] [Android] save on hiding the app. It turned out to be impossible to save more than a couple of KB on exit, so we moved that to the app becoming inactive. On Android, unlike desktop, exiting the app is a 2-stage user interaction. First the app is made inactive (equivalent to the minimize on desktop) and at any point in time later the app is closed or killed. So, while we save on exit on desktop this doesn't work on mobile and we must save on it becoming inactive in order to make sure we don't lose data. --- FloweePay.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/FloweePay.cpp b/FloweePay.cpp index fbbcafb..91d41f4 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -86,6 +86,19 @@ FloweePay::FloweePay() connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() { p2pNet()->shutdown(); }); +#ifdef ANDROID + // on Android, an app is either full screen (active) or inactive and not visible. + // It is expected we save state as we move to inactive state in order to make the app + // trivial to kill without loss of data. + // The above 'aboutToQuit' will not be given enough CPU time to actually save much. + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationInactive) { + p2pNet()->saveData(); + } + }); +#endif // start creation of downloadmanager and loading of data in a different thread ioService().post(std::bind(&FloweePay::init, this)); -- 2.54.0 From 0722026c6f8068e6a0c6aec7b87897a8f4fdd520 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Nov 2022 19:48:17 +0100 Subject: [PATCH 0041/1428] Guard very new signal with precompiler This keeps the project compiling with a less recent Qt --- main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.cpp b/main.cpp index 08a26e9..52fad95 100644 --- a/main.cpp +++ b/main.cpp @@ -181,8 +181,10 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "TransactionInfo"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); QQmlApplicationEngine engine; +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) // quit on error in the QMLs QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &qapp, QCoreApplication::quit, Qt::QueuedConnection); +#endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); -- 2.54.0 From 05d666bc741e1e13562ac90c855888118ead9369 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 13 Nov 2022 16:58:19 +0100 Subject: [PATCH 0042/1428] Add the basic block-headers in the APK --- CMakeLists.txt | 27 ++++++++++++++++++++++++++- android/build-pay.sh | 2 +- main.cpp | 20 ++++++++++++++------ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ecd686..cc48be3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,26 @@ find_package(QREncode REQUIRED) option(local_qml "Allow local QML loading" OFF) +add_definitions(-DTARGET_OS_${CMAKE_SYSTEM_NAME}) +# The cmake system name will hold values like Android, Linux or others. + +function(download_file url path) + if (NOT EXISTS "${path}") + get_filename_component(file "${path}" NAME) + message(STATUS "Downloading file ${file} from ${url}") + + file(DOWNLOAD "${url}" "${path}" INACTIVITY_TIMEOUT 10 STATUS download_result) + list(GET download_result 0 status_code) + list(GET download_result 1 error_message) + + if (NOT status_code EQUAL 0) + file(REMOVE "${path}") + message(FATAL_ERROR "Failed to download ${url}: ${error_message}") + endif() + endif() +endfunction() + + set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp @@ -143,9 +163,13 @@ endif() option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) if (ANDROID AND BUILD_MOBILE_PAY) + # blockheaders to be included in the APK + download_file(https://flowee.org/products/pay/blockheaders + ${CMAKE_BINARY_DIR}/android-build/assets/blockheaders) + configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") - message ("Found translation files, adding to build binary") + message ("pay_mobile: Found pre-compiled translations") set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) endif() @@ -224,6 +248,7 @@ endif() message("") message("Configuration results:") message("----------------------") +message("Target OS: ${CMAKE_SYSTEM_NAME}") if (${BUILD_DESKTOP_PAY}) message ("-> Building Desktop-Pay...") if (${Qt6DBus_FOUND}) diff --git a/android/build-pay.sh b/android/build-pay.sh index 0f8531f..c281b38 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -25,7 +25,7 @@ fi if test -z "$_thehub_dir_"; then echo "Usage:" - echo " build-android [PAY_NATIVCE_builddir]" + echo " build-android [PAY_NATIVE_builddir]" echo "" echo "Start this client in your builddir" echo "HUB-builddir is the dir where the android build of flowe-thehub is." diff --git a/main.cpp b/main.cpp index 52fad95..031daa8 100644 --- a/main.cpp +++ b/main.cpp @@ -78,7 +78,7 @@ int main(int argc, char *argv[]) QCommandLineParser parser; QCommandLineOption connect(QStringList() << "connect", "Connect to HOST", "HOST"); -#ifndef ANDROID +#ifndef TARGET_OS_Android parser.setApplicationDescription("Flowee pay"); QCommandLineOption debug(QStringList() << "debug", "Use debug level logging"); QCommandLineOption verbose(QStringList() << "verbose" << "v", "Be more verbose"); @@ -134,7 +134,7 @@ int main(int argc, char *argv[]) // select chain first (before we create the FloweePay singleton) auto chain = P2PNet::MainChain; -#ifndef ANDROID +#ifndef TARGET_OS_Android if (parser.isSet(testnet4)) chain = P2PNet::Testnet4Chain; if (parser.isSet(offline)) @@ -142,11 +142,19 @@ int main(int argc, char *argv[]) #endif FloweePay::selectChain(chain); + // per OS code to find the static headers + QString osPath; +#ifdef TARGET_OS_Linux + /* On Linux we try by default on /usr/share/floweepay */ + osPath = QString("/usr/share/floweepay/"); +#endif +#ifdef TARGET_OS_Android + osPath = QString("@assets:/"); +#endif std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. - // lets try by default to open the path /usr/share/floweepay/* - blockheaders.reset(new QFile(QString("/usr/share/floweepay/") - + (chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); -#if !defined(NDEBUG) && !defined(ANDROID) + if (!osPath.isEmpty()) + blockheaders.reset(new QFile(osPath + (chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); +#if !defined(NDEBUG) && !defined(TARGET_OS_Android) // override only available in debug mode if (parser.isSet(headers)) { QFileInfo info(parser.value(headers)); -- 2.54.0 From f9dd9f6e434d3c6789fd6b57709af359b7c10bd5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 13 Nov 2022 18:25:13 +0100 Subject: [PATCH 0043/1428] Support reading bip39 wordlists on Android --- CMakeLists.txt | 14 +++++++++++++- FloweePay.cpp | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cc48be3..d05b936 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,8 +164,20 @@ option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) if (ANDROID AND BUILD_MOBILE_PAY) # blockheaders to be included in the APK + set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) download_file(https://flowee.org/products/pay/blockheaders - ${CMAKE_BINARY_DIR}/android-build/assets/blockheaders) + ${ASSETS_DIR}/blockheaders) + file(COPY ${CMAKE_SOURCE_DIR}/data/bip39-english + ${CMAKE_SOURCE_DIR}/data/bip39-chinese_simplified + ${CMAKE_SOURCE_DIR}/data/bip39-chinese_traditional + ${CMAKE_SOURCE_DIR}/data/bip39-czech + ${CMAKE_SOURCE_DIR}/data/bip39-french + ${CMAKE_SOURCE_DIR}/data/bip39-italian + ${CMAKE_SOURCE_DIR}/data/bip39-japanese + ${CMAKE_SOURCE_DIR}/data/bip39-korean + ${CMAKE_SOURCE_DIR}/data/bip39-portuguese + ${CMAKE_SOURCE_DIR}/data/bip39-spanish + DESTINATION ${ASSETS_DIR}) configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") diff --git a/FloweePay.cpp b/FloweePay.cpp index 91d41f4..d5de2c2 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -125,8 +125,17 @@ FloweePay::FloweePay() timer->start(5 * 60 * 1000); connect (timer, SIGNAL(timeout()), this, SIGNAL(expectedChainHeightChanged())); - QDir base(QCoreApplication::applicationDirPath() + "/../share/floweepay/"); - if (base.exists()) { + QString base; +#if TARGET_OS_Android + base = QLatin1String("assets:/"); +#else + QDir baseDir(QCoreApplication::applicationDirPath() + "/../share/floweepay/"); + if (baseDir.exists()) + base = baseDir.absolutePath(); + else + logCritical() << "Warning: No bip39 wordlists found. Looking in:" << baseDir.absolutePath(); +#endif + if (!base.isEmpty()) { // add Mnemonic (BIP39) dictionaries. struct LangPair { const char *id, *filename; }; static const LangPair knownPairs[] = { @@ -144,14 +153,11 @@ FloweePay::FloweePay() }; for (int i = 0; knownPairs[i].id; ++i) { const LangPair lang = knownPairs[i]; - QString fullPath(base.absoluteFilePath(lang.filename)); + QString fullPath(base + '/' + lang.filename); if (QFile::exists(fullPath)) m_hdSeedValidator.registerWordList(lang.id, fullPath); } } - else { - logCritical() << "Warning: No bip39 wordlists found. Looking in:" << base.absolutePath(); - } // forward signal connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); -- 2.54.0 From 8b86e22c6a5b2e0a4e08f58f2b7d7b8c52ee99c0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 13 Nov 2022 20:06:26 +0100 Subject: [PATCH 0044/1428] New revision of the build script --- android/build-pay.sh | 58 +++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index c281b38..0bb4ca0 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -18,18 +18,18 @@ _thehub_dir_="$1" _pay_native_name_="$2" -if test -d .bin; then - .bin/doBuild +if test -f smartBuild.sh; then + ./smartBuild.sh exit fi -if test -z "$_thehub_dir_"; then +if test -z "$_pay_native_name_"; then echo "Usage:" - echo " build-android [PAY_NATIVE_builddir]" + echo " build-pay " echo "" echo "Start this client in your builddir" echo "HUB-builddir is the dir where the android build of flowe-thehub is." - echo "Pay_NATIVE-builddir for a native build of flowee-pay (optional)." + echo "Pay_NATIVE-builddir for a native build of flowee-pay." exit fi @@ -52,13 +52,18 @@ if test -z "$_docker_name_"; then _docker_name_="flowee/buildenv-android:v6.4.0" fi -if test -d "$_pay_native_name_"; then - cp -f "$_pay_native_name_"/*qm . +if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then + echo "Invalid or not compiled for Android Pay-native dir." + exit fi +mkdir -p imports +cp -f "$_pay_native_name_"/*qm imports/ +cp -f "$_pay_native_name_"/blockheaders-meta-extractor imports/ + floweePaySrcDir=`dirname $0`/.. -if ! test -f .config; then +if test ! -f .config; then cat << HERE > .config cd /home/builds/build @@ -72,27 +77,46 @@ if ! test -f build.ninja; then -DOPENSSL_CRYPTO_LIBRARY=/opt/android-ssl/lib/libcrypto.a \\ -DOPENSSL_SSL_LIBRARY=/opt/android-ssl/lib/libssl.a \\ -DOPENSSL_INCLUDE_DIR=/opt/android-ssl/include/ \\ + -DCMAKE_BUILD_TYPE=Release \\ -G Ninja \\ - -DCMAKE_INSTALL_PREFIX=\`pwd\` \\ /home/builds/src fi - -ninja install HERE chmod 755 .config fi -if ! test -d .bin; then - mkdir .bin -cat << HERE > .bin/doBuild +if ! test -f smartBuild.sh; then +cat << HERE > smartBuild.sh +#!/bin/bash +#Created by build-pay.sh + +if test "\$1" = "distclean"; then + perl -e 'use File::Path qw(remove_tree); opendir DIR, "."; while (\$entry = readdir DIR) { if (\$entry=~/^\./) { next; } if (\$entry=~/smartBuild.sh$/ || \$entry=~/^imports$/) { next; } unlink "\$entry"; remove_tree "\$entry"; }' +fi + +if test ! -f build.ninja; then + cp -n imports/*qm . + docker run --rm -ti\ + --volume=`pwd`:/home/builds/build \ + --volume=$floweePaySrcDir:/home/builds/src \ + --volume=$_thehub_dir_:/home/builds/floweelibs \ + $_docker_name_ \ + build/.config +fi + +if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockheaders.info; then + imports/blockheaders-meta-extractor android-build/assets +fi + docker run --rm -ti\ --volume=`pwd`:/home/builds/build \ --volume=$floweePaySrcDir:/home/builds/src \ --volume=$_thehub_dir_:/home/builds/floweelibs \ $_docker_name_ \ - build/.config + "/usr/bin/ninja -C build" + HERE - chmod 700 .bin/doBuild + chmod 700 smartBuild.sh fi -.bin/doBuild +./smartBuild.sh -- 2.54.0 From f47f52cfea14c6a68ab37504056e7a498489e264 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Nov 2022 10:41:19 +0100 Subject: [PATCH 0045/1428] Use member instead of global The chain is set before we init using a global (static, really). What we really use is set at init as a member, while the global can change later but is ignored. So we should use the member to avoid any weird code updating the global after the init point, to something we are not actually using. --- FloweePay.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index d5de2c2..0babc0b 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -691,7 +691,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons auto wallet = createWallet(walletName); wallet->setSingleAddressWallet(true); if (startHeight <= 1) - startHeight = s_chain == P2PNet::MainChain ? 550000 : 1000; + startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; wallet->addPrivateKey(privateKey, startHeight); saveData(); if (!m_offline) @@ -734,7 +734,7 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons try { std::vector derivationPath = HDMasterKey::deriveFromString(derivationPathStr.toStdString()); if (startHeight <= 1) - startHeight = s_chain == P2PNet::MainChain ? 550000 : 1000; + startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; wallet->createHDMasterKey(mnemonic, password, derivationPath, startHeight); wallet->segment()->blockSynched(startHeight); wallet->segment()->blockSynched(startHeight); // yes, twice @@ -880,27 +880,27 @@ QString FloweePay::nameOfUnit(FloweePay::UnitOfBitcoin unit) const { switch (unit) { case FloweePay::BCH: - if (s_chain == P2PNet::MainChain) + if (m_chain == P2PNet::MainChain) return QLatin1String("BCH"); else return QLatin1String("tBCH"); case FloweePay::MilliBCH: - if (s_chain == P2PNet::MainChain) + if (m_chain == P2PNet::MainChain) return QLatin1String("mBCH"); else return QLatin1String("m-tBCH"); case FloweePay::MicroBCH: - if (s_chain == P2PNet::MainChain) + if (m_chain == P2PNet::MainChain) return QString("µBCH"); else return QString("µ-tBCH"); case FloweePay::Bits: - if (s_chain == P2PNet::MainChain) + if (m_chain == P2PNet::MainChain) return QLatin1String("bits"); else return QLatin1String("tbits"); case FloweePay::Satoshis: - if (s_chain == P2PNet::MainChain) + if (m_chain == P2PNet::MainChain) return QLatin1String("sats"); else return QLatin1String("tsats"); -- 2.54.0 From db2587393e8e24929a5426a65aba99acb7d7e273 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 13 Nov 2022 21:56:44 +0100 Subject: [PATCH 0046/1428] Refactor main, split into multiple files To solve this becoming slowly unreadable due to ifdefs, move stuff to methods which have a differnt implementation based on which OS we target. The build-system now chooses to compile only the utils file for the target OS. --- CMakeLists.txt | 17 +++-- main.cpp | 145 +++++++----------------------------- main_utils.cpp | 155 +++++++++++++++++++++++++++++++++++++++ main_utils_android.cpp | 104 ++++++++++++++++++++++++++ mobile/PeersOverview.qml | 2 +- 5 files changed, 300 insertions(+), 123 deletions(-) create mode 100644 main_utils.cpp create mode 100644 main_utils_android.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d05b936..7d5f3b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ find_package(QREncode REQUIRED) option(local_qml "Allow local QML loading" OFF) -add_definitions(-DTARGET_OS_${CMAKE_SYSTEM_NAME}) +add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) # The cmake system name will hold values like Android, Linux or others. function(download_file url path) @@ -86,9 +86,12 @@ set (PAY_SOURCES Wallet_spending.cpp ) -add_library(pay_lib STATIC - ${PAY_SOURCES} -) +if (ANDROID) + list(APPEND PAY_SOURCES main_utils_android.cpp) +else () + list(APPEND PAY_SOURCES main_utils.cpp) +endif () +add_library(pay_lib STATIC ${PAY_SOURCES}) target_link_libraries(pay_lib flowee_apputils @@ -154,11 +157,11 @@ if(BUILD_DESKTOP_PAY) desktop/qml.qrc desktop-i18n.qrc ) + set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") target_link_libraries(pay pay_lib Qt6::Svg) install(TARGETS pay DESTINATION bin) endif() - ###### Pay-mobile executable option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) @@ -195,6 +198,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) target_link_libraries(pay_mobile pay_lib Qt6::Svg) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android + COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) qt6_android_generate_deployment_settings(pay_mobile) @@ -214,6 +218,9 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) mobile/qml.qrc mobile-i18n.qrc ) + set_target_properties(pay_mobile PROPERTIES + COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" + ) target_link_libraries(pay_mobile pay_lib Qt6::Svg) install(TARGETS pay_mobile DESTINATION bin) endif() diff --git a/main.cpp b/main.cpp index 031daa8..8bfc3ee 100644 --- a/main.cpp +++ b/main.cpp @@ -17,27 +17,24 @@ */ #include "BitcoinValue.h" #include "FloweePay.h" -#include "NetDataProvider.h" #include "NewWalletConfig.h" -#include "PortfolioDataProvider.h" #include "PriceDataProvider.h" #include "Payment.h" +#include "TransactionInfo.h" +#include "PaymentRequest.h" #include "QRCreator.h" #include // for ECC_Start() -#include -#include -#include #include #include #include #include -#include #include #include + namespace { void HandleSigTerm(int) { QCoreApplication::quit(); @@ -59,8 +56,21 @@ struct ECC_State }; } +#ifdef TARGET_OS_Android +# include "main_utils_android.cpp" +#else +# include "main_utils.cpp" +#endif + + // defined in qml_path_helper.cpp.in void handleLocalQml(QQmlApplicationEngine &engine); +// defined in the utils file +struct CommandLineParserData; +CommandLineParserData* createCLD(QGuiApplication &app); +void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld); +std::unique_ptr handleStaticChain(CommandLineParserData *cld); +Log::Verbosity logVerbosity(CommandLineParserData *cld); int main(int argc, char *argv[]) @@ -76,51 +86,21 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); - QCommandLineParser parser; - QCommandLineOption connect(QStringList() << "connect", "Connect to HOST", "HOST"); -#ifndef TARGET_OS_Android - parser.setApplicationDescription("Flowee pay"); - QCommandLineOption debug(QStringList() << "debug", "Use debug level logging"); - QCommandLineOption verbose(QStringList() << "verbose" << "v", "Be more verbose"); - QCommandLineOption quiet(QStringList() << "quiet" << "q", "Be quiet, only errors are shown"); - QCommandLineOption testnet4(QStringList() << "testnet4", "Use testnet4"); - parser.addHelpOption(); - parser.addOption(debug); - parser.addOption(verbose); - parser.addOption(quiet); - parser.addOption(connect); - parser.addOption(testnet4); - // nice features to test your QML changes. - QCommandLineOption offline(QStringList() << "offline", "Do not connect"); - parser.addOption(offline); -#ifndef NDEBUG - // to protect people from the bad effect of having and later not having headers we only allow this - // override in debug mode. - QCommandLineOption headers(QStringList() << "headers", "Override location of blockheaders", "PATH"); - parser.addOption(headers); -#endif - parser.process(qapp); - + auto cld = createCLD(qapp); auto *logger = Log::Manager::instance(); logger->clearChannels(); - Log::Verbosity v = Log::WarningLevel; -#ifndef BCH_NO_DEBUG_OUTPUT - if (parser.isSet(debug)) - v = Log::DebugLevel; - else -#endif - if (parser.isSet(verbose)) - v = Log::InfoLevel; - else if (parser.isSet(quiet)) - v = Log::FatalLevel; - logger->clearLogLevels(v); + logger->clearLogLevels(logVerbosity(cld)); logger->addConsoleChannel(); -#endif + + auto blockheaders = handleStaticChain(cld); static const char* languagePacks[] = { +#ifdef DESKTOP "floweepay-desktop", - "floweepay-common", +#elif MOBILE "floweepay-mobile", +#endif + "floweepay-common", nullptr }; @@ -132,59 +112,6 @@ int main(int argc, char *argv[]) delete translator; } - // select chain first (before we create the FloweePay singleton) - auto chain = P2PNet::MainChain; -#ifndef TARGET_OS_Android - if (parser.isSet(testnet4)) - chain = P2PNet::Testnet4Chain; - if (parser.isSet(offline)) - FloweePay::instance()->setOffline(true); -#endif - FloweePay::selectChain(chain); - - // per OS code to find the static headers - QString osPath; -#ifdef TARGET_OS_Linux - /* On Linux we try by default on /usr/share/floweepay */ - osPath = QString("/usr/share/floweepay/"); -#endif -#ifdef TARGET_OS_Android - osPath = QString("@assets:/"); -#endif - std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. - if (!osPath.isEmpty()) - blockheaders.reset(new QFile(osPath + (chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); -#if !defined(NDEBUG) && !defined(TARGET_OS_Android) - // override only available in debug mode - if (parser.isSet(headers)) { - QFileInfo info(parser.value(headers)); - if (info.exists()) { - if (info.isDir()) - blockheaders.reset(new QFile(info.absoluteFilePath() - + (chain == P2PNet::MainChain ? "/blockheaders" : "/blockheaders-testnet4"))); - else - blockheaders.reset(new QFile(info.absoluteFilePath())); - } - else { - // do not load if pointing to invalid path. - logWarning() << "Headers disabled by cli option"; - blockheaders.reset(); - } - } -#endif - - if (blockheaders) { - if (!blockheaders->open(QIODevice::ReadOnly)) { // can't be opened for reading. - blockheaders.reset(); - } - else { - QFileInfo info(*blockheaders); - Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), - (info.absoluteFilePath() + ".info").toStdString()); - blockheaders->close(); - } - } - ECC_State crypo_state; // allows the secp256k1 to function. qmlRegisterType("Flowee.org.pay", 1, 0, "TransactionInfo"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); @@ -200,27 +127,11 @@ int main(int argc, char *argv[]) handleLocalQml(engine); engine.load(engine.baseUrl().url() + "/main.qml"); - QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &parser, &connect]() { - FloweePay *app = FloweePay::instance(); + // make sure that FloweePay::instance() is not called above this line! + // since doing so will start initialization of the p2p stuff in a separate thread. - NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); - app->p2pNet()->addP2PNetListener(netData); - netData->startRefreshTimer(); - - PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); - for (auto &wallet : app->wallets()) { - portfolio->addWalletAccount(wallet); - } - portfolio->selectDefaultWallet(); - - engine.rootContext()->setContextProperty("net", netData); - engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); - engine.rootContext()->setContextProperty("portfolio", portfolio); - if (parser.isSet(connect)) { - app->p2pNet()->connectionManager().peerAddressDb().addOne( // actually connect to it too. - EndPoint(parser.value(connect).toStdString(), 8333)); - } - app->startNet(); // lets go! + QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld]() { + loadCompleteHandler(engine, cld); }); // Clean shutdown on SIGTERM diff --git a/main_utils.cpp b/main_utils.cpp new file mode 100644 index 0000000..e21ef7b --- /dev/null +++ b/main_utils.cpp @@ -0,0 +1,155 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "qqmlcontext.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct CommandLineParserData +{ + CommandLineParserData(QGuiApplication &qapp) + : connect(QStringList() << "connect", "Connect to HOST", "HOST"), + debug(QStringList() << "debug", "Use debug level logging"), + verbose(QStringList() << "verbose" << "v", "Be more verbose"), + quiet(QStringList() << "quiet" << "q", "Be quiet, only errors are shown"), + testnet4(QStringList() << "testnet4", "Use testnet4"), + offline(QStringList() << "offline", "Do not connect"), + headers(QStringList() << "headers", "Override location of blockheaders", "PATH") + { + parser.setApplicationDescription("Flowee pay"); + parser.addHelpOption(); + parser.addOption(debug); + parser.addOption(verbose); + parser.addOption(quiet); + parser.addOption(connect); + parser.addOption(testnet4); + parser.addOption(offline); +#ifndef NDEBUG + // to protect people from the bad effect of having and later not having headers we only allow this + // override in debug mode. + parser.addOption(headers); +#endif + parser.process(qapp); + + // select chain first (before we create the FloweePay singleton) + if (parser.isSet(testnet4)) + chain = P2PNet::Testnet4Chain; + } + + QCommandLineParser parser; + QCommandLineOption connect; + QCommandLineOption debug; + QCommandLineOption verbose; + QCommandLineOption quiet; + QCommandLineOption testnet4; + QCommandLineOption offline; + QCommandLineOption headers; + P2PNet::Chain chain = P2PNet::MainChain; +}; + +CommandLineParserData* createCLD(QGuiApplication &app) +{ + return new CommandLineParserData(app); +} + +Log::Verbosity logVerbosity(CommandLineParserData *cld) +{ +#ifndef BCH_NO_DEBUG_OUTPUT + if (cld->parser.isSet(cld->debug)) + return Log::DebugLevel; +#endif + if (cld->parser.isSet(cld->verbose)) + return Log::InfoLevel; + if (cld->parser.isSet(cld->quiet)) + return Log::FatalLevel; + return Log::WarningLevel; +} + +std::unique_ptr handleStaticChain(CommandLineParserData *cld) +{ + std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. + + + blockheaders.reset(new QFile(QString("/usr/share/floweepay/") + + (cld->chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); +#ifndef NDEBUG + // override only available in debug mode + if (cld->parser.isSet(cld->headers)) { + QFileInfo info(cld->parser.value(cld->headers)); + if (info.exists()) { + if (info.isDir()) + blockheaders.reset(new QFile(info.absoluteFilePath() + + (cld->chain == P2PNet::MainChain ? "/blockheaders" : "/blockheaders-testnet4"))); + else + blockheaders.reset(new QFile(info.absoluteFilePath())); + } + else { + // do not load if pointing to invalid path. + logWarning() << "Headers disabled by cli option"; + blockheaders.reset(); + } + } +#endif + + if (blockheaders) { + if (!blockheaders->open(QIODevice::ReadOnly)) { // can't be opened for reading. + blockheaders.reset(); + } + else { + QString infoFilePath = blockheaders->fileName() + ".info"; + Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), + infoFilePath.toStdString()); + blockheaders->close(); + } + } + return blockheaders; +} + +void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) +{ + FloweePay *app = FloweePay::instance(); + + NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); + app->p2pNet()->addP2PNetListener(netData); + netData->startRefreshTimer(); + + PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); + for (auto &wallet : app->wallets()) { + portfolio->addWalletAccount(wallet); + } + portfolio->selectDefaultWallet(); + + engine.rootContext()->setContextProperty("net", netData); + engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); + engine.rootContext()->setContextProperty("portfolio", portfolio); + if (cld->parser.isSet(cld->offline)) { + FloweePay::instance()->setOffline(true); + } + else if (cld->parser.isSet(cld->connect)) { + app->p2pNet()->connectionManager().peerAddressDb().addOne( // add it to the DB, making sure there is at least one. + EndPoint(cld->parser.value(cld->connect).toStdString(), 8333)); + } + app->startNet(); // lets go! +} diff --git a/main_utils_android.cpp b/main_utils_android.cpp new file mode 100644 index 0000000..d2cf429 --- /dev/null +++ b/main_utils_android.cpp @@ -0,0 +1,104 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "qqmlcontext.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct CommandLineParserData +{ +}; + +CommandLineParserData* createCLD(QGuiApplication &) +{ + return new CommandLineParserData(); +} + +Log::Verbosity logVerbosity(CommandLineParserData*) +{ + return Log::FatalLevel; +} + +std::unique_ptr handleStaticChain(CommandLineParserData*) +{ + std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. + + /* + * On Android the way to get bigger files into the app is to use the 'assets' subsystem. + * Android is smart and doesn't actually install those files, just provides an interface + * to the still compressed archive. + * + * Unfortunately, that means we can't memory map them and as such we need to do a one-time + * copy of those files to our private homedir. + */ + QFileInfo target("staticHeaders"); + QFileInfo infTarget("staticHeaders.info"); + QFileInfo orig("assets:/blockheaders"); + QFileInfo infOrig("assets:/blockheaders.info"); + + if (!orig.exists() || !infOrig.exists()) { + logFatal() << "MIssing blockheader assets"; + abort(); + } + + bool install = !target.exists() || !infTarget.exists() || orig.size() != target.size(); + if (install) { + // make sure we have a local up-to-date copy + QFile::copy(orig.filePath(), target.absoluteFilePath()); + QFile::copy(orig.filePath() + ".info", infTarget.absoluteFilePath()); + } + assert(target.exists()); + assert(infTarget.exists()); + + blockheaders.reset(new QFile(target.absoluteFilePath())); + if (!blockheaders->open(QIODevice::ReadOnly)) { // can't be opened for reading. + blockheaders.reset(); + return blockheaders; + } + Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), + infTarget.absoluteFilePath().toStdString()); + blockheaders->close(); + return blockheaders; +} + +void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) +{ + FloweePay *app = FloweePay::instance(); + + NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); + app->p2pNet()->addP2PNetListener(netData); + netData->startRefreshTimer(); + + PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); + for (auto &wallet : app->wallets()) { + portfolio->addWalletAccount(wallet); + } + portfolio->selectDefaultWallet(); + + engine.rootContext()->setContextProperty("net", netData); + engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); + engine.rootContext()->setContextProperty("portfolio", portfolio); + app->startNet(); // lets go! +} diff --git a/mobile/PeersOverview.qml b/mobile/PeersOverview.qml index 7833f84..9c03e9a 100644 --- a/mobile/PeersOverview.qml +++ b/mobile/PeersOverview.qml @@ -22,7 +22,7 @@ import QtQuick.Layouts Item { Label { id: chainHeightLabel - text: "Headers synced height: " + Pay.chainHeight + text: "Headers synced height: " + Pay.headerChainHeight } ListView { -- 2.54.0 From b48acdea03840a8bb905bcfe4763f88f5704e2b4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 14 Nov 2022 13:11:26 +0100 Subject: [PATCH 0047/1428] Minor fixes Don't use assert for a failing android deployment as on Android we'll almost only deploy using release-builds. Also be consistent with our ifdef usage. --- FloweePay.cpp | 2 +- main_utils_android.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 0babc0b..3a398b4 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -86,7 +86,7 @@ FloweePay::FloweePay() connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() { p2pNet()->shutdown(); }); -#ifdef ANDROID +#ifdef TARGET_OS_Android // on Android, an app is either full screen (active) or inactive and not visible. // It is expected we save state as we move to inactive state in order to make the app // trivial to kill without loss of data. diff --git a/main_utils_android.cpp b/main_utils_android.cpp index d2cf429..08ee999 100644 --- a/main_utils_android.cpp +++ b/main_utils_android.cpp @@ -69,8 +69,10 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) QFile::copy(orig.filePath(), target.absoluteFilePath()); QFile::copy(orig.filePath() + ".info", infTarget.absoluteFilePath()); } - assert(target.exists()); - assert(infTarget.exists()); + if (!target.exists()) + abort(); + if (!infTarget.exists()) + abort(); blockheaders.reset(new QFile(target.absoluteFilePath())); if (!blockheaders->open(QIODevice::ReadOnly)) { // can't be opened for reading. -- 2.54.0 From d2b743e50bf4a4919e1f2d9a773bb5ef53e3807d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Nov 2022 14:31:43 +0100 Subject: [PATCH 0048/1428] Add defaults.ini for mobile. --- mobile/defaults.ini | 24 ++++++++++++++++++++++++ mobile/qml.qrc | 1 + 2 files changed, 25 insertions(+) create mode 100644 mobile/defaults.ini diff --git a/mobile/defaults.ini b/mobile/defaults.ini new file mode 100644 index 0000000..3550a45 --- /dev/null +++ b/mobile/defaults.ini @@ -0,0 +1,24 @@ +[net] +useragent="Flowee:1 (PayMobile)" + +[window] +darkSkin=true + +[general] +# 0: BCH +# 1: MilliBCH +# 2: MicroBCH +# 3: Bits +# 4: Satoshis +unit=0 +# +# when the app first starts up create a special +# wallet so the user can instantly start receiving. +create-start-wallet=true + +[payment] +# Double Spend Proof timeout in milliseconds. +# After the timeout is reached we deem the tx safe. +#dsp-timeout=3000 + + diff --git a/mobile/qml.qrc b/mobile/qml.qrc index 8e01367..2ae30d0 100644 --- a/mobile/qml.qrc +++ b/mobile/qml.qrc @@ -3,5 +3,6 @@ ../images/FloweePay-light.svg main.qml PeersOverview.qml + defaults.ini -- 2.54.0 From 6ae16ef6a4b8e2657948aac30125695886a87a19 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Nov 2022 16:01:45 +0100 Subject: [PATCH 0049/1428] Add basic signing This ships a keystore for convenience, nothing official and anyone could recreate it. Now running 'smartBuild sign' will create the signed APK. --- android/build-pay.sh | 29 ++++++++++++++++++++++++++++- android/selfsigned.keystore | Bin 0 -> 2655 bytes 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 android/selfsigned.keystore diff --git a/android/build-pay.sh b/android/build-pay.sh index 0bb4ca0..d0b88f1 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -85,6 +85,23 @@ HERE chmod 755 .config fi +if test ! -f .sign; then + cat << HERE > .sign +#!/bin/bash + +cd /home/builds/build +export QT_ANDROID_KEYSTORE_STORE_PASS=longPassword +export QT_ANDROID_KEYSTORE_KEY_PASS=longPassword + +/usr/local/bin/androiddeployqt \ + --input /home/builds/build/android-pay_mobile-deployment-settings.json \ + --output /home/builds/build/android-build \ + --apk /home/builds/floweepay.apk \ + --sign /home/builds/src/android/selfsigned.keystore floweepay +HERE + chmod 755 .sign +fi + if ! test -f smartBuild.sh; then cat << HERE > smartBuild.sh #!/bin/bash @@ -113,7 +130,17 @@ docker run --rm -ti\ --volume=$floweePaySrcDir:/home/builds/src \ --volume=$_thehub_dir_:/home/builds/floweelibs \ $_docker_name_ \ - "/usr/bin/ninja -C build" + "/usr/bin/ninja -C build pay_mobile" + +if test "\$1" = "sign" -o "\$2" = "sign" +then + docker run --rm -ti\ + --volume=`pwd`:/home/builds/build \ + --volume=$floweePaySrcDir:/home/builds/src \ + --volume=$_thehub_dir_:/home/builds/floweelibs \ + $_docker_name_ \ + build/.sign +fi HERE chmod 700 smartBuild.sh diff --git a/android/selfsigned.keystore b/android/selfsigned.keystore new file mode 100644 index 0000000000000000000000000000000000000000..6b2ae6a9fede106c995118755de5b48a1be19674 GIT binary patch literal 2655 zcmXqL;)-TsWHxBx5@F-iYV&CO&dbQoxS)xPm8FS`(V&U*uR#;*78EJgr7TUX3k{lB z=NdGz&Sc|;>f+&IWLnU~>R`~sYGaTF*TKqb5NTk6;PMz~vxwvz%D=eTX#Wh!M%y3m zQBVBN#tANBVp11iVpK5TVdH?9$i&IYU?9uJnb79Jn99t;sKp|n7;b7NcOiXY;vLzE z+e)mPSejTa@O$n0DB#@Kb+5ekCmbnZ^Eq5)P>n;0R z@5g7`iEI;dyDqUwvrBOAy_j78)9vAgNT=h5h88||zp`7#@F;CR_-0wyc1s&0+1Y+G z=WNK<6kPe;*3x$B)sFL-GwbYyoLAocto^^7lTV}|TU9n$OvXlc8t3D0@AsU0C+i>#p;gSGyXKekYwEm+vfljk(gTT(8q zzqY{ZHdB+r#k{ExVs0In9pqV2`CB7hB^zyKj$2I4Z`K7w#r5-&h&^HO&qWa|9 z`ZX7}FDz|U&Fg=@XxjV0dl`EUOiPQ{dg<-PeG~HezUauOi3E9HKf=kbV6uFc@4JdA zr#~s(s#v2iEpD~lTSKRs6fv!sQ(TW`Z&t9|)AcgVCR?O8WTDig88@|ZMCGsZO+K`I z?b{k|&X;FqOuFAF%rX09ZRut2vkOlBx)PBxTWChe6)hL7hrj+fm;ZK`C{MVoSLT_& zX_DIhC+kWW?QSpB+}~$%eccb+sCA#^=XU$b^b)l7Ebp&J1t{8^`^XkUtXHw>>s<-?9Hiz52w9y zUF&k^=NC!t(5`MyFF_4CH?_tV{S^&2OfLPKHRrq;+s3Klj}K?AnqG2f%SP_6(%L60 zdLmL;1Qq91i&;F_w>R|UquhPce|uX2x13keUR$$tQ*ZsXi_g`hR;XS5 z5nsi(ZHbH6&*eP2#?5Ta^FrGm$h>kCvQ&xj-Td_b1^53Yb7t{5yk|f3*sYY^^+eed zsdceQhBF)kMZV8I$slN(@=0!?XKLR<=2!P0pA&uHv?kR0$vo?M0$dF-EnQXrPnsT) zX35dwUMXSdsk8O6$yR$gC+#g+=3Mq$qWTVmN%Gg$oro7GKlI%+pZ}(C@${EBH+!y` zz&Xug>FW=whHeIm@ZyqF#85;|h#`$3hasP#oFSDVm7#zkk)aY{rZmNQz znVGqTk*TqXsfDqzK@*E3+#Ghc1x+kQ22Ct_OpFW$O)TmN8Ad{d^_iy`LGSa6kFNBL z-SSS*^7Mk~^0CAe)~`>q1uB~sE#dwV!MN>w$kv7?<_*d$S&EOkPoC`S=3hJE-Q2f_ zXWSRQb!_Gxp4ZK{T;J7JajbIG4WFed@o9GNyVL9ACzrp!7(1tRj<0$BthLHZ-ze|9 zQ@+7=b>5k_GYn2UUuAKcMJ1YA|J<;GaotDfZHL9Y%?{RVV5-eoHjVSW!Kp){mv+qx z=#1@Me=6nauNlq%9K5nVJ6m4*89Phx3$L;RbMV0zLZUv?7evUoRV-h^^FG*izQBv^ zdzT1KZ2syZIoJJcxkuHbn3kMdA6_nBbhhm7GI6z?GiB47R+jlQNrWz+XYRUlz45{8 z^(VBZOe^pe+2^xFXnDSKzObBa_o|=|SNN|m@4lIL=lKy2rDna{gF(OTfB(7vq}J(X z=NEgiTko^iT=#MPuV9$7Sk5@kbx0>%pC&&L=3bw0cuclvVyO_Z8B+=ku z#pYTLHam$MhH)p?mp{ zNT2vm9APdU7XCt^H3@fFeq7$p!Ta>{X^Yq*y+iSPb)0MseQw`Z_F8AQe9g9{ow$mcQw{*-~g3p}*mvMX%5q zhAY`;#hisNAMT7y-=VJY>2`wLrs<4!LKQbo?+7ty%$_5)_ALAJ331=z*R8u>n&9XY zGD$`Hp_BiQuZH%sL#KG#NSg0*;rO%n>nYa(YE}ZRX_xUbeJ9p)qw--L1cfZYcmQHV`Zl%*hfrZbe zP3}0+%r>*M;>;LW@pE|IOim@S*@Y2+r#I*ao0!F^Os%R=CXSkhqEO} zmL_SHavnOrLi5|%N=wtGtm|ecFVF6BJG}G3iDx04mI*RGx@UbRS6H&J@hoomm~&;* z;jqa9g-dpR;g`5k@kY4)NZIpS6>{5I8YBOB8@F{Vuyni<6O$X>YP2A1&84dXrEznN zot!W3Snz0ZGK1lSPV?Xi0a8omPCORJ^#9*}_oUu9uh<2?H%^$n`&IMNz|KI?fRl|? zn~#}Eij{#ygpt8EOFtq`>`ipOh5Zo`slejm5Ec>N6D(Dyl7p5{c)R9V{S?=4Y9E(# KFflW>EdT&Zfx008 literal 0 HcmV?d00001 -- 2.54.0 From 045864456f2a9a71f48a628f64588f9c9e6893cd Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Nov 2022 16:03:42 +0100 Subject: [PATCH 0050/1428] Use plain http for the price history file. This often failed with some crpto error which begs the question of complexity. --- PriceHistoryDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PriceHistoryDataProvider.cpp b/PriceHistoryDataProvider.cpp index 83995c7..d0a4ed0 100644 --- a/PriceHistoryDataProvider.cpp +++ b/PriceHistoryDataProvider.cpp @@ -304,7 +304,7 @@ InitialHistoryFetcher::InitialHistoryFetcher(QObject *parent) void InitialHistoryFetcher::fetch(const QString &path, const QString ¤cy) { assert(!path.isEmpty()); - QNetworkRequest req(QUrl("https://flowee.org/products/pay/fiat/" + currency)); + QNetworkRequest req(QUrl("http://flowee.org/products/pay/fiat/" + currency)); auto app = QCoreApplication::instance(); QString useragent = QString("%1%2/%3") .arg(app->organizationName(), -- 2.54.0 From d3841674459519305b269da388ec2d715c8848f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Nov 2022 21:19:31 +0100 Subject: [PATCH 0051/1428] Prepare for better shared-widgets setup QML allows re-usable components, this makes the already created list of widgets for Desktop easy to use for mobile as well. --- CMakeLists.txt | 36 +++++----- desktop/qml.qrc | 68 ------------------ {desktop => guis}/ControlColors.js | 0 .../widgets => guis/Flowee}/ArrowPoint.qml | 0 .../Flowee}/BitcoinAmountLabel.qml | 0 .../Flowee}/BitcoinValueField.qml | 0 {desktop/widgets => guis/Flowee}/Button.qml | 0 .../Flowee}/CashFusionIcon.qml | 0 {desktop/widgets => guis/Flowee}/CheckBox.qml | 0 .../widgets => guis/Flowee}/CheckBoxLabel.qml | 0 .../widgets => guis/Flowee}/CloseIcon.qml | 0 {desktop/widgets => guis/Flowee}/ComboBox.qml | 0 {desktop/widgets => guis/Flowee}/Dialog.qml | 0 .../Flowee}/DialogButtonBox.qml | 0 .../Flowee}/FiatValueField.qml | 0 {desktop/widgets => guis/Flowee}/GroupBox.qml | 0 .../Flowee}/LabelWithClipboard.qml | 0 .../Flowee}/ListViewKeyHandler.qml | 0 .../Flowee}/MoneyValueField.qml | 0 .../Flowee}/MultilineTextField.qml | 0 .../widgets => guis/Flowee}/PasswdDialog.qml | 0 .../widgets => guis/Flowee}/ScrollThumb.qml | 0 {desktop/widgets => guis/Flowee}/TabBar.qml | 0 .../widgets => guis/Flowee}/TextField.qml | 0 .../widgets => guis/Flowee}/WarningLabel.qml | 0 guis/desktop.qrc | 45 ++++++++++++ .../desktop}/AccountConfigMenu.qml | 0 {desktop => guis/desktop}/AccountDetails.qml | 2 +- {desktop => guis/desktop}/AccountListItem.qml | 2 +- .../desktop}/CardTypeSelector.qml | 0 {desktop => guis/desktop}/ConfigItem.qml | 0 {desktop => guis/desktop}/NetView.qml | 2 +- .../desktop}/NewAccountCreateBasicAccount.qml | 2 +- .../desktop}/NewAccountCreateHDAccount.qml | 2 +- .../desktop}/NewAccountImportAccount.qml | 2 +- {desktop => guis/desktop}/NewAccountPane.qml | 2 +- .../desktop}/PaymentTweakingPanel.qml | 2 +- .../desktop}/ReceiveTransactionPane.qml | 2 +- .../desktop}/SendTransactionPane.qml | 4 +- {desktop => guis/desktop}/SettingsPane.qml | 4 +- .../desktop}/WalletEncryption.qml | 4 +- .../desktop}/WalletEncryptionStatus.qml | 0 .../desktop}/WalletTransaction.qml | 2 +- .../desktop}/WalletTransactionDetails.qml | 2 +- {desktop => guis/desktop}/defaults.ini | 0 .../hicolor/16x16/apps/org.flowee.pay.png | Bin .../hicolor/22x22/apps/org.flowee.pay.png | Bin .../hicolor/24x24/apps/org.flowee.pay.png | Bin .../hicolor/256x256/apps/org.flowee.pay.png | Bin .../hicolor/32x32/apps/org.flowee.pay.png | Bin .../hicolor/48x48/apps/org.flowee.pay.png | Bin .../desktop}/images/activityIcon-light.png | Bin .../desktop}/images/activityIcon.png | Bin .../desktop}/images/edit-copy.svg | 0 .../desktop}/images/edit-delete.svg | 0 .../desktop}/images/eye-closed-light.png | Bin .../desktop}/images/eye-closed.png | Bin .../desktop}/images/eye-open-light.png | Bin {desktop => guis/desktop}/images/eye-open.png | Bin .../desktop}/images/lock-dark.svg | 0 .../desktop}/images/lock-light.svg | 0 .../desktop}/images/receiveIcon.png | Bin .../desktop}/images/sendIcon-light.png | Bin {desktop => guis/desktop}/images/sendIcon.png | Bin .../desktop}/images/settingsIcon-light.png | Bin .../desktop}/images/settingsIcon.png | Bin {desktop => guis/desktop}/main.qml | 8 +-- .../desktop}/org.flowee.pay.desktop | 0 {images => guis/images}/FloweePay-light.svg | 0 {images => guis/images}/FloweePay.png | Bin {images => guis/images}/FloweePay.svg | 0 {images => guis/images}/cashfusion.svg | 0 {images => guis/images}/emblem-warning.svg | 0 guis/mobile.qrc | 8 +++ {mobile => guis/mobile}/defaults.ini | 0 {mobile => guis/mobile}/main.qml | 29 +++++--- guis/widgets.qrc | 27 +++++++ main.cpp | 8 ++- mobile/qml.qrc | 8 --- 79 files changed, 146 insertions(+), 125 deletions(-) delete mode 100644 desktop/qml.qrc rename {desktop => guis}/ControlColors.js (100%) rename {desktop/widgets => guis/Flowee}/ArrowPoint.qml (100%) rename {desktop/widgets => guis/Flowee}/BitcoinAmountLabel.qml (100%) rename {desktop/widgets => guis/Flowee}/BitcoinValueField.qml (100%) rename {desktop/widgets => guis/Flowee}/Button.qml (100%) rename {desktop/widgets => guis/Flowee}/CashFusionIcon.qml (100%) rename {desktop/widgets => guis/Flowee}/CheckBox.qml (100%) rename {desktop/widgets => guis/Flowee}/CheckBoxLabel.qml (100%) rename {desktop/widgets => guis/Flowee}/CloseIcon.qml (100%) rename {desktop/widgets => guis/Flowee}/ComboBox.qml (100%) rename {desktop/widgets => guis/Flowee}/Dialog.qml (100%) rename {desktop/widgets => guis/Flowee}/DialogButtonBox.qml (100%) rename {desktop/widgets => guis/Flowee}/FiatValueField.qml (100%) rename {desktop/widgets => guis/Flowee}/GroupBox.qml (100%) rename {desktop/widgets => guis/Flowee}/LabelWithClipboard.qml (100%) rename {desktop/widgets => guis/Flowee}/ListViewKeyHandler.qml (100%) rename {desktop/widgets => guis/Flowee}/MoneyValueField.qml (100%) rename {desktop/widgets => guis/Flowee}/MultilineTextField.qml (100%) rename {desktop/widgets => guis/Flowee}/PasswdDialog.qml (100%) rename {desktop/widgets => guis/Flowee}/ScrollThumb.qml (100%) rename {desktop/widgets => guis/Flowee}/TabBar.qml (100%) rename {desktop/widgets => guis/Flowee}/TextField.qml (100%) rename {desktop/widgets => guis/Flowee}/WarningLabel.qml (100%) create mode 100644 guis/desktop.qrc rename {desktop => guis/desktop}/AccountConfigMenu.qml (100%) rename {desktop => guis/desktop}/AccountDetails.qml (99%) rename {desktop => guis/desktop}/AccountListItem.qml (99%) rename {desktop => guis/desktop}/CardTypeSelector.qml (100%) rename {desktop => guis/desktop}/ConfigItem.qml (100%) rename {desktop => guis/desktop}/NetView.qml (99%) rename {desktop => guis/desktop}/NewAccountCreateBasicAccount.qml (98%) rename {desktop => guis/desktop}/NewAccountCreateHDAccount.qml (98%) rename {desktop => guis/desktop}/NewAccountImportAccount.qml (99%) rename {desktop => guis/desktop}/NewAccountPane.qml (99%) rename {desktop => guis/desktop}/PaymentTweakingPanel.qml (99%) rename {desktop => guis/desktop}/ReceiveTransactionPane.qml (99%) rename {desktop => guis/desktop}/SendTransactionPane.qml (99%) rename {desktop => guis/desktop}/SettingsPane.qml (98%) rename {desktop => guis/desktop}/WalletEncryption.qml (99%) rename {desktop => guis/desktop}/WalletEncryptionStatus.qml (100%) rename {desktop => guis/desktop}/WalletTransaction.qml (99%) rename {desktop => guis/desktop}/WalletTransactionDetails.qml (99%) rename {desktop => guis/desktop}/defaults.ini (100%) rename {desktop => guis/desktop}/icons/hicolor/16x16/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/icons/hicolor/22x22/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/icons/hicolor/24x24/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/icons/hicolor/256x256/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/icons/hicolor/32x32/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/icons/hicolor/48x48/apps/org.flowee.pay.png (100%) rename {desktop => guis/desktop}/images/activityIcon-light.png (100%) rename {desktop => guis/desktop}/images/activityIcon.png (100%) rename {desktop => guis/desktop}/images/edit-copy.svg (100%) rename {desktop => guis/desktop}/images/edit-delete.svg (100%) rename {desktop => guis/desktop}/images/eye-closed-light.png (100%) rename {desktop => guis/desktop}/images/eye-closed.png (100%) rename {desktop => guis/desktop}/images/eye-open-light.png (100%) rename {desktop => guis/desktop}/images/eye-open.png (100%) rename {desktop => guis/desktop}/images/lock-dark.svg (100%) rename {desktop => guis/desktop}/images/lock-light.svg (100%) rename {desktop => guis/desktop}/images/receiveIcon.png (100%) rename {desktop => guis/desktop}/images/sendIcon-light.png (100%) rename {desktop => guis/desktop}/images/sendIcon.png (100%) rename {desktop => guis/desktop}/images/settingsIcon-light.png (100%) rename {desktop => guis/desktop}/images/settingsIcon.png (100%) rename {desktop => guis/desktop}/main.qml (99%) rename {desktop => guis/desktop}/org.flowee.pay.desktop (100%) rename {images => guis/images}/FloweePay-light.svg (100%) rename {images => guis/images}/FloweePay.png (100%) rename {images => guis/images}/FloweePay.svg (100%) rename {images => guis/images}/cashfusion.svg (100%) rename {images => guis/images}/emblem-warning.svg (100%) create mode 100644 guis/mobile.qrc rename {mobile => guis/mobile}/defaults.ini (100%) rename {mobile => guis/mobile}/main.qml (70%) create mode 100644 guis/widgets.qrc delete mode 100644 mobile/qml.qrc diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d5f3b6..3a29ab9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,9 +121,10 @@ if(NOT ANDROID) qt6_add_translation(qmFiles ${TS_FILES}) add_custom_target(i18n - COMMAND lupdate desktop/qml.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts - COMMAND lupdate ${PAY_SOURCES} -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts - COMMAND lupdate mobile/qml.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts + COMMAND lupdate guis/desktop.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts + COMMAND lupdate ${PAY_SOURCES} guis/widgets.qrc + -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts + COMMAND lupdate guis/mobile.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" @@ -148,13 +149,14 @@ if(BUILD_DESKTOP_PAY) # copied if it doesn't exist in the destination or if it is modified. configure_file(translations/desktop-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) if (local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/desktop) + set (QML_PATH ${CMAKE_SOURCE_DIR}/guis/) endif() - configure_file(qml_path_helper.cpp.in desktop/qml_path_helper.cpp) + configure_file(qml_path_helper.cpp.in guis/desktop/qml_path_helper.cpp) add_executable(pay main.cpp - desktop/qml_path_helper.cpp - desktop/qml.qrc + guis/desktop/qml_path_helper.cpp + guis/desktop.qrc + guis/widgets.qrc desktop-i18n.qrc ) set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") @@ -182,7 +184,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) ${CMAKE_SOURCE_DIR}/data/bip39-spanish DESTINATION ${ASSETS_DIR}) - configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) + configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") message ("pay_mobile: Found pre-compiled translations") set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) @@ -191,8 +193,9 @@ if (ANDROID AND BUILD_MOBILE_PAY) qt6_add_executable(pay_mobile main.cpp - mobile/qml_path_helper.cpp - mobile/qml.qrc + guis/mobile/qml_path_helper.cpp + guis/mobile.qrc + guis/widgets.qrc ${MOBILE_PAY_I18N_QRC} ) target_link_libraries(pay_mobile pay_lib Qt6::Svg) @@ -207,15 +210,16 @@ endif () if(NOT ANDROID AND BUILD_MOBILE_PAY) if(local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/mobile) + set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) endif() - configure_file(qml_path_helper.cpp.in mobile/qml_path_helper.cpp) + configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) add_executable(pay_mobile main.cpp - mobile/qml_path_helper.cpp - mobile/qml.qrc + guis/mobile/qml_path_helper.cpp + guis/mobile.qrc + guis/widgets.qrc mobile-i18n.qrc ) set_target_properties(pay_mobile PROPERTIES @@ -245,8 +249,8 @@ if (BUILD_PAY_TOOLS) install(TARGETS blockheaders-meta-extractor DESTINATION bin) endif() -install(PROGRAMS desktop/org.flowee.pay.desktop DESTINATION share/applications) -set (ICONIN desktop/icons/hicolor/) +install(PROGRAMS guis/desktop/org.flowee.pay.desktop DESTINATION share/applications) +set (ICONIN guis/desktop/icons/hicolor/) set (ICONOUT share/icons/hicolor/) install(FILES ${ICONIN}16x16/apps/org.flowee.pay.png DESTINATION ${ICONOUT}16x16/apps) install(FILES ${ICONIN}22x22/apps/org.flowee.pay.png DESTINATION ${ICONOUT}22x22/apps) diff --git a/desktop/qml.qrc b/desktop/qml.qrc deleted file mode 100644 index 5fb62fb..0000000 --- a/desktop/qml.qrc +++ /dev/null @@ -1,68 +0,0 @@ - - - ../images/FloweePay.png - ../images/FloweePay.svg - ../images/FloweePay-light.svg - ../images/cashfusion.svg - ../images/emblem-warning.svg - images/sendIcon.png - images/sendIcon-light.png - images/receiveIcon.png - images/settingsIcon.png - images/settingsIcon-light.png - images/activityIcon.png - images/activityIcon-light.png - images/eye-closed-light.png - images/eye-closed.png - images/eye-open-light.png - images/eye-open.png - images/edit-delete.svg - images/edit-copy.svg - images/lock-light.svg - images/lock-dark.svg - defaults.ini - ConfigItem.qml - ControlColors.js - main.qml - NetView.qml - ReceiveTransactionPane.qml - SendTransactionPane.qml - WalletTransactionDetails.qml - WalletTransaction.qml - AccountListItem.qml - AccountDetails.qml - SettingsPane.qml - NewAccountPane.qml - CardTypeSelector.qml - NewAccountCreateBasicAccount.qml - NewAccountImportAccount.qml - NewAccountCreateHDAccount.qml - WalletEncryption.qml - widgets/ArrowPoint.qml - widgets/BitcoinAmountLabel.qml - widgets/Button.qml - widgets/CheckBox.qml - widgets/CheckBoxLabel.qml - widgets/CloseIcon.qml - widgets/GroupBox.qml - widgets/LabelWithClipboard.qml - widgets/ListViewKeyHandler.qml - widgets/MultilineTextField.qml - widgets/ScrollThumb.qml - widgets/TabBar.qml - widgets/TextField.qml - widgets/ComboBox.qml - widgets/MoneyValueField.qml - widgets/FiatValueField.qml - widgets/BitcoinValueField.qml - widgets/Dialog.qml - widgets/DialogButtonBox.qml - widgets/CashFusionIcon.qml - PaymentTweakingPanel.qml - widgets/DialogButtonBox.qml - widgets/WarningLabel.qml - widgets/PasswdDialog.qml - WalletEncryptionStatus.qml - AccountConfigMenu.qml - - diff --git a/desktop/ControlColors.js b/guis/ControlColors.js similarity index 100% rename from desktop/ControlColors.js rename to guis/ControlColors.js diff --git a/desktop/widgets/ArrowPoint.qml b/guis/Flowee/ArrowPoint.qml similarity index 100% rename from desktop/widgets/ArrowPoint.qml rename to guis/Flowee/ArrowPoint.qml diff --git a/desktop/widgets/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml similarity index 100% rename from desktop/widgets/BitcoinAmountLabel.qml rename to guis/Flowee/BitcoinAmountLabel.qml diff --git a/desktop/widgets/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml similarity index 100% rename from desktop/widgets/BitcoinValueField.qml rename to guis/Flowee/BitcoinValueField.qml diff --git a/desktop/widgets/Button.qml b/guis/Flowee/Button.qml similarity index 100% rename from desktop/widgets/Button.qml rename to guis/Flowee/Button.qml diff --git a/desktop/widgets/CashFusionIcon.qml b/guis/Flowee/CashFusionIcon.qml similarity index 100% rename from desktop/widgets/CashFusionIcon.qml rename to guis/Flowee/CashFusionIcon.qml diff --git a/desktop/widgets/CheckBox.qml b/guis/Flowee/CheckBox.qml similarity index 100% rename from desktop/widgets/CheckBox.qml rename to guis/Flowee/CheckBox.qml diff --git a/desktop/widgets/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml similarity index 100% rename from desktop/widgets/CheckBoxLabel.qml rename to guis/Flowee/CheckBoxLabel.qml diff --git a/desktop/widgets/CloseIcon.qml b/guis/Flowee/CloseIcon.qml similarity index 100% rename from desktop/widgets/CloseIcon.qml rename to guis/Flowee/CloseIcon.qml diff --git a/desktop/widgets/ComboBox.qml b/guis/Flowee/ComboBox.qml similarity index 100% rename from desktop/widgets/ComboBox.qml rename to guis/Flowee/ComboBox.qml diff --git a/desktop/widgets/Dialog.qml b/guis/Flowee/Dialog.qml similarity index 100% rename from desktop/widgets/Dialog.qml rename to guis/Flowee/Dialog.qml diff --git a/desktop/widgets/DialogButtonBox.qml b/guis/Flowee/DialogButtonBox.qml similarity index 100% rename from desktop/widgets/DialogButtonBox.qml rename to guis/Flowee/DialogButtonBox.qml diff --git a/desktop/widgets/FiatValueField.qml b/guis/Flowee/FiatValueField.qml similarity index 100% rename from desktop/widgets/FiatValueField.qml rename to guis/Flowee/FiatValueField.qml diff --git a/desktop/widgets/GroupBox.qml b/guis/Flowee/GroupBox.qml similarity index 100% rename from desktop/widgets/GroupBox.qml rename to guis/Flowee/GroupBox.qml diff --git a/desktop/widgets/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml similarity index 100% rename from desktop/widgets/LabelWithClipboard.qml rename to guis/Flowee/LabelWithClipboard.qml diff --git a/desktop/widgets/ListViewKeyHandler.qml b/guis/Flowee/ListViewKeyHandler.qml similarity index 100% rename from desktop/widgets/ListViewKeyHandler.qml rename to guis/Flowee/ListViewKeyHandler.qml diff --git a/desktop/widgets/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml similarity index 100% rename from desktop/widgets/MoneyValueField.qml rename to guis/Flowee/MoneyValueField.qml diff --git a/desktop/widgets/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml similarity index 100% rename from desktop/widgets/MultilineTextField.qml rename to guis/Flowee/MultilineTextField.qml diff --git a/desktop/widgets/PasswdDialog.qml b/guis/Flowee/PasswdDialog.qml similarity index 100% rename from desktop/widgets/PasswdDialog.qml rename to guis/Flowee/PasswdDialog.qml diff --git a/desktop/widgets/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml similarity index 100% rename from desktop/widgets/ScrollThumb.qml rename to guis/Flowee/ScrollThumb.qml diff --git a/desktop/widgets/TabBar.qml b/guis/Flowee/TabBar.qml similarity index 100% rename from desktop/widgets/TabBar.qml rename to guis/Flowee/TabBar.qml diff --git a/desktop/widgets/TextField.qml b/guis/Flowee/TextField.qml similarity index 100% rename from desktop/widgets/TextField.qml rename to guis/Flowee/TextField.qml diff --git a/desktop/widgets/WarningLabel.qml b/guis/Flowee/WarningLabel.qml similarity index 100% rename from desktop/widgets/WarningLabel.qml rename to guis/Flowee/WarningLabel.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc new file mode 100644 index 0000000..e4d55d0 --- /dev/null +++ b/guis/desktop.qrc @@ -0,0 +1,45 @@ + + + images/FloweePay.png + images/FloweePay.svg + images/FloweePay-light.svg + images/cashfusion.svg + images/emblem-warning.svg + desktop/images/sendIcon.png + desktop/images/sendIcon-light.png + desktop/images/receiveIcon.png + desktop/images/settingsIcon.png + desktop/images/settingsIcon-light.png + desktop/images/activityIcon.png + desktop/images/activityIcon-light.png + desktop/images/eye-closed-light.png + desktop/images/eye-closed.png + desktop/images/eye-open-light.png + desktop/images/eye-open.png + desktop/images/edit-delete.svg + desktop/images/edit-copy.svg + desktop/images/lock-light.svg + desktop/images/lock-dark.svg + desktop/defaults.ini + desktop/ConfigItem.qml + ControlColors.js + desktop/main.qml + desktop/NetView.qml + desktop/ReceiveTransactionPane.qml + desktop/SendTransactionPane.qml + desktop/WalletTransactionDetails.qml + desktop/WalletTransaction.qml + desktop/AccountListItem.qml + desktop/AccountDetails.qml + desktop/SettingsPane.qml + desktop/NewAccountPane.qml + desktop/CardTypeSelector.qml + desktop/NewAccountCreateBasicAccount.qml + desktop/NewAccountImportAccount.qml + desktop/NewAccountCreateHDAccount.qml + desktop/WalletEncryption.qml + desktop/PaymentTweakingPanel.qml + desktop/WalletEncryptionStatus.qml + desktop/AccountConfigMenu.qml + + diff --git a/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml similarity index 100% rename from desktop/AccountConfigMenu.qml rename to guis/desktop/AccountConfigMenu.qml diff --git a/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml similarity index 99% rename from desktop/AccountDetails.qml rename to guis/desktop/AccountDetails.qml index af8cac4..1d9afce 100644 --- a/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee Item { id: root diff --git a/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml similarity index 99% rename from desktop/AccountListItem.qml rename to guis/desktop/AccountListItem.qml index 888c044..7f765c1 100644 --- a/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee Item { id: root diff --git a/desktop/CardTypeSelector.qml b/guis/desktop/CardTypeSelector.qml similarity index 100% rename from desktop/CardTypeSelector.qml rename to guis/desktop/CardTypeSelector.qml diff --git a/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml similarity index 100% rename from desktop/ConfigItem.qml rename to guis/desktop/ConfigItem.qml diff --git a/desktop/NetView.qml b/guis/desktop/NetView.qml similarity index 99% rename from desktop/NetView.qml rename to guis/desktop/NetView.qml index 19eb5c2..9b83899 100644 --- a/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee ApplicationWindow { id: root diff --git a/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml similarity index 98% rename from desktop/NewAccountCreateBasicAccount.qml rename to guis/desktop/NewAccountCreateBasicAccount.qml index 69c6dee..edfe9a8 100644 --- a/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee ColumnLayout { spacing: 10 diff --git a/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml similarity index 98% rename from desktop/NewAccountCreateHDAccount.qml rename to guis/desktop/NewAccountCreateHDAccount.qml index fe15924..e6aeb16 100644 --- a/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee ColumnLayout { id: newAccountCreateHDAccount diff --git a/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml similarity index 99% rename from desktop/NewAccountImportAccount.qml rename to guis/desktop/NewAccountImportAccount.qml index d53b8fb..54b9a8c 100644 --- a/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee import Flowee.org.pay 1.0 GridLayout { diff --git a/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml similarity index 99% rename from desktop/NewAccountPane.qml rename to guis/desktop/NewAccountPane.qml index a61d5d1..5624a63 100644 --- a/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -17,7 +17,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee FocusScope { id: root diff --git a/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml similarity index 99% rename from desktop/PaymentTweakingPanel.qml rename to guis/desktop/PaymentTweakingPanel.qml index 25bf25c..f86d54f 100644 --- a/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -17,7 +17,7 @@ */ import QtQuick 2.11 import QtQuick.Controls 2.11 -import "widgets" as Flowee +import "../Flowee" as Flowee Item { id: root diff --git a/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml similarity index 99% rename from desktop/ReceiveTransactionPane.qml rename to guis/desktop/ReceiveTransactionPane.qml index 18f5ae7..c99229f 100644 --- a/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -20,7 +20,7 @@ import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 import QtQuick.Shapes 1.11 // for shape-path import Flowee.org.pay 1.0 -import "widgets" as Flowee +import "../Flowee" as Flowee Pane { id: receivePane diff --git a/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml similarity index 99% rename from desktop/SendTransactionPane.qml rename to guis/desktop/SendTransactionPane.qml index f5c368c..305f936 100644 --- a/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -20,8 +20,8 @@ import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 import QtQuick.Window 2.11 import QtQuick.Shapes 1.11 // for shape-path -import "widgets" as Flowee -import "./ControlColors.js" as ControlColors +import "../Flowee" as Flowee +import "../ControlColors.js" as ControlColors import Flowee.org.pay 1.0 Item { diff --git a/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml similarity index 98% rename from desktop/SettingsPane.qml rename to guis/desktop/SettingsPane.qml index fc35f5e..de920f1 100644 --- a/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -19,8 +19,8 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "./ControlColors.js" as ControlColors -import "widgets" as Flowee +import "../ControlColors.js" as ControlColors +import "../Flowee" as Flowee Pane { property string title: qsTr("Settings") diff --git a/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml similarity index 99% rename from desktop/WalletEncryption.qml rename to guis/desktop/WalletEncryption.qml index 383063f..84ec712 100644 --- a/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -18,8 +18,8 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee -import "ControlColors.js" as ControlColors +import "../Flowee" as Flowee +import "../ControlColors.js" as ControlColors FocusScope { id: root diff --git a/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml similarity index 100% rename from desktop/WalletEncryptionStatus.qml rename to guis/desktop/WalletEncryptionStatus.qml diff --git a/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml similarity index 99% rename from desktop/WalletTransaction.qml rename to guis/desktop/WalletTransaction.qml index 8998450..f4d61b9 100644 --- a/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -17,7 +17,7 @@ */ import QtQuick 2.11 import QtQuick.Controls 2.11 -import "widgets" as Flowee +import "../Flowee" as Flowee Item { id: txRoot diff --git a/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml similarity index 99% rename from desktop/WalletTransactionDetails.qml rename to guis/desktop/WalletTransactionDetails.qml index 0c7875f..43d5332 100644 --- a/desktop/WalletTransactionDetails.qml +++ b/guis/desktop/WalletTransactionDetails.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 import QtQuick.Layouts 1.11 -import "widgets" as Flowee +import "../Flowee" as Flowee GridLayout { id: root diff --git a/desktop/defaults.ini b/guis/desktop/defaults.ini similarity index 100% rename from desktop/defaults.ini rename to guis/desktop/defaults.ini diff --git a/desktop/icons/hicolor/16x16/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/16x16/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/16x16/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/16x16/apps/org.flowee.pay.png diff --git a/desktop/icons/hicolor/22x22/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/22x22/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/22x22/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/22x22/apps/org.flowee.pay.png diff --git a/desktop/icons/hicolor/24x24/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/24x24/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/24x24/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/24x24/apps/org.flowee.pay.png diff --git a/desktop/icons/hicolor/256x256/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/256x256/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/256x256/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/256x256/apps/org.flowee.pay.png diff --git a/desktop/icons/hicolor/32x32/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/32x32/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/32x32/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/32x32/apps/org.flowee.pay.png diff --git a/desktop/icons/hicolor/48x48/apps/org.flowee.pay.png b/guis/desktop/icons/hicolor/48x48/apps/org.flowee.pay.png similarity index 100% rename from desktop/icons/hicolor/48x48/apps/org.flowee.pay.png rename to guis/desktop/icons/hicolor/48x48/apps/org.flowee.pay.png diff --git a/desktop/images/activityIcon-light.png b/guis/desktop/images/activityIcon-light.png similarity index 100% rename from desktop/images/activityIcon-light.png rename to guis/desktop/images/activityIcon-light.png diff --git a/desktop/images/activityIcon.png b/guis/desktop/images/activityIcon.png similarity index 100% rename from desktop/images/activityIcon.png rename to guis/desktop/images/activityIcon.png diff --git a/desktop/images/edit-copy.svg b/guis/desktop/images/edit-copy.svg similarity index 100% rename from desktop/images/edit-copy.svg rename to guis/desktop/images/edit-copy.svg diff --git a/desktop/images/edit-delete.svg b/guis/desktop/images/edit-delete.svg similarity index 100% rename from desktop/images/edit-delete.svg rename to guis/desktop/images/edit-delete.svg diff --git a/desktop/images/eye-closed-light.png b/guis/desktop/images/eye-closed-light.png similarity index 100% rename from desktop/images/eye-closed-light.png rename to guis/desktop/images/eye-closed-light.png diff --git a/desktop/images/eye-closed.png b/guis/desktop/images/eye-closed.png similarity index 100% rename from desktop/images/eye-closed.png rename to guis/desktop/images/eye-closed.png diff --git a/desktop/images/eye-open-light.png b/guis/desktop/images/eye-open-light.png similarity index 100% rename from desktop/images/eye-open-light.png rename to guis/desktop/images/eye-open-light.png diff --git a/desktop/images/eye-open.png b/guis/desktop/images/eye-open.png similarity index 100% rename from desktop/images/eye-open.png rename to guis/desktop/images/eye-open.png diff --git a/desktop/images/lock-dark.svg b/guis/desktop/images/lock-dark.svg similarity index 100% rename from desktop/images/lock-dark.svg rename to guis/desktop/images/lock-dark.svg diff --git a/desktop/images/lock-light.svg b/guis/desktop/images/lock-light.svg similarity index 100% rename from desktop/images/lock-light.svg rename to guis/desktop/images/lock-light.svg diff --git a/desktop/images/receiveIcon.png b/guis/desktop/images/receiveIcon.png similarity index 100% rename from desktop/images/receiveIcon.png rename to guis/desktop/images/receiveIcon.png diff --git a/desktop/images/sendIcon-light.png b/guis/desktop/images/sendIcon-light.png similarity index 100% rename from desktop/images/sendIcon-light.png rename to guis/desktop/images/sendIcon-light.png diff --git a/desktop/images/sendIcon.png b/guis/desktop/images/sendIcon.png similarity index 100% rename from desktop/images/sendIcon.png rename to guis/desktop/images/sendIcon.png diff --git a/desktop/images/settingsIcon-light.png b/guis/desktop/images/settingsIcon-light.png similarity index 100% rename from desktop/images/settingsIcon-light.png rename to guis/desktop/images/settingsIcon-light.png diff --git a/desktop/images/settingsIcon.png b/guis/desktop/images/settingsIcon.png similarity index 100% rename from desktop/images/settingsIcon.png rename to guis/desktop/images/settingsIcon.png diff --git a/desktop/main.qml b/guis/desktop/main.qml similarity index 99% rename from desktop/main.qml rename to guis/desktop/main.qml index 39f6554..28545c1 100644 --- a/desktop/main.qml +++ b/guis/desktop/main.qml @@ -15,11 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls import QtQuick.Layouts 1.11 -import "widgets" as Flowee -import "./ControlColors.js" as ControlColors +import "../Flowee" as Flowee +import "../ControlColors.js" as ControlColors ApplicationWindow { id: mainWindow diff --git a/desktop/org.flowee.pay.desktop b/guis/desktop/org.flowee.pay.desktop similarity index 100% rename from desktop/org.flowee.pay.desktop rename to guis/desktop/org.flowee.pay.desktop diff --git a/images/FloweePay-light.svg b/guis/images/FloweePay-light.svg similarity index 100% rename from images/FloweePay-light.svg rename to guis/images/FloweePay-light.svg diff --git a/images/FloweePay.png b/guis/images/FloweePay.png similarity index 100% rename from images/FloweePay.png rename to guis/images/FloweePay.png diff --git a/images/FloweePay.svg b/guis/images/FloweePay.svg similarity index 100% rename from images/FloweePay.svg rename to guis/images/FloweePay.svg diff --git a/images/cashfusion.svg b/guis/images/cashfusion.svg similarity index 100% rename from images/cashfusion.svg rename to guis/images/cashfusion.svg diff --git a/images/emblem-warning.svg b/guis/images/emblem-warning.svg similarity index 100% rename from images/emblem-warning.svg rename to guis/images/emblem-warning.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc new file mode 100644 index 0000000..13c55d8 --- /dev/null +++ b/guis/mobile.qrc @@ -0,0 +1,8 @@ + + + images/FloweePay-light.svg + mobile/main.qml + mobile/defaults.ini + ControlColors.js + + diff --git a/mobile/defaults.ini b/guis/mobile/defaults.ini similarity index 100% rename from mobile/defaults.ini rename to guis/mobile/defaults.ini diff --git a/mobile/main.qml b/guis/mobile/main.qml similarity index 70% rename from mobile/main.qml rename to guis/mobile/main.qml index 09408a9..051ec2f 100644 --- a/mobile/main.qml +++ b/guis/mobile/main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "../ControlColors.js" as ControlColors ApplicationWindow { id: mainWindow @@ -25,24 +26,30 @@ ApplicationWindow { width: 360 height: 720 visible: true + onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow) property bool isLoading: typeof portfolio === "undefined"; + onIsLoadingChanged: { + if (!isLoading) { + } + } property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" - Loader { - id: content - anchors.fill: parent - source: mainWindow.isLoading ? "" : "PeersOverview.qml" + Item { + id: header + } - Text { - color: "white" - text: "Loading" - anchors.centerIn: parent - font.pointSize: 40 - visible: mainWindow.isLoading + StackView { + id: stackView + anchors.left: parent.left + anchors.right: parent.right + anchors.top: header.bottom + anchors.bottom: parent.bottom } + + } diff --git a/guis/widgets.qrc b/guis/widgets.qrc new file mode 100644 index 0000000..5485d36 --- /dev/null +++ b/guis/widgets.qrc @@ -0,0 +1,27 @@ + + + Flowee/ArrowPoint.qml + Flowee/BitcoinAmountLabel.qml + Flowee/Button.qml + Flowee/CheckBox.qml + Flowee/CheckBoxLabel.qml + Flowee/CloseIcon.qml + Flowee/GroupBox.qml + Flowee/LabelWithClipboard.qml + Flowee/ListViewKeyHandler.qml + Flowee/MultilineTextField.qml + Flowee/ScrollThumb.qml + Flowee/TabBar.qml + Flowee/TextField.qml + Flowee/ComboBox.qml + Flowee/MoneyValueField.qml + Flowee/FiatValueField.qml + Flowee/BitcoinValueField.qml + Flowee/Dialog.qml + Flowee/DialogButtonBox.qml + Flowee/CashFusionIcon.qml + Flowee/DialogButtonBox.qml + Flowee/WarningLabel.qml + Flowee/PasswdDialog.qml + + diff --git a/main.cpp b/main.cpp index 8bfc3ee..6a12fdd 100644 --- a/main.cpp +++ b/main.cpp @@ -125,7 +125,13 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); handleLocalQml(engine); - engine.load(engine.baseUrl().url() + "/main.qml"); + engine.load(engine.baseUrl().url() + +#ifdef DESKTOP + "/desktop" +#elif MOBILE + "/mobile" +#endif + "/main.qml"); // make sure that FloweePay::instance() is not called above this line! // since doing so will start initialization of the p2p stuff in a separate thread. diff --git a/mobile/qml.qrc b/mobile/qml.qrc deleted file mode 100644 index 2ae30d0..0000000 --- a/mobile/qml.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - ../images/FloweePay-light.svg - main.qml - PeersOverview.qml - defaults.ini - - -- 2.54.0 From cce9237d82c2b989619ad3dcf38267e3362e92de Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 10:41:11 +0100 Subject: [PATCH 0052/1428] Use namespaces This works around an inconsistency in the QML language. Inside a file variables are preferred local, hiding the global ones that may have the same name. The same is not true for named QML files (classes). An imported is used before one of the same name in the same directory. To avoid future additions to Qt Controls breaking our code, and to generally avoid surprises with common names, lets standardize using namespacing for Qt-Quick-Controls classes. --- guis/Flowee/BitcoinAmountLabel.qml | 14 +++++++------- guis/Flowee/BitcoinValueField.qml | 4 ++-- guis/Flowee/CashFusionIcon.qml | 4 ++-- guis/Flowee/CheckBox.qml | 8 ++++---- guis/Flowee/CheckBoxLabel.qml | 6 +++--- guis/Flowee/CloseIcon.qml | 2 -- guis/Flowee/Dialog.qml | 10 +++++----- guis/Flowee/FiatValueField.qml | 4 ++-- guis/Flowee/GroupBox.qml | 4 ++-- guis/Flowee/LabelWithClipboard.qml | 8 ++++---- guis/Flowee/MoneyValueField.qml | 6 +++--- guis/Flowee/MultilineTextField.qml | 4 ++-- guis/Flowee/ScrollThumb.qml | 4 ++-- guis/Flowee/TabBar.qml | 4 ++-- guis/Flowee/WarningLabel.qml | 4 ++-- 15 files changed, 42 insertions(+), 44 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index c3ca7fb..c2b1c1a 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -16,14 +16,14 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import QtQuick.Layouts 1.11 /** * This class displays a Bitcoin value using the current settings * and renders it smartly to avoid it just being a long list of digits. */ -Control { +QQC2.Control { id: root property double value: 5E8 property bool colorize: true @@ -66,7 +66,7 @@ Control { amountString = Pay.amountToString(sats) } - Label { + QQC2.Label { id: main text: { var s = row.amountString @@ -90,7 +90,7 @@ Control { Layout.alignment: Qt.AlignBaseline } - Label { + QQC2.Label { text: { var s = row.amountString var pos = s.length - 5 @@ -102,7 +102,7 @@ Control { Layout.alignment: Qt.AlignBaseline visible: Pay.unitAllowedDecimals === 8 } - Label { + QQC2.Label { id: satsLabel text: { var s = row.amountString @@ -115,14 +115,14 @@ Control { visible: Pay.unitAllowedDecimals >= 2 } - Label { + QQC2.Label { text: Pay.unitName color: main.color visible: root.includeUnit Layout.alignment: Qt.AlignBaseline } - Label { + QQC2.Label { visible: root.showFiat Layout.alignment: Qt.AlignBaseline text: { diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index b3b537b..289a19c 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import Flowee.org.pay 1.0 /* @@ -42,7 +42,7 @@ MoneyValueField { showFiat: false } - Label { + QQC2.Label { id: unit text: Pay.unitName y: 6 diff --git a/guis/Flowee/CashFusionIcon.qml b/guis/Flowee/CashFusionIcon.qml index 293d1b2..7a27f4b 100644 --- a/guis/Flowee/CashFusionIcon.qml +++ b/guis/Flowee/CashFusionIcon.qml @@ -1,5 +1,5 @@ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 Image { id: fusedIcon @@ -7,7 +7,7 @@ Image { source: "qrc:/cashfusion.svg" width: 24 height: 24 - ToolTip { + QQC2.ToolTip { delay: 600 text: qsTr("Coin has been fused for increased anonymity") visible: mouseArea.inside diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 2587202..2f7df2a 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 Item { id: root @@ -75,14 +75,14 @@ Item { } hoverEnabled: true - ToolTip { + QQC2.ToolTip { parent: root text: root.tooltipText delay: 1500 visible: mousy.containsMouse && root.tooltipText !== "" } } - Label { + QQC2.Label { id: title anchors.left: slider.right anchors.verticalCenter: parent.verticalCenter @@ -100,7 +100,7 @@ Item { anchors.leftMargin: 16 radius: width color: q.palette.text - Label { + QQC2.Label { id: q text: "?" color: palette.base diff --git a/guis/Flowee/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml index 0654b11..85d2e5b 100644 --- a/guis/Flowee/CheckBoxLabel.qml +++ b/guis/Flowee/CheckBoxLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 /** * This is a buddy that goes with a CheckBox component for when @@ -25,12 +25,12 @@ import QtQuick.Controls 2.11 * a text value, it will then become a clickable label which triggers * the checkbox when clicked. */ -Label { +QQC2.Label { id: root property var buddy: null property string toolTipText: "" - ToolTip { + QQC2.ToolTip { delay: 600 text: root.toolTipText visible: root.toolTipText !== "" && mouseArea.containsMouse diff --git a/guis/Flowee/CloseIcon.qml b/guis/Flowee/CloseIcon.qml index 7b4b48e..632260c 100644 --- a/guis/Flowee/CloseIcon.qml +++ b/guis/Flowee/CloseIcon.qml @@ -16,8 +16,6 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 Item { id: root diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 50031d4..3265ca6 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -16,11 +16,11 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "." as Flowee; -Popup { +QQC2.Popup { id: root signal accepted @@ -35,7 +35,7 @@ Popup { } modal: true - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent onVisibleChanged: { // make sure any content gets focus on open if (visible && content.item) @@ -71,12 +71,12 @@ Popup { return max / 2 * 3; } spacing: 10 - Label { + QQC2.Label { id: titleLabel font.bold: true anchors.horizontalCenter: parent.horizontalCenter } - Label { + QQC2.Label { id: mainTextLabel // this next line will always create a binding loop. But its harmless, so ignore that comment. width: parent.width diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index e60f0bd..127e0bd 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import Flowee.org.pay 1.0 /* @@ -32,7 +32,7 @@ MoneyValueField { property double baselineOffset: balance.baselineOffset + balance.y valueObject.maxFractionalDigits: Fiat.displayCents ? 2 : 0 - Label { + QQC2.Label { id: balance x: 6 y: 6 diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index 62d7502..a8ef270 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -16,10 +16,10 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import QtQuick.Layouts 1.11 -Control { +QQC2.Control { id: root property bool collapsable: true diff --git a/guis/Flowee/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml index 798ca3f..3531693 100644 --- a/guis/Flowee/LabelWithClipboard.qml +++ b/guis/Flowee/LabelWithClipboard.qml @@ -16,9 +16,9 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 -Label { +QQC2.Label { id: root elide: Text.ElideMiddle @@ -32,7 +32,7 @@ Label { onClicked: menu.start(parent); cursorShape: Qt.PointingHandCursor - Menu { + QQC2.Menu { id: menu function start(label) { if (label.clipboardText !== "") @@ -42,7 +42,7 @@ Label { popup(); } property string text: "" - MenuItem { + QQC2.MenuItem { text: root.menuText onTriggered: Pay.copyToClipboard(menu.text) } diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index b37a5bf..85fcdcc 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import Flowee.org.pay 1.0 /* @@ -65,7 +65,7 @@ FocusScope { visible: root.activeFocus x: 8 y: 6 - Label { + QQC2.Label { id: begin text: privValue.enteredString.substring(0, privValue.cursorPos) } @@ -84,7 +84,7 @@ FocusScope { onTriggered: parent.cursorVisible = !parent.cursorVisible } } - Label { + QQC2.Label { anchors.left: begin.right text: privValue.enteredString.substring(privValue.cursorPos) } diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 1ff3c22..6f72eb2 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -15,7 +15,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 import QtQuick.Layouts 1.11 /* @@ -27,7 +27,7 @@ import QtQuick.Layouts 1.11 * * So, here we go, a basic multi line line-edit. */ -Control { +QQC2.Control { id: root property string text: "" diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index 512ed3f..7154b0f 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -16,9 +16,9 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 -ScrollBar { +QQC2.ScrollBar { id: root /// override this if the flickable is not the direct parent. diff --git a/guis/Flowee/TabBar.qml b/guis/Flowee/TabBar.qml index 16362bf..361511a 100644 --- a/guis/Flowee/TabBar.qml +++ b/guis/Flowee/TabBar.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 FocusScope { id: floweeTabBar @@ -70,7 +70,7 @@ FocusScope { width: height height: payTabButtonText.height } - Label { + QQC2.Label { id: payTabButtonText anchors.verticalCenter: parent.verticalCenter font.bold: true diff --git a/guis/Flowee/WarningLabel.qml b/guis/Flowee/WarningLabel.qml index 4860072..e0ebe92 100644 --- a/guis/Flowee/WarningLabel.qml +++ b/guis/Flowee/WarningLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick.Controls 2.11 as QQC2 Item { id: warningLabel @@ -35,7 +35,7 @@ Item { height: 32 anchors.verticalCenter: parent.verticalCenter } - Label { + QQC2.Label { id: warningText anchors.left: warningIcon.right anchors.leftMargin: 5 -- 2.54.0 From e2bea6a5afd3dfee44100425773bfec4cd726e4d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 11:38:28 +0100 Subject: [PATCH 0053/1428] Start basic mobile GUI structure On the mainscreen have some big tab-buttons for the main features then we have a menu overlay which can hold various other screens and features. Those screens will be placed on top of the main tabbed screen using a stack-view which is common on touch-interfaces. --- guis/mobile.qrc | 8 ++ guis/mobile/MainView.qml | 39 ++++++++ guis/mobile/MainViewBase.qml | 119 ++++++++++++++++++++++++ guis/mobile/MenuOverlay.qml | 92 ++++++++++++++++++ guis/mobile/Page.qml | 80 ++++++++++++++++ guis/mobile/images/back-arrow-light.svg | 16 ++++ guis/mobile/images/back-arrow.svg | 16 ++++ guis/mobile/main.qml | 15 +-- 8 files changed, 374 insertions(+), 11 deletions(-) create mode 100644 guis/mobile/MainView.qml create mode 100644 guis/mobile/MainViewBase.qml create mode 100644 guis/mobile/MenuOverlay.qml create mode 100644 guis/mobile/Page.qml create mode 100644 guis/mobile/images/back-arrow-light.svg create mode 100644 guis/mobile/images/back-arrow.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 13c55d8..9d35825 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -1,8 +1,16 @@ images/FloweePay-light.svg + images/FloweePay.svg + mobile/images/back-arrow.svg + mobile/images/back-arrow-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js + mobile/MenuOverlay.qml + mobile/NetView.qml + mobile/Page.qml + mobile/MainViewBase.qml + mobile/MainView.qml diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml new file mode 100644 index 0000000..780f0b7 --- /dev/null +++ b/guis/mobile/MainView.qml @@ -0,0 +1,39 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 2.15 + +MainViewBase { + Rectangle { + color: "blue"; + anchors.fill: parent + } + Rectangle { + color: "red"; + anchors.fill: parent + } + Rectangle { + anchors.fill: parent + color: "black"; + } + Rectangle { + anchors.fill: parent + color: "white"; + } +} diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml new file mode 100644 index 0000000..b45e208 --- /dev/null +++ b/guis/mobile/MainViewBase.qml @@ -0,0 +1,119 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 2.15 + +QQC2.Control { + id: root + width: parent.width + height: parent.height + + // This trick means any child items the FloweeTabBar are actually added to the 'stack' item's children. + default property alias content: stack.children + property int currentIndex: 0 + onCurrentIndexChanged: setOpacities() + Component.onCompleted: setOpacities() + + function setOpacities() { + var visibleChild = null; + for (let i = 0; i < stack.children.length; ++i) { + let on = i === currentIndex; + let child = stack.children[i]; + child.visible = on; + if (on) + visibleChild = child; + } + + // try to make sure the current tab gets keyboard focus properly. + // We do some extra work in case the tab itself is a loader. + forceActiveFocus(); + visibleChild.focus = true + if (visibleChild instanceof Loader) { + var child = visibleChild.item; + if (child !== null) + child.focus = true; + } + } + + Rectangle { + id: header + width: parent.width + height: 40 + color: root.palette.base + + Column { + id: menuButton + spacing: 3 + y: 6 + x: 5 + + Repeater { + model: 3 + delegate: Rectangle { + color: mainWindow.palette.text + width: 12 + height: 3 + radius: 2 + } + } + } + MouseArea { + anchors.fill: menuButton + anchors.margins: -10 + cursorShape: Qt.PointingHandCursor + onClicked: menuOverlay.open = true; + } + + Image { + source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" + // ratio: 449 / 77 + width: 150 + height: 26 + y: 4 + x: 32 + } + + } + + Item { + id: stack + width: root.width + anchors.top: header.bottom; anchors.bottom: tabbar.top + } + Row { + id: tabbar + anchors.bottom: parent.bottom + + Repeater { + model: stack.children.length + delegate: Item { + height: 80 + width: root.width / stack.children.length; + QQC2.Label { + text: modelData + 1 + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: root.currentIndex = modelData + } + } + } + } +} diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml new file mode 100644 index 0000000..dd7dfac --- /dev/null +++ b/guis/mobile/MenuOverlay.qml @@ -0,0 +1,92 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 2.15 +import "../ControlColors.js" as ControlColors + +Item { + id: root + property bool open: false + + anchors.fill: parent + + Rectangle { + anchors.fill: parent + opacity: root.open ? 0.5 : 0 + color: "black" + } + + Rectangle { + id: menuArea + color: mainWindow.palette.base + width: 300 + height: parent.height + x: root.open ? 0 : 0 - width -3 + clip: true + + Rectangle { + // TODO get little light-on/light-off icon instead + color: "black" + width: 20 + height: 20 + anchors.right: parent.right + anchors.rightMargin: 10 + y: 10 + MouseArea { + anchors.fill: parent + onClicked: { + Pay.useDarkSkin = !Pay.useDarkSkin + ControlColors.applySkin(mainWindow) + } + } + } + + ColumnLayout { + width: parent.width + spacing: 10 + y: 40 + x: 15 + Label { + text: "Network Details" + width: parent.width + wrapMode: Text.WordWrap + MouseArea { + anchors.fill: parent + anchors.margins: -4 + onClicked: { + thePile.push("NetView.qml"); + root.open = false; + } + } + } + } + + Behavior on x { NumberAnimation { } } + } + // allow close by clicking next to the menu + MouseArea { + width: parent.width - menuArea.width + height: parent.height + anchors.right: parent.right + enabled: root.open + onClicked: root.open = false; + } + + // TODO add gesture (swipe right) to close menu +} diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml new file mode 100644 index 0000000..430b78a --- /dev/null +++ b/guis/mobile/Page.qml @@ -0,0 +1,80 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 2.15 + +QQC2.Control { + id: root + + width: parent.width + height: parent.height + + default property alias content: child.children + property alias headerText: headerLabel.text + + Rectangle { + id: header + width: parent.width + height: 40 + color: root.palette.base + + Image { + id: backButton + x: 6 + y: 10 + source: Pay.useDarkSkin ? "qrc:/back-arrow-light.svg" : "qrc:/back-arrow.svg" + width: 30 + height: 20 + MouseArea { + anchors.fill: parent + onClicked: { + console.log(" closing"); + thePile.pop(); + } + } + } + + QQC2.Label { + id: headerLabel + anchors.centerIn: parent + } + } + + Flickable { + width: parent.width + y: header.height + height: parent.height - y + clip: true + contentHeight: child.height + contentWidth: width + GridLayout { + id: child + width: parent.width + height: implicitHeight + columns: 1 + } + } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + event.accepted = true; + console.log(" closing"); + thePile.pop(); + } + } +} diff --git a/guis/mobile/images/back-arrow-light.svg b/guis/mobile/images/back-arrow-light.svg new file mode 100644 index 0000000..3dda073 --- /dev/null +++ b/guis/mobile/images/back-arrow-light.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/guis/mobile/images/back-arrow.svg b/guis/mobile/images/back-arrow.svg new file mode 100644 index 0000000..50b7223 --- /dev/null +++ b/guis/mobile/images/back-arrow.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 051ec2f..490beb4 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -38,18 +38,11 @@ ApplicationWindow { property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" - Item { - id: header - - } - StackView { - id: stackView - anchors.left: parent.left - anchors.right: parent.right - anchors.top: header.bottom - anchors.bottom: parent.bottom + id: thePile + anchors.fill: parent + initialItem: "./MainView.qml" } - + MenuOverlay { id: menuOverlay } } -- 2.54.0 From 3aa5bab36a01da80b4cb40fd951bf74ecb898211 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 12:08:30 +0100 Subject: [PATCH 0054/1428] Add missing file --- guis/mobile/NetView.qml | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 guis/mobile/NetView.qml diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml new file mode 100644 index 0000000..09b5442 --- /dev/null +++ b/guis/mobile/NetView.qml @@ -0,0 +1,98 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick 2.15 +import QtQuick.Controls 2.11 as QQC2 +import QtQuick.Layouts 1.11 +import "../Flowee" as Flowee + +Page { + headerText: qsTr("Peers") + ListView { + id: listView + model: net.peers + QQC2.ScrollBar.vertical: QQC2.ScrollBar { } + + focus: true + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape) { + root.visible = false; + event.accepted = true + } + } + + delegate: Rectangle { + width: listView.width + height: peerPane.height + 12 + color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base + ColumnLayout { + id: peerPane + width: listView.width - 20 + x: 10 + y: 6 + + QQC2.Label { + text: modelData.userAgent + } + QQC2.Label { + text: qsTr("Address", "network address (IP)") + ": " + modelData.address + } + RowLayout { + height: secondRow.height + QQC2.Label { + id: secondRow + text: qsTr("Start-height: %1").arg(modelData.startHeight) + } + QQC2.Label { + text: qsTr("ban-score: %1").arg(modelData.banScore) + } + } + QQC2.Label { + id : accountStatus + font.bold: true + text: { + var id = modelData.segmentId; + if (id === 0) { + // not peered yet. + if (modelData.services.Length === 0) + return qsTr("initializing connection") + if (!modelData.headersReceived) + return qsTr("Verifying peer") + return "" + } + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + if (accounts[i].id === id) + return qsTr("Peer for wallet: %1").arg(accounts[i].name); + } + return qsTr("Peer for wallet"); + } + visible: text !== "" + } + Flow { + Repeater { + model: modelData.services + delegate: QQC2.Label { text: modelData } + } + } + } + } + Keys.forwardTo: Flowee.ListViewKeyHandler { + target: listView + } + } +} -- 2.54.0 From 3fcca1487a9a3599b9e0b9efc0467e49a879ea94 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 12:25:33 +0100 Subject: [PATCH 0055/1428] Remove duplicate --- guis/widgets.qrc | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 5485d36..1fb84f0 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -18,7 +18,6 @@ Flowee/FiatValueField.qml Flowee/BitcoinValueField.qml Flowee/Dialog.qml - Flowee/DialogButtonBox.qml Flowee/CashFusionIcon.qml Flowee/DialogButtonBox.qml Flowee/WarningLabel.qml -- 2.54.0 From 674fedd41b1a3c6e9af51f67ba9ff01d4cfc4716 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 13:05:44 +0100 Subject: [PATCH 0056/1428] Use the Qt created cmake method for adding qrc files --- CMakeLists.txt | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a29ab9..d5fae84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,10 +18,7 @@ cmake_minimum_required(VERSION 3.19) project(flowee_pay VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) - set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) @@ -144,21 +141,24 @@ option (BUILD_DESKTOP_PAY if(BUILD_DESKTOP_PAY) # The qm files are generated in the build tree, but the qrc file is inside the # source directory and the path to resources are relative to the location of - # the qrc file itself. We use configure_file() to copy the qrc file in the build + # the qrc file itself. We copy the qrc file in the build # directory such that it can find the qm translations files. The qrc file is # copied if it doesn't exist in the destination or if it is modified. - configure_file(translations/desktop-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) + file(COPY translations/desktop-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) if (local_qml) set (QML_PATH ${CMAKE_SOURCE_DIR}/guis/) endif() configure_file(qml_path_helper.cpp.in guis/desktop/qml_path_helper.cpp) - add_executable(pay + set (SOURCES_PAY main.cpp guis/desktop/qml_path_helper.cpp + ) + qt6_add_resources(SOURCES_PAY guis/desktop.qrc guis/widgets.qrc - desktop-i18n.qrc + ${CMAKE_BINARY_DIR}/desktop-i18n.qrc ) + add_executable(pay ${SOURCES_PAY}) set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") target_link_libraries(pay pay_lib Qt6::Svg) install(TARGETS pay DESTINATION bin) @@ -185,27 +185,28 @@ if (ANDROID AND BUILD_MOBILE_PAY) DESTINATION ${ASSETS_DIR}) configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) - if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") - message ("pay_mobile: Found pre-compiled translations") - set (MOBILE_PAY_I18N_QRC mobile-i18n.qrc) - configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) - endif() - - qt6_add_executable(pay_mobile + set (SOURCES_PAY_MOBILE main.cpp guis/mobile/qml_path_helper.cpp + ) + qt6_add_resources(SOURCES_PAY_MOBILE guis/mobile.qrc guis/widgets.qrc - ${MOBILE_PAY_I18N_QRC} ) + if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") + message ("pay_mobile: Found pre-compiled translations") + file(COPY translations/mobile-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) + qt6_add_resources(SOURCES_PAY_MOBILE ${CMAKE_BINARY_DIR}/mobile-i18n.qrc) + endif() + + qt6_add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) target_link_libraries(pay_mobile pay_lib Qt6::Svg) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - qt6_android_generate_deployment_settings(pay_mobile) - qt_android_add_apk_target(pay_mobile) + qt6_android_add_apk_target(pay_mobile) endif () if(NOT ANDROID AND BUILD_MOBILE_PAY) @@ -213,15 +214,18 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) endif() configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) - configure_file(translations/mobile-i18n.qrc ${CMAKE_BINARY_DIR} COPYONLY) + file(COPY translations/mobile-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) - add_executable(pay_mobile + set (SOURCES_PAY_MOBILE main.cpp guis/mobile/qml_path_helper.cpp + ) + qt6_add_resources(SOURCES_PAY_MOBILE guis/mobile.qrc guis/widgets.qrc - mobile-i18n.qrc + ${CMAKE_BINARY_DIR}/mobile-i18n.qrc ) + add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) set_target_properties(pay_mobile PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) -- 2.54.0 From 3705e5caefadb70b1824f96adbfd4161ef76b5dd Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 13:09:46 +0100 Subject: [PATCH 0057/1428] Add 'noapk' arg to smartBuild --- android/build-pay.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index d0b88f1..3e91630 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -19,7 +19,7 @@ _thehub_dir_="$1" _pay_native_name_="$2" if test -f smartBuild.sh; then - ./smartBuild.sh + ./smartBuild.sh noapk exit fi @@ -96,7 +96,6 @@ export QT_ANDROID_KEYSTORE_KEY_PASS=longPassword /usr/local/bin/androiddeployqt \ --input /home/builds/build/android-pay_mobile-deployment-settings.json \ --output /home/builds/build/android-build \ - --apk /home/builds/floweepay.apk \ --sign /home/builds/src/android/selfsigned.keystore floweepay HERE chmod 755 .sign @@ -125,14 +124,23 @@ if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockh imports/blockheaders-meta-extractor android-build/assets fi +if test "\$1" = "sign" -o "\$2" = "sign" +then + MAKE_SIGNED_APK=1 +else + if test "\$1" != "noapk"; then + MAKE_UNSIGNED_APK=apk + fi +fi + docker run --rm -ti\ --volume=`pwd`:/home/builds/build \ --volume=$floweePaySrcDir:/home/builds/src \ --volume=$_thehub_dir_:/home/builds/floweelibs \ $_docker_name_ \ - "/usr/bin/ninja -C build pay_mobile" + "/usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir \$MAKE_UNSIGNED_APK" -if test "\$1" = "sign" -o "\$2" = "sign" +if test -n "\$MAKE_SIGNED_APK" then docker run --rm -ti\ --volume=`pwd`:/home/builds/build \ @@ -140,10 +148,12 @@ then --volume=$_thehub_dir_:/home/builds/floweelibs \ $_docker_name_ \ build/.sign + echo -n "-- COPYING: " + cp -v android-build//build/outputs/apk/release/android-build-release-signed.apk floweepay.apk fi HERE chmod 700 smartBuild.sh fi -./smartBuild.sh +./smartBuild.sh noapk -- 2.54.0 From bb7eb892d09f0b2d7ce372c53e0ff68a3890d350 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 14:35:36 +0100 Subject: [PATCH 0058/1428] Remove this old dir. --- mobile/PeersOverview.qml | 95 ---------------------------------------- 1 file changed, 95 deletions(-) delete mode 100644 mobile/PeersOverview.qml diff --git a/mobile/PeersOverview.qml b/mobile/PeersOverview.qml deleted file mode 100644 index 9c03e9a..0000000 --- a/mobile/PeersOverview.qml +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - Label { - id: chainHeightLabel - text: "Headers synced height: " + Pay.headerChainHeight - } - - ListView { - id: listView - model: net.peers - clip: true - ScrollBar.vertical: ScrollBar { } - - width: parent.width - anchors.top: chainHeightLabel.bottom - anchors.bottom: parent.bottom - focus: true - delegate: Rectangle { - width: listView.width - height: peerPane.height + 12 - color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base - ColumnLayout { - id: peerPane - width: listView.width - 20 - x: 10 - y: 6 - - Label { - text: modelData.userAgent - } - Label { - text: qsTr("Address", "network address (IP)") + ": " + modelData.address - } - RowLayout { - height: secondRow.height - Label { - id: secondRow - text: qsTr("Start-height: %1").arg(modelData.startHeight) - } - Label { - text: qsTr("ban-score: %1").arg(modelData.banScore) - } - } - Label { - id : accountStatus - font.bold: true - text: { - var id = modelData.segmentId; - if (id === 0) { - // not peered yet. - if (modelData.services.Length === 0) - return qsTr("initializing connection") - if (!modelData.headersReceived) - return qsTr("Verifying peer") - return qsTr("Assigning...") - } - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - if (accounts[i].id === id) - return qsTr("Peer for wallet: %1").arg(accounts[i].name); - } - return qsTr("Peer for initial wallet"); - } - visible: text !== "" - } - Flow { - Repeater { - model: modelData.services - delegate: Label { text: modelData } - } - } - } - } - } -} -- 2.54.0 From e2ab48e81612390b633296064afbbf78ab5d7fcc Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Nov 2022 15:19:35 +0100 Subject: [PATCH 0059/1428] Make the phone UI look the same as my Linux one. We now hardcode the style and I needed to override the Label in order to explicitly use the palette from the mainWindow. Not sure why the one on Android behaved different than on Linux. --- guis/Flowee/Label.qml | 24 ++++++++++++++++++++++++ guis/mobile/MainView.qml | 1 + guis/mobile/MainViewBase.qml | 3 ++- guis/mobile/MenuOverlay.qml | 7 +++---- guis/mobile/NetView.qml | 12 ++++++------ guis/mobile/Page.qml | 7 ++++--- guis/mobile/main.qml | 10 +++++++--- guis/widgets.qrc | 1 + 8 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 guis/Flowee/Label.qml diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml new file mode 100644 index 0000000..1bf1e4f --- /dev/null +++ b/guis/Flowee/Label.qml @@ -0,0 +1,24 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick.Controls as QQC2 + +QQC2.Label { + // With Qt6.4 on Android, this extra line is needed to + // get the label to follow the app-color-style + color: mainWindow.palette.text +} diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 780f0b7..ab48d72 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -18,6 +18,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 +import "../Flowee" as Flowee MainViewBase { Rectangle { diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index b45e208..c173587 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -18,6 +18,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 +import "../Flowee" as Flowee QQC2.Control { id: root @@ -105,7 +106,7 @@ QQC2.Control { delegate: Item { height: 80 width: root.width / stack.children.length; - QQC2.Label { + Flowee.Label { text: modelData + 1 anchors.centerIn: parent } diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index dd7dfac..4a565bd 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -16,16 +16,15 @@ * along with this program. If not, see . */ import QtQuick 2.15 -import QtQuick.Controls 2.15 +import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 import "../ControlColors.js" as ControlColors +import "../Flowee" as Flowee Item { id: root property bool open: false - anchors.fill: parent - Rectangle { anchors.fill: parent opacity: root.open ? 0.5 : 0 @@ -62,7 +61,7 @@ Item { spacing: 10 y: 40 x: 15 - Label { + Flowee.Label { text: "Network Details" width: parent.width wrapMode: Text.WordWrap diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index 09b5442..c8d5d5d 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -45,23 +45,23 @@ Page { x: 10 y: 6 - QQC2.Label { + Flowee.Label { text: modelData.userAgent } - QQC2.Label { + Flowee.Label { text: qsTr("Address", "network address (IP)") + ": " + modelData.address } RowLayout { height: secondRow.height - QQC2.Label { + Flowee.Label { id: secondRow text: qsTr("Start-height: %1").arg(modelData.startHeight) } - QQC2.Label { + Flowee.Label { text: qsTr("ban-score: %1").arg(modelData.banScore) } } - QQC2.Label { + Flowee.Label { id : accountStatus font.bold: true text: { @@ -86,7 +86,7 @@ Page { Flow { Repeater { model: modelData.services - delegate: QQC2.Label { text: modelData } + delegate: Flowee.Label { text: modelData } } } } diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 430b78a..f8e45a6 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -18,12 +18,13 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 +import "../Flowee" as Flowee QQC2.Control { id: root - width: parent.width - height: parent.height + width: parent == null ? 10 : parent.width + height: parent == null ? 10 : parent.height default property alias content: child.children property alias headerText: headerLabel.text @@ -50,7 +51,7 @@ QQC2.Control { } } - QQC2.Label { + Flowee.Label { id: headerLabel anchors.centerIn: parent } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 490beb4..606b42d 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -16,10 +16,12 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors +import QtQuick.Controls.Basic + ApplicationWindow { id: mainWindow title: "Flowee Pay" @@ -43,6 +45,8 @@ ApplicationWindow { anchors.fill: parent initialItem: "./MainView.qml" } - - MenuOverlay { id: menuOverlay } + MenuOverlay { + id: menuOverlay + anchors.fill: parent + } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 1fb84f0..de00efb 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -22,5 +22,6 @@ Flowee/DialogButtonBox.qml Flowee/WarningLabel.qml Flowee/PasswdDialog.qml + Flowee/Label.qml -- 2.54.0 From cb7b4549635023510823d1ff898feafeb8a7eb53 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Nov 2022 00:12:17 +0100 Subject: [PATCH 0060/1428] Implement some more layouting This makes the MenuOverlay interaction work better. We start with some main view setup that moves in the direction of the actual UX design. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 67 ++++++++++++++++++++++++++++++++++ guis/mobile/MainView.qml | 19 +++++----- guis/mobile/MainViewBase.qml | 28 +++++++++----- guis/mobile/MenuOverlay.qml | 42 +++++++++++++++++++-- 5 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 guis/mobile/AccountHistory.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 9d35825..a4703ca 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -12,5 +12,6 @@ mobile/Page.qml mobile/MainViewBase.qml mobile/MainView.qml + mobile/AccountHistory.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml new file mode 100644 index 0000000..059bf32 --- /dev/null +++ b/guis/mobile/AccountHistory.qml @@ -0,0 +1,67 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 2.15 +import "../Flowee" as Flowee + +Item { + id: accountHistory + property string icon: "qrc:/bla" + ColumnLayout { + spacing: 10 + width: parent.width - 20 + x: 10 + + Item { + id: walletSelector + height: currentWalletName.height + + Flowee.Label { + id: currentWalletName + text: "Daily Wallet" + } + } + Flowee.Label { + id: balanceLabel + text: "€ 100"; + } + + GridLayout { + width: parent.width + columns: 3 + + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "yellow" + } + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "yellow" + } + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "yellow" + } + } + + Item { + width: parent.width + height: 300 + Rectangle { + width: parent.width - 30 + x: 15 + radius: 10 + height: 300 + color: mainWindow.palette.window + } + } + } +} diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index ab48d72..d3ffdcf 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -21,20 +21,19 @@ import QtQuick.Layouts 2.15 import "../Flowee" as Flowee MainViewBase { - Rectangle { - color: "blue"; + AccountHistory { anchors.fill: parent } - Rectangle { - color: "red"; + Item { + id: sendScreen + property string icon: "qrc:/bla" anchors.fill: parent + } - Rectangle { + Item { + id: receiveScreen + property string icon: "qrc:/bla" anchors.fill: parent - color: "black"; - } - Rectangle { - anchors.fill: parent - color: "white"; + } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index c173587..ab507f9 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -25,7 +25,7 @@ QQC2.Control { width: parent.width height: parent.height - // This trick means any child items the FloweeTabBar are actually added to the 'stack' item's children. + // This trick means any child items are actually added to the 'stack' item's children. default property alias content: stack.children property int currentIndex: 0 onCurrentIndexChanged: setOpacities() @@ -61,8 +61,8 @@ QQC2.Control { Column { id: menuButton spacing: 3 - y: 6 - x: 5 + y: 12 + x: 10 Repeater { model: 3 @@ -76,18 +76,26 @@ QQC2.Control { } MouseArea { anchors.fill: menuButton - anchors.margins: -10 + anchors.margins: -20 cursorShape: Qt.PointingHandCursor onClicked: menuOverlay.open = true; } - Image { - source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" - // ratio: 449 / 77 - width: 150 - height: 26 - y: 4 + Item { + // Here we just want the text part. So clip that out. + clip: true + y: 10 x: 32 + width: 122 + height: 21 + Image { + source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" + // ratio: 449 / 77 + width: 150 + height: 26 + x: -28 + y: -5 + } } } diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 4a565bd..e193017 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -25,9 +25,20 @@ Item { id: root property bool open: false + /* + onOpenChanged: { + // a little non-declarative, but needed due to the drag overwriting the X + menuArea.x = root.open ? 0 : 0 - width -3 + } */ + Rectangle { anchors.fill: parent - opacity: root.open ? 0.5 : 0 + opacity: { + if (!root.open) + return 0; + // we become 50% opaque when the menuArea is fully open (x == 0) + return (menuArea.x + 250) / 500; + } color: "black" } @@ -76,7 +87,33 @@ Item { } } - Behavior on x { NumberAnimation { } } + Behavior on x { NumberAnimation { duration: 100 } } + + property bool opened: false + onXChanged: { + if (!root.open) + opened = false; + else if (x === 0) + opened = true; + // close on user drag to the left + if (opened && x < -80) + root.open = false + } + // gesture (swipe right) to close menu + DragHandler { + id: dragHandler + enabled: root.open + yAxis.enabled: false + xAxis.minimum: -200 + xAxis.maximum: 0 + + onActiveChanged: { + // should the user abort the swipe left, restore + // the original binding + if (!active && root.open) + menuArea.x = root.open ? 0 : 0 - width -3 + } + } } // allow close by clicking next to the menu MouseArea { @@ -87,5 +124,4 @@ Item { onClicked: root.open = false; } - // TODO add gesture (swipe right) to close menu } -- 2.54.0 From e8ff9b294085cf7307963fc896dcbad32e7e2caa Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Nov 2022 22:11:53 +0100 Subject: [PATCH 0061/1428] Remove comment no longer needed The code is much more readble, now it would just confuse. --- main.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/main.cpp b/main.cpp index 6a12fdd..2cbfe42 100644 --- a/main.cpp +++ b/main.cpp @@ -133,9 +133,6 @@ int main(int argc, char *argv[]) #endif "/main.qml"); - // make sure that FloweePay::instance() is not called above this line! - // since doing so will start initialization of the p2p stuff in a separate thread. - QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld]() { loadCompleteHandler(engine, cld); }); -- 2.54.0 From 3b4726418fd5aaccb73c0d3e60905ff4d84168c7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Nov 2022 23:05:24 +0100 Subject: [PATCH 0062/1428] Cleanup headers. --- AccountInfo.cpp | 1 + AccountInfo.h | 1 - PortfolioDataProvider.cpp | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/AccountInfo.cpp b/AccountInfo.cpp index 14a0549..286dda3 100644 --- a/AccountInfo.cpp +++ b/AccountInfo.cpp @@ -18,6 +18,7 @@ #include "AccountInfo.h" #include "WalletHistoryModel.h" #include "FloweePay.h" +#include "PaymentRequest.h" #include #include diff --git a/AccountInfo.h b/AccountInfo.h index aadfb21..54b0068 100644 --- a/AccountInfo.h +++ b/AccountInfo.h @@ -19,7 +19,6 @@ #define ACCOUNTINFO_H #include -#include "PaymentRequest.h" #include "TransactionInfo.h" #include "Wallet.h" #include "WalletHistoryModel.h" diff --git a/PortfolioDataProvider.cpp b/PortfolioDataProvider.cpp index 7b75619..f9a8ed9 100644 --- a/PortfolioDataProvider.cpp +++ b/PortfolioDataProvider.cpp @@ -17,9 +17,7 @@ */ #include "PortfolioDataProvider.h" #include "AccountInfo.h" -#include "BitcoinValue.h" #include "FloweePay.h" -#include "Payment.h" #include "Wallet.h" #include -- 2.54.0 From bb8a940fbfb7d2f33ff309e876965a0cfb527ac0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Nov 2022 23:07:15 +0100 Subject: [PATCH 0063/1428] Continue implementing the UX design. This adds account showing and selection. Icons for the "night-theme" selector. Improvements to the color set and a splashscreen. --- FloweePay.cpp | 13 + FloweePay.h | 7 + guis/mobile.qrc | 4 +- guis/mobile/AccountHistory.qml | 277 +++++++++++--- guis/mobile/Loading.qml | 32 ++ guis/mobile/MainViewBase.qml | 6 +- guis/mobile/MenuOverlay.qml | 17 +- guis/mobile/Page.qml | 22 +- guis/mobile/images/back-arrow-light.svg | 16 - guis/mobile/images/back-arrow.svg | 35 +- guis/mobile/images/maslenica.svg | 489 ++++++++++++++++++++++++ guis/mobile/images/moon.svg | 65 ++++ guis/mobile/main.qml | 48 ++- 13 files changed, 928 insertions(+), 103 deletions(-) create mode 100644 guis/mobile/Loading.qml delete mode 100644 guis/mobile/images/back-arrow-light.svg create mode 100644 guis/mobile/images/maslenica.svg create mode 100644 guis/mobile/images/moon.svg diff --git a/FloweePay.cpp b/FloweePay.cpp index 3a398b4..3125d4b 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -621,6 +621,19 @@ uint32_t FloweePay::walletStartHeightHint() const return time(nullptr); } +int FloweePay::fontScaling() const +{ + return m_fontScaling; +} + +void FloweePay::setFontScaling(int newFontScaling) +{ + if (m_fontScaling == newFontScaling) + return; + m_fontScaling = newFontScaling; + emit fontScalingChanged(); +} + PriceDataProvider *FloweePay::prices() const { return m_prices.get(); diff --git a/FloweePay.h b/FloweePay.h index 29c9365..7cfd3b2 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -63,6 +63,7 @@ class FloweePay : public QObject, WorkerThreads, P2PNetInterface Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged); Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) + Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) public: enum StringType { Unknown = 0, @@ -249,6 +250,9 @@ public: void setNewBlockMuted(bool mute); + int fontScaling() const; + void setFontScaling(int newFontScaling); + signals: void loadComplete(); /// \internal @@ -266,6 +270,8 @@ signals: void hideBalanceChanged(); void newBlockMutedChanged(); + void fontScalingChanged(); + private slots: void loadingCompleted(); @@ -292,6 +298,7 @@ private: int m_windowWidth = 500; int m_windowHeight = 500; int m_initialHeaderChainHeight = 0; + int m_fontScaling = 100; bool m_darkSkin = true; bool m_createStartWallet = false; bool m_hideBalance = false; diff --git a/guis/mobile.qrc b/guis/mobile.qrc index a4703ca..4898a6a 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -3,7 +3,8 @@ images/FloweePay-light.svg images/FloweePay.svg mobile/images/back-arrow.svg - mobile/images/back-arrow-light.svg + mobile/images/maslenica.svg + mobile/images/moon.svg mobile/main.qml mobile/defaults.ini ControlColors.js @@ -13,5 +14,6 @@ mobile/MainViewBase.qml mobile/MainView.qml mobile/AccountHistory.qml + mobile/Loading.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 059bf32..706e359 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -3,64 +3,235 @@ import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 import "../Flowee" as Flowee -Item { +Flickable { id: accountHistory - property string icon: "qrc:/bla" - ColumnLayout { - spacing: 10 - width: parent.width - 20 - x: 10 + property string icon: "qrc:/bla" // TODO make icon and show on tab - Item { - id: walletSelector - height: currentWalletName.height + contentHeight: column.height + 20 + clip: true + Rectangle { + // background color "base", to indicate these are widgets here. + anchors.fill: parent + color: mainWindow.palette.base + ColumnLayout { + id: column + spacing: 20 + width: parent.width - 20 + x: 10 + y: 10 + + Item { + id: smallAccountSelector + width: parent.width + height: walletSelector.height + z: 10 + + property bool open: false + + visible: { + // if there is only an initial, not user-owned wallet, there is nothing to show here. + return portfolio.current.isUserOwned + } + + Rectangle { + id: walletSelector + height: currentWalletName.height + 14 + width: parent.width + 5 + color: "#00000000" + border.color: mainWindow.palette.button + border.width: hasMultipleWallets ? 0.7 : 0 + x: -5 + + property bool hasMultipleWallets: portfolio.accounts.length > 1 + + Flowee.Label { + x: 5 + id: currentWalletName + text: portfolio.current.name + anchors.verticalCenter: parent.verticalCenter + } + Flowee.ArrowPoint { + id: arrowPoint + visible: parent.hasMultipleWallets + color: mainWindow.palette.text + anchors.right: parent.right + anchors.rightMargin: 10 + rotation: 90 + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + enabled: parent.hasMultipleWallets + onClicked: { + smallAccountSelector.open = !smallAccountSelector.open + // move focus so the 'back' button will close it. + extraWallets.forceActiveFocus(); + } + } + } + + Rectangle { + id: extraWallets + width: parent.width + y: walletSelector.height + height: parent.open ? columnLayout.height + 20 : 0 + color: mainWindow.palette.base + Behavior on height { + NumberAnimation { + duration: 200 + } + } + + clip: true + visible: height > 0 + focus: true + ColumnLayout { + y: 10 + id: columnLayout + width: parent.width + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight + } + } + + Repeater { // portfolio holds all our accounts + width: parent.width + model: portfolio.accounts + delegate: Item { + width: extraWallets.width + height: accountName.height * 2 + 12 + Flowee.Label { + id: accountName + y: 6 + text: modelData.name + } + Flowee.Label { + id: lastActive + anchors.top: accountName.bottom + text: qsTr("last active") + ": " + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.Label { + anchors.top: lastActive.top + anchors.left: lastActive.right + text: "today"; + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.Label { + anchors.right: parent.right + anchors.top: accountName.top + text: "€ 123.00"; + } + MouseArea { + anchors.fill: parent + onClicked: { + portfolio.current = modelData + smallAccountSelector.open = false + } + } + } + } + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight + } + } + } + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + event.accepted = true; + smallAccountSelector.open = false + } + } + } + } + Flowee.Label { + id: balanceLabel + text: "€ 100"; + } + + GridLayout { + width: parent.width + columns: 3 + + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "#00000000" + border.color: "yellow" + border.width: 1 + } + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "#00000000" + border.color: "yellow" + border.width: 1 + } + Rectangle { + Layout.alignment: Qt.AlignHCenter + width: 60 + height: 60 + radius: 30 + color: "#00000000" + border.color: "yellow" + border.width: 1 + } + Flowee.Label { + text: "Send Money" + horizontalAlignment: Qt.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + } + Flowee.Label { + text: "Scheduled" + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Qt.AlignHCenter + Layout.fillWidth: true + } + Flowee.Label { + text: "Receive" + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Qt.AlignHCenter + Layout.fillWidth: true + } + } + + Item { width: 1; height: 10 } // spacer Flowee.Label { - id: currentWalletName - text: "Daily Wallet" + text: "Earlier this month" } - } - Flowee.Label { - id: balanceLabel - text: "€ 100"; - } - - GridLayout { - width: parent.width - columns: 3 - - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "yellow" - } - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "yellow" - } - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "yellow" - } - } - - Item { - width: parent.width - height: 300 - Rectangle { - width: parent.width - 30 - x: 15 - radius: 10 - height: 300 - color: mainWindow.palette.window + Item { + width: parent.width + height: 100 + Rectangle { + width: parent.width - 30 + x: 15 + radius: 10 + height: 100 + color: mainWindow.palette.window + } } } } diff --git a/guis/mobile/Loading.qml b/guis/mobile/Loading.qml new file mode 100644 index 0000000..84bdae3 --- /dev/null +++ b/guis/mobile/Loading.qml @@ -0,0 +1,32 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Rectangle { + color: "#0b1088" + width: parent.width + height: parent.height + + Image { + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: parent.width / 10 * 8 + height: width / 449 * 77 + anchors.centerIn: parent + } +} diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index ab507f9..4d75865 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -56,7 +56,7 @@ QQC2.Control { id: header width: parent.width height: 40 - color: root.palette.base + color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue Column { id: menuButton @@ -67,7 +67,7 @@ QQC2.Control { Repeater { model: 3 delegate: Rectangle { - color: mainWindow.palette.text + color: "white" width: 12 height: 3 radius: 2 @@ -89,7 +89,7 @@ QQC2.Control { width: 122 height: 21 Image { - source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" + source: "qrc:/FloweePay.svg" // ratio: 449 / 77 width: 150 height: 26 diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index e193017..8b92e30 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -25,12 +25,6 @@ Item { id: root property bool open: false - /* - onOpenChanged: { - // a little non-declarative, but needed due to the drag overwriting the X - menuArea.x = root.open ? 0 : 0 - width -3 - } */ - Rectangle { anchors.fill: parent opacity: { @@ -50,14 +44,13 @@ Item { x: root.open ? 0 : 0 - width -3 clip: true - Rectangle { - // TODO get little light-on/light-off icon instead - color: "black" - width: 20 - height: 20 + Image { + width: 25 + height: 25 anchors.right: parent.right anchors.rightMargin: 10 y: 10 + source: Pay.useDarkSkin ? "qrc:/maslenica.svg" : "qrc:/moon.svg" MouseArea { anchors.fill: parent onClicked: { @@ -96,7 +89,7 @@ Item { else if (x === 0) opened = true; // close on user drag to the left - if (opened && x < -80) + if (opened && x < -50) root.open = false } // gesture (swipe right) to close menu diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index f8e45a6..3d1966f 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -33,26 +33,25 @@ QQC2.Control { id: header width: parent.width height: 40 - color: root.palette.base + color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue Image { id: backButton - x: 6 - y: 10 - source: Pay.useDarkSkin ? "qrc:/back-arrow-light.svg" : "qrc:/back-arrow.svg" - width: 30 - height: 20 + x: 13 + source: "qrc:/back-arrow.svg" + width: 20 * 1.1 + height: 15 * 1.1 + anchors.verticalCenter: parent.verticalCenter MouseArea { anchors.fill: parent - onClicked: { - console.log(" closing"); - thePile.pop(); - } + anchors.margins: -15 + onClicked: thePile.pop(); } } - Flowee.Label { + Text { id: headerLabel + color: "white" anchors.centerIn: parent } } @@ -74,7 +73,6 @@ QQC2.Control { Keys.onPressed: (event)=> { if (event.key === Qt.Key_Back) { event.accepted = true; - console.log(" closing"); thePile.pop(); } } diff --git a/guis/mobile/images/back-arrow-light.svg b/guis/mobile/images/back-arrow-light.svg deleted file mode 100644 index 3dda073..0000000 --- a/guis/mobile/images/back-arrow-light.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/guis/mobile/images/back-arrow.svg b/guis/mobile/images/back-arrow.svg index 50b7223..8b6f5d4 100644 --- a/guis/mobile/images/back-arrow.svg +++ b/guis/mobile/images/back-arrow.svg @@ -3,14 +3,43 @@ width="20mm" height="15mm" viewBox="0 0 20 15.000001" + version="1.1" + id="svg4" + sodipodi:docname="back-arrow.svg" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + + + style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3.13059;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" + d="m 6.5453482,0.56753243 c 0,0 1.0813315,-0.99492759 1.9065757,-0.15402513 0.6868849,0.6239627 0.2169303,1.2958851 0.1428295,1.3926452 -0.074101,0.096766 -5.0830267,4.713792 -5.0830267,4.713792 l 15.5306663,0.014714 c 0,0 0.926866,-0.015035 0.933921,1.0549127 -0.04567,0.8839934 -0.543346,0.8898354 -0.837703,0.9386733 -0.530032,0.066651 -15.6773337,-0.033778 -15.6773337,-0.033778 0,0 4.5763069,4.2270235 4.7038592,4.3778395 0.1275705,0.1508 0.8883278,0.766506 0.046105,1.77872 C 7.5509095,15.444637 6.3847233,14.604777 6.2764929,14.516313 5.9816861,14.275352 0.83832602,8.9329858 0.49291962,8.6003866 0.29644432,8.4112056 0.14008139,8.0760656 0.13961464,7.5248588 0.16258586,6.9575043 0.14941392,6.9772248 0.32559992,6.8015992 0.57601512,6.5519978 6.5453607,0.56742598 6.5453607,0.56742598 Z" + id="path113" + sodipodi:nodetypes="cccccccccscccccc" /> diff --git a/guis/mobile/images/maslenica.svg b/guis/mobile/images/maslenica.svg new file mode 100644 index 0000000..34d6939 --- /dev/null +++ b/guis/mobile/images/maslenica.svg @@ -0,0 +1,489 @@ + + + + sun + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guis/mobile/images/moon.svg b/guis/mobile/images/moon.svg new file mode 100644 index 0000000..e8e8bc0 --- /dev/null +++ b/guis/mobile/images/moon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 606b42d..de2aacd 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -32,8 +32,17 @@ ApplicationWindow { property bool isLoading: typeof portfolio === "undefined"; onIsLoadingChanged: { - if (!isLoading) { - } + // only load our UI when the p2p layer is loaded and all + // variables are available. + if (!isLoading) + thePile.replace("./MainView.qml"); + } + Component.onCompleted: { + var scale = Pay.fontScaling; + mainWindow.font.bold = scale > 90; + mainWindow.font.fontPtSize = scale > 90; + var baseFromOS = mainWindow.font.pointSize; + mainWindow.font.pointSize = baseFromOS + 2 * (scale / 100) } property color floweeSalmon: "#ff9d94" @@ -43,10 +52,43 @@ ApplicationWindow { StackView { id: thePile anchors.fill: parent - initialItem: "./MainView.qml" + initialItem: "./Loading.qml"; } MenuOverlay { id: menuOverlay anchors.fill: parent } + + Item { + id: touchFeedbackOverlay + anchors.fill: parent + + Rectangle { + id: clickFeedback + color: "red" + opacity: 0.3 + width: 70 + height: 70 + radius: 35 + visible: opacity > 0 + + Timer { + running: parent.visible + interval: 50 + onTriggered: clickFeedback.opacity = 0 + } + Behavior on opacity { NumberAnimation { duration: 150 } } + } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onClicked: (event) => { + event.accepted = false; + clickFeedback.x = event.x - clickFeedback.width / 2; + clickFeedback.y = event.y - clickFeedback.height / 2; + clickFeedback.opacity = 0.6 + } + } + } } -- 2.54.0 From cc26702ff538e9449e9ab33a15c447dcfa91b2e5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 10:25:19 +0100 Subject: [PATCH 0064/1428] Add home icons --- guis/mobile/images/homeButtonIcon-light.svg | 34 +++++++++++++++++++++ guis/mobile/images/homeButtonIcon.svg | 34 +++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 guis/mobile/images/homeButtonIcon-light.svg create mode 100644 guis/mobile/images/homeButtonIcon.svg diff --git a/guis/mobile/images/homeButtonIcon-light.svg b/guis/mobile/images/homeButtonIcon-light.svg new file mode 100644 index 0000000..50029af --- /dev/null +++ b/guis/mobile/images/homeButtonIcon-light.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + diff --git a/guis/mobile/images/homeButtonIcon.svg b/guis/mobile/images/homeButtonIcon.svg new file mode 100644 index 0000000..41d2e85 --- /dev/null +++ b/guis/mobile/images/homeButtonIcon.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + -- 2.54.0 From 6a9848366d874aab683ae65dedf42197ac1d9fe0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 13:09:12 +0100 Subject: [PATCH 0065/1428] Refactor enum; move to own class The usage of the big application singleton for enums is not the best for linkage. See, in QML you like to use enums, but you need to register the object it is defined on with QML. So, you ideally have one object with loads of enums. Easy to maintain, less to learn for the QML author. Using the application-wide singleton made kind of sense, but this creates a dependency requirement for all users of these enums that doesn't jibe well with maintainable code. So, introduce a simple enums-only class that can be used from QML and is cheap to include from any using classes. --- CMakeLists.txt | 1 + FloweePay.cpp | 24 ++++++------- FloweePay.h | 17 ++------- PaymentDetailOutput.cpp | 8 ++--- WalletEnums.cpp | 23 ++++++++++++ WalletEnums.h | 45 ++++++++++++++++++++++++ guis/desktop/NewAccountImportAccount.qml | 18 +++++----- guis/desktop/SendTransactionPane.qml | 6 ++-- main.cpp | 2 +- 9 files changed, 100 insertions(+), 44 deletions(-) create mode 100644 WalletEnums.cpp create mode 100644 WalletEnums.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d5fae84..967e793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,7 @@ set (PAY_SOURCES Wallet_encryption.cpp Wallet_support.cpp Wallet_spending.cpp + WalletEnums.cpp ) if (ANDROID) diff --git a/FloweePay.cpp b/FloweePay.cpp index 3125d4b..1fd8704 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -772,34 +772,34 @@ bool FloweePay::checkDerivation(const QString &path) const } } -FloweePay::StringType FloweePay::identifyString(const QString &string) const +WalletEnums::StringType FloweePay::identifyString(const QString &string) const { const QString string_ = string.trimmed(); const std::string s = string_.toStdString(); if (string_.isEmpty()) { m_hdSeedValidator.clearSelectedLanguage(); - return Unknown; + return WalletEnums::Unknown; } CBase58Data legacy; if (legacy.SetString(s)) { if ((m_chain == P2PNet::MainChain && legacy.isMainnetPkh()) || (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetPkh())) - return LegacyPKH; + return WalletEnums::LegacyPKH; if ((m_chain == P2PNet::MainChain && legacy.isMainnetSh()) || (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetSh())) - return LegacySH; + return WalletEnums::LegacySH; if ((m_chain == P2PNet::MainChain && legacy.isMainnetPrivKey()) || (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetPrivKey())) - return PrivateKey; + return WalletEnums::PrivateKey; } CashAddress::Content c = CashAddress::decodeCashAddrContent(s, m_chainPrefix); if (!c.hash.empty()) { if (c.type == CashAddress::PUBKEY_TYPE) - return CashPKH; + return WalletEnums::CashPKH; if (c.type == CashAddress::SCRIPT_TYPE) - return CashSH; + return WalletEnums::CashSH; } try { @@ -816,7 +816,7 @@ FloweePay::StringType FloweePay::identifyString(const QString &string) const if (index != -1) { auto validity = m_hdSeedValidator.validateMnemonic(string_, index); if (validity == Mnemonic::Valid) - return CorrectMnemonic; + return WalletEnums::CorrectMnemonic; } else { // not a recognized word break; @@ -824,7 +824,7 @@ FloweePay::StringType FloweePay::identifyString(const QString &string) const } else if (index == -1) { // a not-first-word failed the lookup. if (space2 != -1) // this is the last word, don't highlight while writing. - return PartialMnemonicWithTypo; + return WalletEnums::PartialMnemonicWithTypo; break; } // if we get to this point in the loop then we have a real word that we found in the dictionary. @@ -833,13 +833,13 @@ FloweePay::StringType FloweePay::identifyString(const QString &string) const space = space2; } while (space != -1); if (firstWord >= 0) - return PartialMnemonic; + return WalletEnums::PartialMnemonic; } catch (const std::exception &e) { // probably deployment issues (faulty word list) logFatal() << e; - return MissingLexicon; + return WalletEnums::MissingLexicon; } - return Unknown; + return WalletEnums::Unknown; } NewWalletConfig* FloweePay::createNewBasicWallet(const QString &walletName) diff --git a/FloweePay.h b/FloweePay.h index 7cfd3b2..e54124e 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -20,6 +20,7 @@ #include "NotificationManager.h" #include "PriceHistoryDataProvider.h" +#include "WalletEnums.h" #include @@ -65,18 +66,6 @@ class FloweePay : public QObject, WorkerThreads, P2PNetInterface Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) public: - enum StringType { - Unknown = 0, - PrivateKey, - CashPKH, - CashSH, - LegacyPKH, - LegacySH, - PartialMnemonic, - PartialMnemonicWithTypo, - CorrectMnemonic, - MissingLexicon - }; enum UnitOfBitcoin { BCH, MilliBCH, @@ -151,7 +140,7 @@ public: Q_INVOKABLE bool checkDerivation(const QString &path) const; /// take a bitcoin-address and identify the type. - Q_INVOKABLE FloweePay::StringType identifyString(const QString &string) const; + Q_INVOKABLE WalletEnums::StringType identifyString(const QString &string) const; /// return a string version of the \a unit name. tBCH for instance. Q_INVOKABLE QString nameOfUnit(FloweePay::UnitOfBitcoin unit) const; @@ -231,8 +220,6 @@ public: Q_INVOKABLE void copyToClipboard(const QString &text); Q_INVOKABLE void openInExplorer(const QString &text); - Q_ENUM(StringType UnitOfBitcoin) - QString version() const; QString libsVersion() const; diff --git a/PaymentDetailOutput.cpp b/PaymentDetailOutput.cpp index 183fb66..cea7494 100644 --- a/PaymentDetailOutput.cpp +++ b/PaymentDetailOutput.cpp @@ -121,7 +121,7 @@ void PaymentDetailOutput::setAddress(const QString &address_) std::string encodedAddress; switch (FloweePay::instance()->identifyString(address)) { - case FloweePay::LegacyPKH: { + case WalletEnums::LegacyPKH: { CBase58Data legacy; auto ok = legacy.SetString(m_address.toStdString()); assert(ok); @@ -132,13 +132,13 @@ void PaymentDetailOutput::setAddress(const QString &address_) encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); break; } - case FloweePay::CashPKH: { + case WalletEnums::CashPKH: { auto c = CashAddress::decodeCashAddrContent(m_address.toStdString(), chainPrefixCopy); assert (!c.hash.empty() && c.type == CashAddress::PUBKEY_TYPE); encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); break; } - case FloweePay::LegacySH: { + case WalletEnums::LegacySH: { CBase58Data legacy; auto ok = legacy.SetString(m_address.toStdString()); assert(ok); @@ -149,7 +149,7 @@ void PaymentDetailOutput::setAddress(const QString &address_) encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); break; } - case FloweePay::CashSH: { + case WalletEnums::CashSH: { auto c = CashAddress::decodeCashAddrContent(m_address.toStdString(), chainPrefixCopy); assert (!c.hash.empty() && c.type == CashAddress::SCRIPT_TYPE); encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); diff --git a/WalletEnums.cpp b/WalletEnums.cpp new file mode 100644 index 0000000..ca8bb17 --- /dev/null +++ b/WalletEnums.cpp @@ -0,0 +1,23 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "WalletEnums.h" + +WalletEnums::WalletEnums(QObject *parent) + : QObject(parent) +{ +} diff --git a/WalletEnums.h b/WalletEnums.h new file mode 100644 index 0000000..d6f6fd2 --- /dev/null +++ b/WalletEnums.h @@ -0,0 +1,45 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef WALLETENUMS_H +#define WALLETENUMS_H + +#include + +class WalletEnums : public QObject +{ + Q_OBJECT +public: + WalletEnums(QObject *parent = nullptr); + + enum StringType { + Unknown = 0, + PrivateKey, + CashPKH, + CashSH, + LegacyPKH, + LegacySH, + PartialMnemonic, + PartialMnemonicWithTypo, + CorrectMnemonic, + MissingLexicon + }; + + Q_ENUM(StringType UnitOfBitcoin) +}; + +#endif diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 54b9a8c..509e2b8 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -27,9 +27,9 @@ GridLayout { rowSpacing: 10 property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Bitcoin.PrivateKey || typedData === Bitcoin.CorrectMnemonic; - property bool isMnemonic: typedData === Bitcoin.CorrectMnemonic || typedData === Bitcoin.PartialMnemonic || typedData === Bitcoin.PartialMnemonicWithTypo; - property bool isPrivateKey: typedData === Bitcoin.PrivateKey + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool isPrivateKey: typedData === Wallet.PrivateKey Label { text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") @@ -49,7 +49,7 @@ GridLayout { Label { id: feedback text: importAccount.finished ? "✔" : " " - color: Bitcoin.useDarkSkin ? "#37be2d" : "green" + color: Pay.useDarkSkin ? "#37be2d" : "green" font.pixelSize: 24 Layout.alignment: Qt.AlignTop } @@ -68,16 +68,16 @@ GridLayout { Label { id: detectedType - color: typedData === Bitcoin.PartialMnemonicWithTypo ? "red" : feedback.color + color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color text: { var typedData = importAccount.typedData - if (typedData === Bitcoin.PrivateKey) + if (typedData === Wallet.PrivateKey) return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Bitcoin.CorrectMnemonic) + if (typedData === Wallet.CorrectMnemonic) return qsTr("BIP 39 seed-phrase", "description of type") - if (typedData === Bitcoin.PartialMnemonicWithTypo) + if (typedData === Wallet.PartialMnemonicWithTypo) return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Bitcoin.MissingLexicon) + if (typedData === Wallet.MissingLexicon) return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this return "" } diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 305f936..0803835 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -489,8 +489,8 @@ Item { Flowee.TextField { id: destination focus: true - property bool addressOk: (addressType === Bitcoin.CashPKH || addressType === Bitcoin.CashSH) - || (paymentDetail.forceLegacyOk && (addressType === Bitcoin.LegacySH || addressType === Bitcoin.LegacyPKH)) + property bool addressOk: (addressType === Wallet.CashPKH || addressType === Wallet.CashSH) + || (paymentDetail.forceLegacyOk && (addressType === Wallet.LegacySH || addressType === Wallet.LegacyPKH)) property var addressType: Pay.identifyString(text); Layout.fillWidth: true Layout.columnSpan: 3 @@ -597,7 +597,7 @@ Item { Item { id: warningArea // BTC address entered warning. - visible: (destination.addressType === Bitcoin.LegacySH || destination.addressType === Bitcoin.LegacyPKH) + visible: (destination.addressType === Wallet.LegacySH || destination.addressType === Wallet.LegacyPKH) && paymentDetail.forceLegacyOk === false; width: parent.width - 40 diff --git a/main.cpp b/main.cpp index 2cbfe42..81ea46d 100644 --- a/main.cpp +++ b/main.cpp @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) qapp.setApplicationVersion("2022.09.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); - qmlRegisterType("Flowee.org.pay", 1, 0, "Bitcoin"); + qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); -- 2.54.0 From 73d8eae31a3c69f242a1b48c940b6b1441b05135 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 13:41:12 +0100 Subject: [PATCH 0066/1428] Add filter to wallethistorymodel. --- WalletEnums.h | 10 ++++++++++ WalletHistoryModel.cpp | 31 ++++++++++++++++++++++++++++++- WalletHistoryModel.h | 13 ++++++++++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/WalletEnums.h b/WalletEnums.h index d6f6fd2..4bf5f2d 100644 --- a/WalletEnums.h +++ b/WalletEnums.h @@ -39,6 +39,16 @@ public: MissingLexicon }; + enum Include { + IncludeNothing = 0, + IncludeRejected = 1, + IncludeUnconfirmed = 2, + IncludeConfirmed = 4, + + IncludeAll = IncludeRejected | IncludeUnconfirmed | IncludeConfirmed + }; + Q_DECLARE_FLAGS(Includes, Include) + Q_FLAG(Includes) Q_ENUM(StringType UnitOfBitcoin) }; diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index a11184c..3ccf81f 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include #include +#include WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) : QAbstractListModel(parent), @@ -167,6 +168,7 @@ void WalletHistoryModel::transactionChanged(int txIndex) void WalletHistoryModel::createMap() { + m_recreateTriggered = false; m_rowsProxy.clear(); m_rowsProxy.resize(m_wallet->m_walletTransactions.size()); @@ -177,10 +179,37 @@ void WalletHistoryModel::createMap() int i = m_rowsProxy.size() - 1; for (const auto &iter : m_wallet->m_walletTransactions) { assert(i >= 0); + if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) + && iter.second.isUnconfirmed()) + continue; + if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) + && iter.second.isRejected()) + continue; + if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) + && !iter.second.isUnconfirmed()) + continue; m_rowsProxy[i--] = iter.first; } } +const QFlags &WalletHistoryModel::includeFlags() const +{ + return m_includeFlags; +} + +void WalletHistoryModel::setIncludeFlags(const QFlags &flags) +{ + if (m_includeFlags == flags) + return; + m_includeFlags = flags; + emit includeFlagsChanged(); + + if (m_recreateTriggered) + return; + m_recreateTriggered = true; + QTimer::singleShot(0, this, SLOT(createMap())); +} + int WalletHistoryModel::lastSyncIndicator() const { return m_lastSyncIndicator; diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index 8cf10c1..87a9ffb 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #define WALLETHSTORYMODEL_H #include +#include "WalletEnums.h" class Wallet; @@ -26,6 +27,7 @@ class WalletHistoryModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int lastSyncIndicator READ lastSyncIndicator WRITE setLastSyncIndicator RESET resetLastSyncIndicator NOTIFY lastSyncIndicatorChanged) + Q_PROPERTY(QFlags includeFlags READ includeFlags WRITE setIncludeFlags NOTIFY includeFlagsChanged) public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); @@ -59,19 +61,24 @@ public: void setLastSyncIndicator(int x); void resetLastSyncIndicator(); + const QFlags &includeFlags() const; + void setIncludeFlags(const QFlags &flags); + signals: void lastSyncIndicatorChanged(); + void includeFlagsChanged(); private slots: void appendTransactions(int firstNew, int count); void transactionChanged(int txIndex); - -private: void createMap(); +private: QVector m_rowsProxy; Wallet *m_wallet; + QFlags m_includeFlags = WalletEnums::IncludeAll; int m_lastSyncIndicator = 0; + bool m_recreateTriggered = false; }; #endif -- 2.54.0 From e426188f955ae3f814902cdfa32f634574bdeed6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 13:43:04 +0100 Subject: [PATCH 0067/1428] Boring GUI updates Add tabbar icon, fix font usage, add basic history list. Show wallet balance with the right label. --- guis/mobile.qrc | 3 + guis/mobile/AccountHistory.qml | 145 ++++++++++++++++++--------------- guis/mobile/IconButton.qml | 49 +++++++++++ guis/mobile/MainViewBase.qml | 20 ++++- guis/mobile/Page.qml | 2 +- guis/mobile/main.qml | 16 +++- 6 files changed, 164 insertions(+), 71 deletions(-) create mode 100644 guis/mobile/IconButton.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 4898a6a..d67699b 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -5,6 +5,8 @@ mobile/images/back-arrow.svg mobile/images/maslenica.svg mobile/images/moon.svg + mobile/images/homeButtonIcon.svg + mobile/images/homeButtonIcon-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js @@ -15,5 +17,6 @@ mobile/MainView.qml mobile/AccountHistory.qml mobile/Loading.qml + mobile/IconButton.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 706e359..278c65a 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 @@ -5,7 +22,8 @@ import "../Flowee" as Flowee Flickable { id: accountHistory - property string icon: "qrc:/bla" // TODO make icon and show on tab + property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string title: qsTr("Home") contentHeight: column.height + 20 clip: true @@ -107,6 +125,11 @@ Flickable { delegate: Item { width: extraWallets.width height: accountName.height * 2 + 12 + Rectangle { + color: mainWindow.palette.button + anchors.fill: parent + visible: modelData === portfolio.current + } Flowee.Label { id: accountName y: 6 @@ -122,14 +145,16 @@ Flickable { Flowee.Label { anchors.top: lastActive.top anchors.left: lastActive.right - text: "today"; + text: Pay.formatDate(modelData.lastMinedTransaction) font.pointSize: mainWindow.font.pointSize * 0.8 font.bold: false } - Flowee.Label { + Flowee.BitcoinAmountLabel { anchors.right: parent.right anchors.top: accountName.top - text: "€ 123.00"; + showFiat: true + value: modelData.balanceConfirmed + modelData.balanceUnconfirmed + colorize: false } MouseArea { anchors.fill: parent @@ -161,77 +186,69 @@ Flickable { } } } - Flowee.Label { - id: balanceLabel - text: "€ 100"; + Flowee.BitcoinAmountLabel { + opacity: Pay.hideBalance ? 0.2 : 1 + value: { + if (Pay.hideBalance) + return 88888888; + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + } + colorize: false } - GridLayout { + Row { width: parent.width - columns: 3 - - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "#00000000" - border.color: "yellow" - border.width: 1 + height: 60 + IconButton { + width: parent.width / 3 + text: qsTr("Send Money") } - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "#00000000" - border.color: "yellow" - border.width: 1 + IconButton { + width: parent.width / 3 + text: qsTr("Scheduled") } - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: 60 - height: 60 - radius: 30 - color: "#00000000" - border.color: "yellow" - border.width: 1 - } - Flowee.Label { - text: "Send Money" - horizontalAlignment: Qt.AlignHCenter - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - } - Flowee.Label { - text: "Scheduled" - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Qt.AlignHCenter - Layout.fillWidth: true - } - Flowee.Label { - text: "Receive" - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Qt.AlignHCenter - Layout.fillWidth: true + IconButton { + width: parent.width / 3 + text: qsTr("Receive") } } - Item { width: 1; height: 10 } // spacer + /* TODO + "Is archive" / "Unrchive"" - Flowee.Label { - text: "Earlier this month" - } - Item { + Is Encryopted / Decrypt + */ + ListView { + id: activityVIew + model: portfolio.current.transactions + clip: true width: parent.width - height: 100 - Rectangle { - width: parent.width - 30 - x: 15 - radius: 10 - height: 100 - color: mainWindow.palette.window + delegate: Item { + width: activityVIew.width + height: 40 } + + ScrollBar.vertical: Flowee.ScrollThumb { + id: thumb + minimumSize: 20 / activityView.height + visible: size < 0.9 + preview: Rectangle { + width: label.width + 12 + height: label.height + 12 + radius: 5 + color: label.palette.dark + QQC2.Label { + id: label + anchors.centerIn: parent + color: palette.light + text: isLoading || activityView.model === null ? "" : activityView.model.dateForItem(thumb.position); + } + } + } + Keys.forwardTo: Flowee.ListViewKeyHandler { + target: activityView + } + } } } diff --git a/guis/mobile/IconButton.qml b/guis/mobile/IconButton.qml new file mode 100644 index 0000000..42bdccd --- /dev/null +++ b/guis/mobile/IconButton.qml @@ -0,0 +1,49 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: root + height: 60 + label.height + 10 + width: height + signal clicked; + property alias text: label.text + + Rectangle { + x: (parent.width - width) / 2 + width: 60 + height: 60 + radius: 30 + color: "#00000000" + border.color: "yellow" + border.width: 1 + } + Flowee.Label { + id: label + wrapMode: Text.WordWrap + width: parent.width + anchors.bottom: parent.bottom + horizontalAlignment: Text.AlignHCenter + } + + MouseArea { + anchors.fill: parent + onClicked: root.clicked() + } +} diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4d75865..ff3e291 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -111,12 +111,26 @@ QQC2.Control { Repeater { model: stack.children.length - delegate: Item { + delegate: Rectangle { height: 80 width: root.width / stack.children.length; + color: { + modelData == root.currentIndex + ? root.palette.button + : root.palette.base + } + Image { + source: stack.children[modelData].icon + width: 35 + height: 35 + y: 12 + anchors.horizontalCenter: parent.horizontalCenter + } Flowee.Label { - text: modelData + 1 - anchors.centerIn: parent + text: stack.children[modelData].title + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 } MouseArea { anchors.fill: parent diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 3d1966f..dd9ece2 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -49,7 +49,7 @@ QQC2.Control { } } - Text { + QQC2.Label { id: headerLabel color: "white" anchors.centerIn: parent diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index de2aacd..03297d6 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -65,19 +65,27 @@ ApplicationWindow { Rectangle { id: clickFeedback - color: "red" + color: mainWindow.floweeGreen opacity: 0.3 width: 70 height: 70 radius: 35 visible: opacity > 0 + Rectangle { + width: 7 + height: 7 + anchors.centerIn: parent + } + Timer { + id: fadeTimer running: parent.visible interval: 50 + repeat: true onTriggered: clickFeedback.opacity = 0 } - Behavior on opacity { NumberAnimation { duration: 150 } } + Behavior on opacity { NumberAnimation { duration: 100 } } } MouseArea { @@ -85,9 +93,11 @@ ApplicationWindow { propagateComposedEvents: true onClicked: (event) => { event.accepted = false; + fadeTimer.stop(); clickFeedback.x = event.x - clickFeedback.width / 2; clickFeedback.y = event.y - clickFeedback.height / 2; - clickFeedback.opacity = 0.6 + clickFeedback.opacity = 0.4 + fadeTimer.start(); } } } -- 2.54.0 From 7b9c733a62c1a3869b107983f39399b4a35dd872 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 13:52:27 +0100 Subject: [PATCH 0068/1428] Simplify --- WalletHistoryModel.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index 3ccf81f..d02e7cd 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -170,15 +170,11 @@ void WalletHistoryModel::createMap() { m_recreateTriggered = false; m_rowsProxy.clear(); - m_rowsProxy.resize(m_wallet->m_walletTransactions.size()); + m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. - - // the simplest form; reverse order. This assumes the last entry is the newest one - int i = m_rowsProxy.size() - 1; for (const auto &iter : m_wallet->m_walletTransactions) { - assert(i >= 0); if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && iter.second.isUnconfirmed()) continue; @@ -188,8 +184,10 @@ void WalletHistoryModel::createMap() if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) && !iter.second.isUnconfirmed()) continue; - m_rowsProxy[i--] = iter.first; + m_rowsProxy.push_back(iter.first); } + // the simplest form; reverse order. This assumes the last entry is the newest one + std::reverse(m_rowsProxy.begin(), m_rowsProxy.end()); } const QFlags &WalletHistoryModel::includeFlags() const -- 2.54.0 From d7211ae8d7dba40810e5296a5e16118f65fb7b49 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 18:15:33 +0100 Subject: [PATCH 0069/1428] Add concept of groups to WalletHistoryModel Grouping transactions based on things like "last week" "July" is now possible in the UI with these new properties. --- WalletEnums.h | 21 +++++- WalletHistoryModel.cpp | 162 ++++++++++++++++++++++++++++++++++++++++- WalletHistoryModel.h | 10 +++ 3 files changed, 191 insertions(+), 2 deletions(-) diff --git a/WalletEnums.h b/WalletEnums.h index 4bf5f2d..9f156aa 100644 --- a/WalletEnums.h +++ b/WalletEnums.h @@ -38,6 +38,7 @@ public: CorrectMnemonic, MissingLexicon }; + Q_ENUM(StringType) enum Include { IncludeNothing = 0, @@ -49,7 +50,25 @@ public: }; Q_DECLARE_FLAGS(Includes, Include) Q_FLAG(Includes) - Q_ENUM(StringType UnitOfBitcoin) + + /// used by the WalletHistoryModel to group items visually + enum ItemGroupInfo { + GroupStart, + GroupMiddle, + GroupEnd, + Ungrouped + }; + Q_ENUM(ItemGroupInfo) + // Grouping period + enum GroupingPeriod { + Today, + Yesterday, + EarlierThisWeek, // this week, but we grouped some in the previous category(s) + Week, + EarlierThisMonth, // this month, but we grouped some in the previous category(s) + Month + }; + Q_ENUM(GroupingPeriod) }; #endif diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index d02e7cd..fb6fbca 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -25,6 +25,122 @@ #include #include +namespace { + +struct TransactionGroup; +class TransactionGroupingResolver +{ +public: + std::vector m_groups; + + void add(int index, int blockheight); +}; + +struct TransactionGroup +{ + enum GroupingPeriod { + Today, + Yesterday, + EarlierThisWeek, // this week, but we grouped some in the previous category(s) + Week, + EarlierThisMonth, // this month, but we grouped some in the previous category(s) + Month, + Unset + }; + + GroupingPeriod period = Unset; + int startTxIndex = -1; + int endTxIndex = -1; + uint32_t startTime = 0; + uint32_t endTime = 0; + + bool add(int index, uint32_t timestamp); +}; + +void TransactionGroupingResolver::add(int index, int blockheight) +{ + if (m_groups.empty()) + m_groups.push_back(TransactionGroup()); + + if (blockheight <= 0) { + assert(m_groups.size() == 1); + // not (yet) confirmed. + m_groups.back().add(index, 0); + return; + } + + const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); + uint32_t timestamp = bc.block(blockheight).nTime; + assert(timestamp > 0); + + if (!m_groups.back().add(index, timestamp)) { + TransactionGroup newGroup; + newGroup.period = m_groups.back().period; + bool ok = newGroup.add(index, timestamp); + assert (ok); + m_groups.push_back(newGroup); + } +} + +/* + * Attempt to add a transaction to this group. + */ +bool TransactionGroup::add(int index, uint32_t timestamp) +{ + if (startTxIndex == -1) { + endTxIndex = index; + + // first one in this group. Now we need to decide which period we area actually looking at. + QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); + int days = 0; + + if (period != Month) { + QDate today = QDate::currentDate(); + if (date == today) { + period = Today; + days = 1; + } + else if (date == today.addDays(-1)) { + period = Yesterday; + date = date.addDays(-1); + days = 1; + } + else if (date > today.addDays(-1 * today.dayOfWeek() + 1)) { + // this week + period = period == Unset ? Week : EarlierThisWeek; + date = date.addDays(-1 * date.dayOfWeek()); + days = 7; + } + else if (date > today.addDays(-1 * today.day() + 1)) { + // this month + if (period == Unset) + period = Month; + else // if (period != Month) + period = EarlierThisMonth; + date = date.addDays(-1 * date.day() + 1); + days = date.daysInMonth(); + } + } + if (days == 0) { // any (other) month + date = date.addDays(-1 * date.day() + 1); + days = date.daysInMonth(); + } + assert(days > 0); + const QDateTime dt(date, QTime()); + startTime = dt.toSecsSinceEpoch(); + endTime = dt.addDays(days).toSecsSinceEpoch() - 1; + } + else if (timestamp < startTime) { + // doesn't fit in our time-period. + return false; + } + startTxIndex = index; + return true; +} + +}; + + WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) : QAbstractListModel(parent), m_wallet(wallet) @@ -106,8 +222,29 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(item.isCashFusionTx); case Comment: return QVariant(item.userComment); + case ItemGroupInfo: + for (const auto &group : m_groups) { + const int txIndex = itemIter->first; + const bool start = group.startTxIndex == txIndex; + const bool end = group.endTxIndex == txIndex; + if (start && end) + return WalletEnums::Ungrouped; + // notice that since we invert the ordering, the end/start get inverted too + if (start) + return WalletEnums::GroupEnd; + if (end) + return WalletEnums::GroupStart; + } + return WalletEnums::GroupMiddle; + case ItemGroupType: + for (const auto &group : m_groups) { + const int txIndex = itemIter->first; + if (txIndex >= group.startTxIndex && txIndex <= group.endTxIndex) { + return group.period; + } + } + assert(false); // internal data inconsistency } - return QVariant(); } @@ -125,6 +262,8 @@ QHash WalletHistoryModel::roleNames() const answer[IsCoinbase] = "isCoinbase"; answer[IsCashFusion] = "isCashFusion"; answer[Comment] = "comment"; + answer[ItemGroupInfo] = "grouping"; + answer[ItemGroupType] = "groupType"; // answer[SavedFiatRate] = "savedFiatRate"; return answer; @@ -172,6 +311,8 @@ void WalletHistoryModel::createMap() m_rowsProxy.clear(); m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); + TransactionGroupingResolver resolver; + // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. for (const auto &iter : m_wallet->m_walletTransactions) { @@ -188,6 +329,25 @@ void WalletHistoryModel::createMap() } // the simplest form; reverse order. This assumes the last entry is the newest one std::reverse(m_rowsProxy.begin(), m_rowsProxy.end()); + + // Last, resolve grouping + for (int id : m_rowsProxy) { + auto iter = m_wallet->m_walletTransactions.find(id); + assert(iter != m_wallet->m_walletTransactions.end()); + resolver.add(iter->first, iter->second.minedBlockHeight); + } + + // copy out the resolved groups to the lower-overhead ones. + m_groups.clear(); + for (auto g : resolver.m_groups) { + GroupInfo groupInfo; + groupInfo.startTxIndex = g.startTxIndex; + groupInfo.endTxIndex = g.endTxIndex; + assert(g.period != TransactionGroup::Unset); + static_assert(static_cast(TransactionGroup::Week) == WalletEnums::Week); + groupInfo.period = static_cast(g.period); + m_groups.push_back(groupInfo); + } } const QFlags &WalletHistoryModel::includeFlags() const diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index 87a9ffb..d631546 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -43,6 +43,8 @@ public: IsCoinbase, IsCashFusion, Comment, + ItemGroupInfo, + ItemGroupType, // SavedFiatRate, // TODO }; @@ -77,6 +79,14 @@ private: QVector m_rowsProxy; Wallet *m_wallet; QFlags m_includeFlags = WalletEnums::IncludeAll; + + struct GroupInfo { + WalletEnums::GroupingPeriod period; + int startTxIndex; + int endTxIndex; + }; + std::vector m_groups; + int m_lastSyncIndicator = 0; bool m_recreateTriggered = false; }; -- 2.54.0 From 553be15c8d3f1917ad09ede2b3000a5b7a029f50 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 21:19:25 +0100 Subject: [PATCH 0070/1428] Add group-cache for faster lookup and a public getter --- WalletHistoryModel.cpp | 96 ++++++++++++++++++++++++++++++------------ WalletHistoryModel.h | 13 +++++- 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index fb6fbca..2fca925 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -113,15 +113,13 @@ bool TransactionGroup::add(int index, uint32_t timestamp) } else if (date > today.addDays(-1 * today.day() + 1)) { // this month - if (period == Unset) - period = Month; - else // if (period != Month) - period = EarlierThisMonth; + period = EarlierThisMonth; date = date.addDays(-1 * date.day() + 1); days = date.daysInMonth(); } } if (days == 0) { // any (other) month + period = Month; date = date.addDays(-1 * date.day() + 1); days = date.daysInMonth(); } @@ -172,8 +170,9 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const assert(m_rowsProxy.size() > index.row()); assert(index.row() >= 0); - // logDebug() << " getting" << index.row() << "=>" << m_rowsProxy.at(index.row()); - auto itemIter = m_wallet->m_walletTransactions.find(m_rowsProxy.at(index.row())); + const int txIndex = m_rowsProxy.at(index.row()); + // logDebug() << " getting" << index.row() << "=>" << txIndex; + auto itemIter = m_wallet->m_walletTransactions.find(txIndex); assert(itemIter != m_wallet->m_walletTransactions.end()); if (itemIter == m_wallet->m_walletTransactions.end()) return QVariant(); @@ -222,32 +221,42 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(item.isCashFusionTx); case Comment: return QVariant(item.userComment); - case ItemGroupInfo: - for (const auto &group : m_groups) { - const int txIndex = itemIter->first; - const bool start = group.startTxIndex == txIndex; - const bool end = group.endTxIndex == txIndex; - if (start && end) - return WalletEnums::Ungrouped; - // notice that since we invert the ordering, the end/start get inverted too - if (start) - return WalletEnums::GroupEnd; - if (end) - return WalletEnums::GroupStart; - } + case ItemGroupInfo: { + const auto &group = m_groups.at(groupIdForTxIndex(txIndex)); + const bool start = group.startTxIndex == txIndex; + const bool end = group.endTxIndex == txIndex; + if (start && end) + return WalletEnums::Ungrouped; + // notice that since we invert the ordering, the end/start get inverted too + if (start) + return WalletEnums::GroupEnd; + if (end) + return WalletEnums::GroupStart; return WalletEnums::GroupMiddle; - case ItemGroupType: - for (const auto &group : m_groups) { - const int txIndex = itemIter->first; - if (txIndex >= group.startTxIndex && txIndex <= group.endTxIndex) { - return group.period; - } - } - assert(false); // internal data inconsistency } + case ItemGroupType: + return m_groups.at(groupIdForTxIndex(txIndex)).period; + case ItemGroupId: + return groupIdForTxIndex(txIndex); + } + return QVariant(); } +int WalletHistoryModel::groupIdForTxIndex(int txIndex) const +{ + // Method is const because the cache is mutable. + if (m_groupCache.txIndex != txIndex) { + for (size_t i = 0; i < m_groups.size(); ++i) { + if (txIndex >= m_groups.at(i).startTxIndex && txIndex <= m_groups.at(i).endTxIndex) { + m_groupCache = { txIndex, (int) i }; + break; + } + } + } + assert(m_groupCache.txIndex == txIndex); + return m_groupCache.groupId; +} QHash WalletHistoryModel::roleNames() const { @@ -262,6 +271,7 @@ QHash WalletHistoryModel::roleNames() const answer[IsCoinbase] = "isCoinbase"; answer[IsCashFusion] = "isCashFusion"; answer[Comment] = "comment"; + answer[ItemGroupId] = "groupId"; answer[ItemGroupInfo] = "grouping"; answer[ItemGroupType] = "groupType"; // answer[SavedFiatRate] = "savedFiatRate"; @@ -269,6 +279,37 @@ QHash WalletHistoryModel::roleNames() const return answer; } +QString WalletHistoryModel::grouypingPeriod(int groupId) const +{ + if (groupId < 0 || groupId >= m_groups.size()) + throw std::runtime_error("Out of bounds"); + + switch (m_groups.at(groupId).period) { + case WalletEnums::Today: + return tr("Today"); + case WalletEnums::Yesterday: + return tr("Yesterday"); + case WalletEnums::EarlierThisWeek: + return tr("Earlier this week"); + case WalletEnums::Week: + return tr("This week"); + case WalletEnums::EarlierThisMonth: + return tr("Earlier this month"); + case WalletEnums::Month: + default: { + auto txIndex = m_groups.at(groupId).startTxIndex; + auto itemIter = m_wallet->m_walletTransactions.find(txIndex); + assert(itemIter != m_wallet->m_walletTransactions.end()); + + const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); + uint32_t timestamp = bc.block(itemIter->second.minedBlockHeight).nTime; + assert(timestamp > 0); + QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); + return date.toString("MMMM"); + } + } +} + QString WalletHistoryModel::dateForItem(qreal offset) const { if (std::isnan(offset)) @@ -348,6 +389,7 @@ void WalletHistoryModel::createMap() groupInfo.period = static_cast(g.period); m_groups.push_back(groupInfo); } + m_groupCache = { -1 , 0 }; } const QFlags &WalletHistoryModel::includeFlags() const diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index d631546..74377e0 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -43,8 +43,9 @@ public: IsCoinbase, IsCashFusion, Comment, - ItemGroupInfo, - ItemGroupType, + ItemGroupId, ///< Is an int indicating which group we are in. + ItemGroupInfo, ///< Is an enum WalletEnums::ItemGroupInfo to help with painting outlines. + ItemGroupType, ///< Is an enum WalletEnums::GroupingPeriod to help setting a group-title // SavedFiatRate, // TODO }; @@ -52,6 +53,8 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; + Q_INVOKABLE QString grouypingPeriod(int groupId) const; + /** * Return a formatted date for an item in our list. * @param offset a real number that is between 0.0 and 1.0, indicating the location of the item. @@ -86,6 +89,12 @@ private: int endTxIndex; }; std::vector m_groups; + struct GroupCache { + int txIndex; + int groupId; + }; + int groupIdForTxIndex(int txIndex) const; + mutable GroupCache m_groupCache = { -1, 0 }; int m_lastSyncIndicator = 0; bool m_recreateTriggered = false; -- 2.54.0 From ebfb82d36155f8299443c921353f9e631fb0dd93 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 21:20:00 +0100 Subject: [PATCH 0071/1428] Make the formatDate method drop the year if possible --- FloweePay.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/FloweePay.cpp b/FloweePay.cpp index 1fd8704..5d54462 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -412,6 +412,24 @@ QString FloweePay::formatDate(QDateTime date) const if (days < 9) // return day of the week return date.toString("dddd"); } + + if (date.date().year() == QDate::currentDate().year()) { + static QString shortFormat; + if (shortFormat.isEmpty()) { + // We basically just need to know if this locale has months first or not + int m = format.indexOf('m'); + if (m == -1) + m = format.indexOf('M'); + int d = format.indexOf('d'); + if (d == -1) + d = format.indexOf('D'); + if (m < d) + shortFormat = "d MMM"; + else + shortFormat = "MMM d"; + } + return date.toString(shortFormat); + } return date.toString(format); } -- 2.54.0 From 7def5fd4e9102d42dedcbb1fb4e937e532bfe624 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Nov 2022 21:20:29 +0100 Subject: [PATCH 0072/1428] Make the account history tab mostly functional. --- guis/mobile/AccountHistory.qml | 539 ++++++++++++++++++++------------- guis/mobile/MainViewBase.qml | 3 +- guis/mobile/main.qml | 2 + 3 files changed, 327 insertions(+), 217 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 278c65a..c7826b2 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -19,237 +19,346 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 as QQC2 import QtQuick.Layouts 2.15 import "../Flowee" as Flowee +import Flowee.org.pay; + +ListView { + id: root -Flickable { - id: accountHistory property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); property string title: qsTr("Home") - contentHeight: column.height + 20 - clip: true - Rectangle { - // background color "base", to indicate these are widgets here. - anchors.fill: parent - color: mainWindow.palette.base - ColumnLayout { - id: column - spacing: 20 - width: parent.width - 20 - x: 10 - y: 10 + /* + The structure here is a bit funny. + But we want to have the entire page scroll in order to show all the transactions we can. + To avoid a mess of two scroll-areas (of Flickables) we simply make the top part into a + header of the listview. + */ + header: ColumnLayout { + id: column + spacing: 20 + width: parent.width - 20 + x: 10 + y: 10 - Item { - id: smallAccountSelector + Item { + id: smallAccountSelector + width: parent.width + height: walletSelector.height + z: 10 + + property bool open: false + + visible: { + // if there is only an initial, not user-owned wallet, there is nothing to show here. + return portfolio.current.isUserOwned + } + + Rectangle { + id: walletSelector + height: currentWalletName.height + 14 + width: parent.width + 5 + color: "#00000000" + border.color: mainWindow.palette.button + border.width: hasMultipleWallets ? 0.7 : 0 + x: -5 + + property bool hasMultipleWallets: portfolio.accounts.length > 1 + + Flowee.Label { + x: 5 + id: currentWalletName + text: portfolio.current.name + anchors.verticalCenter: parent.verticalCenter + } + Flowee.ArrowPoint { + id: arrowPoint + visible: parent.hasMultipleWallets + color: mainWindow.palette.text + anchors.right: parent.right + anchors.rightMargin: 10 + rotation: 90 + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + enabled: parent.hasMultipleWallets + onClicked: { + smallAccountSelector.open = !smallAccountSelector.open + // move focus so the 'back' button will close it. + extraWallets.forceActiveFocus(); + } + } + } + + Rectangle { + id: extraWallets width: parent.width - height: walletSelector.height - z: 10 - - property bool open: false - - visible: { - // if there is only an initial, not user-owned wallet, there is nothing to show here. - return portfolio.current.isUserOwned - } - - Rectangle { - id: walletSelector - height: currentWalletName.height + 14 - width: parent.width + 5 - color: "#00000000" - border.color: mainWindow.palette.button - border.width: hasMultipleWallets ? 0.7 : 0 - x: -5 - - property bool hasMultipleWallets: portfolio.accounts.length > 1 - - Flowee.Label { - x: 5 - id: currentWalletName - text: portfolio.current.name - anchors.verticalCenter: parent.verticalCenter - } - Flowee.ArrowPoint { - id: arrowPoint - visible: parent.hasMultipleWallets - color: mainWindow.palette.text - anchors.right: parent.right - anchors.rightMargin: 10 - rotation: 90 - anchors.verticalCenter: parent.verticalCenter - } - MouseArea { - anchors.fill: parent - enabled: parent.hasMultipleWallets - onClicked: { - smallAccountSelector.open = !smallAccountSelector.open - // move focus so the 'back' button will close it. - extraWallets.forceActiveFocus(); - } + y: walletSelector.height + height: parent.open ? columnLayout.height + 20 : 0 + color: mainWindow.palette.base + Behavior on height { + NumberAnimation { + duration: 200 } } - Rectangle { - id: extraWallets - width: parent.width - y: walletSelector.height - height: parent.open ? columnLayout.height + 20 : 0 - color: mainWindow.palette.base - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - clip: true - visible: height > 0 - focus: true - ColumnLayout { - y: 10 - id: columnLayout - width: parent.width - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - - Repeater { // portfolio holds all our accounts - width: parent.width - model: portfolio.accounts - delegate: Item { - width: extraWallets.width - height: accountName.height * 2 + 12 - Rectangle { - color: mainWindow.palette.button - anchors.fill: parent - visible: modelData === portfolio.current - } - Flowee.Label { - id: accountName - y: 6 - text: modelData.name - } - Flowee.Label { - id: lastActive - anchors.top: accountName.bottom - text: qsTr("last active") + ": " - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.Label { - anchors.top: lastActive.top - anchors.left: lastActive.right - text: Pay.formatDate(modelData.lastMinedTransaction) - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.BitcoinAmountLabel { - anchors.right: parent.right - anchors.top: accountName.top - showFiat: true - value: modelData.balanceConfirmed + modelData.balanceUnconfirmed - colorize: false - } - MouseArea { - anchors.fill: parent - onClicked: { - portfolio.current = modelData - smallAccountSelector.open = false - } - } - } - } - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - } - - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { - event.accepted = true; - smallAccountSelector.open = false - } - } - } - } - Flowee.BitcoinAmountLabel { - opacity: Pay.hideBalance ? 0.2 : 1 - value: { - if (Pay.hideBalance) - return 88888888; - return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed - } - colorize: false - } - - Row { - width: parent.width - height: 60 - IconButton { - width: parent.width / 3 - text: qsTr("Send Money") - } - IconButton { - width: parent.width / 3 - text: qsTr("Scheduled") - } - IconButton { - width: parent.width / 3 - text: qsTr("Receive") - } - } - - /* TODO - "Is archive" / "Unrchive"" - - Is Encryopted / Decrypt - */ - ListView { - id: activityVIew - model: portfolio.current.transactions clip: true - width: parent.width - delegate: Item { - width: activityVIew.width - height: 40 - } + visible: height > 0 + focus: true + ColumnLayout { + y: 10 + id: columnLayout + width: parent.width + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight + } + } - ScrollBar.vertical: Flowee.ScrollThumb { - id: thumb - minimumSize: 20 / activityView.height - visible: size < 0.9 - preview: Rectangle { - width: label.width + 12 - height: label.height + 12 - radius: 5 - color: label.palette.dark - QQC2.Label { - id: label - anchors.centerIn: parent - color: palette.light - text: isLoading || activityView.model === null ? "" : activityView.model.dateForItem(thumb.position); + Repeater { // portfolio holds all our accounts + width: parent.width + model: portfolio.accounts + delegate: Item { + width: extraWallets.width + height: accountName.height * 2 + 12 + Rectangle { + color: mainWindow.palette.button + anchors.fill: parent + visible: modelData === portfolio.current + } + Flowee.Label { + id: accountName + y: 6 + text: modelData.name + } + Flowee.Label { + id: lastActive + anchors.top: accountName.bottom + text: qsTr("last active") + ": " + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.Label { + anchors.top: lastActive.top + anchors.left: lastActive.right + text: Pay.formatDate(modelData.lastMinedTransaction) + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.BitcoinAmountLabel { + anchors.right: parent.right + anchors.top: accountName.top + showFiat: true + value: modelData.balanceConfirmed + modelData.balanceUnconfirmed + colorize: false + } + MouseArea { + anchors.fill: parent + onClicked: { + portfolio.current = modelData + smallAccountSelector.open = false + } + } + } + } + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight } } } - Keys.forwardTo: Flowee.ListViewKeyHandler { - target: activityView - } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + event.accepted = true; + smallAccountSelector.open = false + } + } } } + Flowee.BitcoinAmountLabel { + opacity: Pay.hideBalance ? 0.2 : 1 + value: { + if (Pay.hideBalance) + return 88888888; + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + } + colorize: false + } + + Row { + width: parent.width + height: 60 + IconButton { + width: parent.width / 3 + text: qsTr("Send Money") + } + IconButton { + width: parent.width / 3 + text: qsTr("Scheduled") + } + IconButton { + width: parent.width / 3 + text: qsTr("Receive") + } + } + + /* TODO + "Is archive" / "Unrchive"" + + Is Encryopted / Decrypt + */ + } + + model: portfolio.current.transactions + clip: true + width: parent.width + height: contentHeight + focus: true + reuseItems: true + section.property: "groupId" + section.delegate: Item { + required property int section + height: label.height + 15 + width: root.width + Rectangle { + color: mainWindow.palette.window + anchors.fill: parent + } + Flowee.Label { + id: label + x: 10 + y: 12 + text: portfolio.current.transactions.grouypingPeriod(parent.section) + } + +/* TODO; to-top button + Image { + source: "qrc:/back-arrow.svg" + rotation: 90 + width: 15 + height: 20 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.bottom: parent.bottom + } */ + } + delegate: Item { + id: transactionDelegate + property var grouping: model.grouping + + width: root.width + height: 80 + clip: true + + Rectangle { + width: parent.width - 16 + x: 8 + visible: transactionDelegate.grouping !== Wallet.Ungrouped; + // we always have the rounded circles, but if we should not see them, we move them out of the screen. + height: { + var h = 80 + if (transactionDelegate.grouping !== Wallet.GroupStart) + h += 20; + if (transactionDelegate.grouping !== Wallet.GroupEnd) + h += 20; + return h; + } + y: transactionDelegate.grouping === Wallet.GroupStart ? 0 : -20; + + radius: 20 + color: mainWindow.palette.base + border.width: 1 + border.color: root.palette.highlight + } + + Item { + id: ruler + width: parent.width + height: 6 + anchors.verticalCenter: parent.verticalCenter + } + Rectangle { + width: 45 + height: 45 + radius: 22.5 + x: 20 + anchors.verticalCenter: ruler.verticalCenter + } + + Flowee.Label { + id: commentLabel + anchors.bottom: ruler.top + anchors.right: price.left + anchors.left: parent.left + anchors.leftMargin: 80 + clip: true // TODO wordwrap? + text: { + var comment = model.comment + if (comment !== "") + return comment; + + if (model.isCoinbase) + return qsTr("Miner Reward"); + if (model.isCashFusion) + return qsTr("Cash Fusion"); + if (model.fundsIn === 0) + return qsTr("Received"); + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // then the diff is likely just fees. + return qsTr("Moved"); + return qsTr("Sent"); + } + } + Flowee.Label { + anchors.top: ruler.bottom + anchors.left: commentLabel.left + text: Pay.formatDate(model.date); + } + + Rectangle { + id: price + width: amount.width + 10 + height: amount.height + 10 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.verticalCenter: ruler.verticalCenter + radius: 6 + + property int amountBch: model.fundsOut - model.fundsIn + color: amountBch < 0 ? "#00000000" + : (Flowee.useDarkSkin ? "#1d6828" : "#8cff94") // green background + Flowee.Label { + id: amount + text: Fiat.formattedPrice(parent.amountBch, fiatHistory.historicalPrice(model.date)); + anchors.centerIn: parent + opacity: Math.abs(parent.amountBch) < 2000 ? 0.5 : 1 + } + } + + Rectangle { + visible: transactionDelegate.grouping !== Wallet.GroupEnd + && transactionDelegate.grouping !== Wallet.Ungrouped; + anchors.bottom: parent.bottom + height: 1 + width: parent.width - 16 + x: 8 + color: root.palette.highlight + } + } + Keys.forwardTo: Flowee.ListViewKeyHandler { + target: root } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index ff3e291..fca3771 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -97,7 +97,6 @@ QQC2.Control { y: -5 } } - } Item { @@ -115,7 +114,7 @@ QQC2.Control { height: 80 width: root.width / stack.children.length; color: { - modelData == root.currentIndex + modelData === root.currentIndex ? root.palette.button : root.palette.base } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 03297d6..fcb31e6 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -59,6 +59,7 @@ ApplicationWindow { anchors.fill: parent } + /* Item { id: touchFeedbackOverlay anchors.fill: parent @@ -101,4 +102,5 @@ ApplicationWindow { } } } + */ } -- 2.54.0 From 6c7c0813c08a96a70f7f603096de6c22abd5dcf8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 12:48:11 +0100 Subject: [PATCH 0073/1428] Simplify the sections part of account history --- WalletHistoryModel.cpp | 61 +++++++++++++--------------------- WalletHistoryModel.h | 7 ++-- guis/mobile/AccountHistory.qml | 19 +++++------ 3 files changed, 34 insertions(+), 53 deletions(-) diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index 2fca925..59f2c65 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -234,10 +234,27 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return WalletEnums::GroupStart; return WalletEnums::GroupMiddle; } - case ItemGroupType: - return m_groups.at(groupIdForTxIndex(txIndex)).period; - case ItemGroupId: - return groupIdForTxIndex(txIndex); + case ItemGroupPeriod: + switch (m_groups.at(groupIdForTxIndex(txIndex)).period) { + case WalletEnums::Today: + return tr("Today"); + case WalletEnums::Yesterday: + return tr("Yesterday"); + case WalletEnums::EarlierThisWeek: + return tr("Earlier this week"); + case WalletEnums::Week: + return tr("This week"); + case WalletEnums::EarlierThisMonth: + return tr("Earlier this month"); + case WalletEnums::Month: + default: { + const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); + uint32_t timestamp = bc.block(itemIter->second.minedBlockHeight).nTime; + assert(timestamp > 0); + QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); + return date.toString("MMMM"); + } + } } return QVariant(); @@ -271,45 +288,13 @@ QHash WalletHistoryModel::roleNames() const answer[IsCoinbase] = "isCoinbase"; answer[IsCashFusion] = "isCashFusion"; answer[Comment] = "comment"; - answer[ItemGroupId] = "groupId"; - answer[ItemGroupInfo] = "grouping"; - answer[ItemGroupType] = "groupType"; + answer[ItemGroupInfo] = "groupType"; + answer[ItemGroupPeriod] = "grouping"; // answer[SavedFiatRate] = "savedFiatRate"; return answer; } -QString WalletHistoryModel::grouypingPeriod(int groupId) const -{ - if (groupId < 0 || groupId >= m_groups.size()) - throw std::runtime_error("Out of bounds"); - - switch (m_groups.at(groupId).period) { - case WalletEnums::Today: - return tr("Today"); - case WalletEnums::Yesterday: - return tr("Yesterday"); - case WalletEnums::EarlierThisWeek: - return tr("Earlier this week"); - case WalletEnums::Week: - return tr("This week"); - case WalletEnums::EarlierThisMonth: - return tr("Earlier this month"); - case WalletEnums::Month: - default: { - auto txIndex = m_groups.at(groupId).startTxIndex; - auto itemIter = m_wallet->m_walletTransactions.find(txIndex); - assert(itemIter != m_wallet->m_walletTransactions.end()); - - const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); - uint32_t timestamp = bc.block(itemIter->second.minedBlockHeight).nTime; - assert(timestamp > 0); - QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); - return date.toString("MMMM"); - } - } -} - QString WalletHistoryModel::dateForItem(qreal offset) const { if (std::isnan(offset)) diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index 74377e0..adc5fb0 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -43,9 +43,8 @@ public: IsCoinbase, IsCashFusion, Comment, - ItemGroupId, ///< Is an int indicating which group we are in. - ItemGroupInfo, ///< Is an enum WalletEnums::ItemGroupInfo to help with painting outlines. - ItemGroupType, ///< Is an enum WalletEnums::GroupingPeriod to help setting a group-title + ItemGroupInfo, ///< Is an enum WalletEnums::ItemGroupInfo to help with painting outlines. + ItemGroupPeriod, ///< Is a string describing/identifying the group // SavedFiatRate, // TODO }; @@ -53,8 +52,6 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; - Q_INVOKABLE QString grouypingPeriod(int groupId) const; - /** * Return a formatted date for an item in our list. * @param offset a real number that is between 0.0 and 1.0, indicating the location of the item. diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index c7826b2..78ad79d 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -228,9 +228,8 @@ ListView { height: contentHeight focus: true reuseItems: true - section.property: "groupId" + section.property: "grouping" section.delegate: Item { - required property int section height: label.height + 15 width: root.width Rectangle { @@ -241,7 +240,7 @@ ListView { id: label x: 10 y: 12 - text: portfolio.current.transactions.grouypingPeriod(parent.section) + text: section } /* TODO; to-top button @@ -257,7 +256,7 @@ ListView { } delegate: Item { id: transactionDelegate - property var grouping: model.grouping + property var groupType: model.groupType width: root.width height: 80 @@ -266,17 +265,17 @@ ListView { Rectangle { width: parent.width - 16 x: 8 - visible: transactionDelegate.grouping !== Wallet.Ungrouped; + visible: transactionDelegate.groupType !== Wallet.Ungrouped; // we always have the rounded circles, but if we should not see them, we move them out of the screen. height: { var h = 80 - if (transactionDelegate.grouping !== Wallet.GroupStart) + if (transactionDelegate.groupType !== Wallet.GroupStart) h += 20; - if (transactionDelegate.grouping !== Wallet.GroupEnd) + if (transactionDelegate.groupType !== Wallet.GroupEnd) h += 20; return h; } - y: transactionDelegate.grouping === Wallet.GroupStart ? 0 : -20; + y: transactionDelegate.groupType === Wallet.GroupStart ? 0 : -20; radius: 20 color: mainWindow.palette.base @@ -349,8 +348,8 @@ ListView { } Rectangle { - visible: transactionDelegate.grouping !== Wallet.GroupEnd - && transactionDelegate.grouping !== Wallet.Ungrouped; + visible: transactionDelegate.groupType !== Wallet.GroupEnd + && transactionDelegate.groupType !== Wallet.Ungrouped; anchors.bottom: parent.bottom height: 1 width: parent.width - 16 -- 2.54.0 From ef3498ee67f6d097e6fe0b08762c26eeae7c0780 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 14:11:55 +0100 Subject: [PATCH 0074/1428] Only enable touch-feedback on Android This approach works great for touch events, not so wonderful for mouse events as it stops me being able to swipe on lists with the mouse. So disable this on platforms that don't use touch primarily. --- FloweePay.cpp | 11 +++++++++++ FloweePay.h | 4 ++++ guis/mobile/main.qml | 3 +-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 5d54462..db9d8f2 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -335,6 +335,17 @@ QString FloweePay::basedir() const return m_basedir; } +QString FloweePay::platform() const +{ +#ifdef TARGET_OS_Android + return "Android"; +#endif +#ifdef TARGET_OS_Linux + return "Linux"; +#endif + return "unknown"; // TODO for other platforms check which names CMake uses. +} + QString FloweePay::amountToStringPretty(double price) const { QString answer = FloweePay::amountToString(static_cast(price), m_unit); diff --git a/FloweePay.h b/FloweePay.h index e54124e..dce19a8 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -65,6 +65,7 @@ class FloweePay : public QObject, WorkerThreads, P2PNetInterface Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) + Q_PROPERTY(QString platform READ platform CONSTANT) public: enum UnitOfBitcoin { BCH, @@ -98,6 +99,9 @@ public: /// return the app data location QString basedir() const; + /// returns platform name, Linux / Android / etc + QString platform() const; + /// for a price, in satoshis, return a formatted string in unitName(). Q_INVOKABLE inline QString amountToString(double price) const { return FloweePay::amountToString(static_cast(price), m_unit); diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index fcb31e6..b4a0429 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -59,10 +59,10 @@ ApplicationWindow { anchors.fill: parent } - /* Item { id: touchFeedbackOverlay anchors.fill: parent + visible: Pay.platform === "Android" Rectangle { id: clickFeedback @@ -102,5 +102,4 @@ ApplicationWindow { } } } - */ } -- 2.54.0 From a3831df0d6e04b3524d350839f28d5b6c5807244 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 14:29:54 +0100 Subject: [PATCH 0075/1428] Move data of the menu to a model. --- CMakeLists.txt | 1 + MenuModel.cpp | 71 +++++++++++++++++++++++++++++++++++++ MenuModel.h | 50 ++++++++++++++++++++++++++ guis/mobile/MenuOverlay.qml | 27 ++++++++------ main.cpp | 3 ++ 5 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 MenuModel.cpp create mode 100644 MenuModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 967e793..0e6fe1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set (PAY_SOURCES AddressInfo.cpp BitcoinValue.cpp FloweePay.cpp + MenuModel.cpp NetDataProvider.cpp NetPeer.cpp NewWalletConfig.cpp diff --git a/MenuModel.cpp b/MenuModel.cpp new file mode 100644 index 0000000..5fbea9b --- /dev/null +++ b/MenuModel.cpp @@ -0,0 +1,71 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "MenuModel.h" + +MenuModel::MenuModel(QObject *parent) + : QAbstractListModel{parent}, + m_current(&m_root) +{ + m_root.children.append({tr("Add Wallet "), "NewWallet.qml", {}}); + // m_root.children.append({tr("Accounts"), "AccountsList.qml", {}}); + m_root.children.append({tr("Network Details"), "NetView.qml", {}}); + /* + m_root.children.append({tr("Settings"), "", { + { tr("Settings 1"), "", {} }, + { tr("Settings 2"), "", {} }, + }}); */ + +} + +int MenuModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree + return 0; + + assert(m_current); + return m_current->children.size(); +} + +QVariant MenuModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + assert(index.row() >= 0); + assert(m_current); + assert(m_current->children.size() > index.row()); + const auto &item = m_current->children.at(index.row()); + + switch (role) { + case Name: + return item.name; + case Target: + return item.target; + case HasChildren: + return !item.children.isEmpty(); + } + return QVariant(); +} + +QHash MenuModel::roleNames() const +{ + QHash answer; + answer[Name] = "name"; + answer[Target] = "target"; + answer[HasChildren] = "hasChildren"; + return answer; +} diff --git a/MenuModel.h b/MenuModel.h new file mode 100644 index 0000000..7a76b82 --- /dev/null +++ b/MenuModel.h @@ -0,0 +1,50 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef MENUMODEL_H +#define MENUMODEL_H + +#include + +class MenuModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit MenuModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +private: + enum Roles { + Name, + Target, + HasChildren + }; + + struct MenuItem { + QString name; + QString target; // the QML component to load + QList children; + }; + + MenuItem m_root; + MenuItem *m_current; +}; + +#endif diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 8b92e30..939bb55 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -65,16 +65,23 @@ Item { spacing: 10 y: 40 x: 15 - Flowee.Label { - text: "Network Details" - width: parent.width - wrapMode: Text.WordWrap - MouseArea { - anchors.fill: parent - anchors.margins: -4 - onClicked: { - thePile.push("NetView.qml"); - root.open = false; + Repeater { + model: MenuModel + + Flowee.Label { + text: model.name + width: menuArea.width + wrapMode: Text.WordWrap + MouseArea { + anchors.fill: parent + anchors.margins: -4 + onClicked: { + var target = model.target + if (target !== "") { + thePile.push(model.target) + root.open = false; + } + } } } } diff --git a/main.cpp b/main.cpp index 81ea46d..da9e3f4 100644 --- a/main.cpp +++ b/main.cpp @@ -23,6 +23,7 @@ #include "TransactionInfo.h" #include "PaymentRequest.h" #include "QRCreator.h" +#include "MenuModel.h" #include // for ECC_Start() @@ -124,6 +125,8 @@ int main(int argc, char *argv[]) engine.addImageProvider(QLatin1String("qr"), new QRCreator()); engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); + MenuModel menuModel; + engine.rootContext()->setContextProperty("MenuModel", &menuModel); handleLocalQml(engine); engine.load(engine.baseUrl().url() + #ifdef DESKTOP -- 2.54.0 From a46a7e5b7402bb41a06db0c6fd53ef74171a6d9b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 15:45:16 +0100 Subject: [PATCH 0076/1428] re-add dropped line This restores the ability to click on the import tile --- guis/desktop/NewAccountPane.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index 5624a63..73f83e9 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -118,6 +118,7 @@ FocusScope { key: 2 title: qsTr("Import") width: parent.selectorWidth + onClicked: parent.cardClicked(key); features: [ qsTr("Imports seed-phrase"), -- 2.54.0 From 1ebfcbf99735952f897aa504a2ff0fd1ec210620 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 15:47:12 +0100 Subject: [PATCH 0077/1428] Move CardTypeSelector to be a common widget --- guis/{desktop => Flowee}/CardTypeSelector.qml | 1 - guis/desktop.qrc | 1 - guis/desktop/NewAccountPane.qml | 6 +++--- guis/desktop/WalletEncryption.qml | 4 ++-- guis/widgets.qrc | 1 + 5 files changed, 6 insertions(+), 7 deletions(-) rename guis/{desktop => Flowee}/CardTypeSelector.qml (98%) diff --git a/guis/desktop/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml similarity index 98% rename from guis/desktop/CardTypeSelector.qml rename to guis/Flowee/CardTypeSelector.qml index 0813b10..45eaf87 100644 --- a/guis/desktop/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -15,7 +15,6 @@ * along with this program. If not, see . */ import QtQuick 2.11 -import QtQuick.Controls 2.11 Item { id: root diff --git a/guis/desktop.qrc b/guis/desktop.qrc index e4d55d0..a314f3c 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -33,7 +33,6 @@ desktop/AccountDetails.qml desktop/SettingsPane.qml desktop/NewAccountPane.qml - desktop/CardTypeSelector.qml desktop/NewAccountCreateBasicAccount.qml desktop/NewAccountImportAccount.qml desktop/NewAccountCreateHDAccount.qml diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index 73f83e9..e2a887b 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -87,7 +87,7 @@ FocusScope { contentArea.flick(0, -1000); } - CardTypeSelector { + Flowee.CardTypeSelector { id: accountTypeBasic key: 0 title: qsTr("Basic") @@ -100,7 +100,7 @@ FocusScope { qsTr("Great for brief usage", "Context: wallet type") ] } - CardTypeSelector { + Flowee.CardTypeSelector { id: accountTypePreferred key: 1 title: qsTr("HD wallet") @@ -113,7 +113,7 @@ FocusScope { qsTr("Most compatible", "The most compatible wallet type") ] } - CardTypeSelector { + Flowee.CardTypeSelector { id: accountTypeImport key: 2 title: qsTr("Import") diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index 84ec712..d88d7ec 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -83,7 +83,7 @@ FocusScope { contentArea.flick(0, -1000); } - CardTypeSelector { + Flowee.CardTypeSelector { id: pinToPay key: 0 title: qsTr("Pin to Pay") @@ -96,7 +96,7 @@ FocusScope { qsTr("Keeps in sync", "pin to pay"), ] } - CardTypeSelector { + Flowee.CardTypeSelector { id: pinToOpen key: 1 title: qsTr("Pin to Open") diff --git a/guis/widgets.qrc b/guis/widgets.qrc index de00efb..eed664a 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -3,6 +3,7 @@ Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml Flowee/Button.qml + Flowee/CardTypeSelector.qml Flowee/CheckBox.qml Flowee/CheckBoxLabel.qml Flowee/CloseIcon.qml -- 2.54.0 From 67a09fac0b3ae61391ee7d669769d6bfdbc24fb7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 19:02:23 +0100 Subject: [PATCH 0078/1428] Structure menu pane and make less beta looking We add a logo, app version number and a link to the new accounts pane. --- MenuModel.cpp | 2 +- guis/mobile.qrc | 3 + guis/mobile/MenuOverlay.qml | 158 +++++++++++++++++++----- guis/mobile/TextButton.qml | 39 ++++++ guis/mobile/images/smallArrow-light.svg | 6 + guis/mobile/images/smallArrow.svg | 9 ++ 6 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 guis/mobile/TextButton.qml create mode 100644 guis/mobile/images/smallArrow-light.svg create mode 100644 guis/mobile/images/smallArrow.svg diff --git a/MenuModel.cpp b/MenuModel.cpp index 5fbea9b..7ea0975 100644 --- a/MenuModel.cpp +++ b/MenuModel.cpp @@ -21,7 +21,7 @@ MenuModel::MenuModel(QObject *parent) : QAbstractListModel{parent}, m_current(&m_root) { - m_root.children.append({tr("Add Wallet "), "NewWallet.qml", {}}); + // m_root.children.append({tr("Add Wallet "), "NewWallet.qml", {}}); // m_root.children.append({tr("Accounts"), "AccountsList.qml", {}}); m_root.children.append({tr("Network Details"), "NetView.qml", {}}); /* diff --git a/guis/mobile.qrc b/guis/mobile.qrc index d67699b..c35da86 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -7,6 +7,8 @@ mobile/images/moon.svg mobile/images/homeButtonIcon.svg mobile/images/homeButtonIcon-light.svg + mobile/images/smallArrow-light.svg + mobile/images/smallArrow.svg mobile/main.qml mobile/defaults.ini ControlColors.js @@ -18,5 +20,6 @@ mobile/AccountHistory.qml mobile/Loading.qml mobile/IconButton.qml + mobile/TextButton.qml diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 939bb55..bd0f849 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 2.15 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../ControlColors.js" as ControlColors import "../Flowee" as Flowee @@ -42,51 +42,145 @@ Item { width: 300 height: parent.height x: root.open ? 0 : 0 - width -3 - clip: true - Image { - width: 25 - height: 25 - anchors.right: parent.right - anchors.rightMargin: 10 - y: 10 - source: Pay.useDarkSkin ? "qrc:/maslenica.svg" : "qrc:/moon.svg" - MouseArea { - anchors.fill: parent - onClicked: { - Pay.useDarkSkin = !Pay.useDarkSkin - ControlColors.applySkin(mainWindow) + Rectangle { + id: baseArea + width: parent.width + height: logo.height + 20 + Math.max(currentAccountName.height, 12) + 10 + + (openAccounts ? extraOptions.height + 10: 0) + color: Qt.lighter(mainWindow.palette.base) + property bool openAccounts: false + clip: true + + Rectangle { + id: logo + width: 70 + height: 70 + x: 5 + y: 5 + radius: 35 + color: mainWindow.floweeBlue + Item { + // clip the logo only, ignore the text part + width: 50 + height: 50 + clip: true + x: 13 + y: 16 + Image { + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: height / 77 * 449 + height: 50 + } } } + + Image { + width: 25 + height: 25 + anchors.right: parent.right + anchors.rightMargin: 10 + y: 10 + source: Pay.useDarkSkin ? "qrc:/maslenica.svg" : "qrc:/moon.svg" + MouseArea { + anchors.fill: parent + anchors.margins: -10 + onClicked: { + Pay.useDarkSkin = !Pay.useDarkSkin + ControlColors.applySkin(mainWindow) + } + } + } + Flowee.Label { + id: currentAccountName + x: 10 + y: logo.height + 20 + text: { + if (mainWindow.isLoading) + return "" + return portfolio.current.name + } + } + Image { + id: openButton + source: Pay.useDarkSkin ? "qrc:/smallArrow-light.svg" : "qrc:/smallArrow.svg" + rotation: baseArea.openAccounts ? 180 : 0 + Behavior on rotation { NumberAnimation {} } + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.bottom: currentAccountName.bottom + anchors.bottomMargin: 8 + width: 20 + height: 7 + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + onClicked: baseArea.openAccounts = !baseArea.openAccounts + } + } + + ColumnLayout { + id: extraOptions + width: baseArea.width - 20 + anchors.top: currentAccountName.bottom + anchors.topMargin: 20 + x: 10 + Repeater { // portfolio holds all our accounts + width: parent.width + model: mainWindow.isLoading ? 0 : portfolio.accounts + TextButton { + text: modelData.name + onClicked: portfolio.current = modelData + } + } + TextButton { + id: textButton + text: qsTr("Add Wallet") + onClicked: { + thePile.push("./NewAccount.qml") + root.open = false + baseArea.openAccounts = false + } + } + } + + Behavior on height { NumberAnimation { } } } ColumnLayout { - width: parent.width - spacing: 10 - y: 40 - x: 15 + anchors.top: baseArea.bottom + anchors.topMargin: 10 + width: parent.width - 20 + x: 10 + spacing: 30 + Repeater { model: MenuModel - Flowee.Label { + TextButton { text: model.name - width: menuArea.width - wrapMode: Text.WordWrap - MouseArea { - anchors.fill: parent - anchors.margins: -4 - onClicked: { - var target = model.target - if (target !== "") { - thePile.push(model.target) - root.open = false; - } + onClicked: { + var target = model.target + if (target !== "") { + thePile.push(model.target) + root.open = false; } } } } } + Flowee.Label { + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + x: 10 + text: "Flowee Pay (mobile) v" + Application.version + font.pointSize: mainWindow.font.pointSize * 0.9 + font.bold: false + } + Behavior on x { NumberAnimation { duration: 100 } } property bool opened: false diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml new file mode 100644 index 0000000..d34d276 --- /dev/null +++ b/guis/mobile/TextButton.qml @@ -0,0 +1,39 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: root + width: parent.width + height: label.height + 20 + + signal clicked + property alias text: label.text + + Flowee.Label { + id: label + anchors.verticalCenter: parent.verticalCenter + width: parent.width + wrapMode: Text.WordWrap + } + MouseArea { + anchors.fill: parent + onClicked: root.clicked() + } +} diff --git a/guis/mobile/images/smallArrow-light.svg b/guis/mobile/images/smallArrow-light.svg new file mode 100644 index 0000000..86913ce --- /dev/null +++ b/guis/mobile/images/smallArrow-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/guis/mobile/images/smallArrow.svg b/guis/mobile/images/smallArrow.svg new file mode 100644 index 0000000..d6ce9b5 --- /dev/null +++ b/guis/mobile/images/smallArrow.svg @@ -0,0 +1,9 @@ + + + + + + -- 2.54.0 From 4061b97c579fb8b162f1ae963b62d4d9712ef3fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 19:29:13 +0100 Subject: [PATCH 0079/1428] fixlets --- guis/mobile/AccountHistory.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 78ad79d..284561d 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -36,7 +36,7 @@ ListView { header: ColumnLayout { id: column spacing: 20 - width: parent.width - 20 + width: root.width - 20 x: 10 y: 10 @@ -338,7 +338,7 @@ ListView { property int amountBch: model.fundsOut - model.fundsIn color: amountBch < 0 ? "#00000000" - : (Flowee.useDarkSkin ? "#1d6828" : "#8cff94") // green background + : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background Flowee.Label { id: amount text: Fiat.formattedPrice(parent.amountBch, fiatHistory.historicalPrice(model.date)); -- 2.54.0 From 4cf2dfa8e31facc422e359e7a074c20c1a70482b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 19:29:46 +0100 Subject: [PATCH 0080/1428] First setup for the NewAccount page. --- guis/mobile.qrc | 1 + guis/mobile/NewAccount.qml | 146 +++++++++++++++++++++++++++++++++++++ guis/mobile/Page.qml | 17 ++++- 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 guis/mobile/NewAccount.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c35da86..7f9b4e4 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -13,6 +13,7 @@ mobile/defaults.ini ControlColors.js mobile/MenuOverlay.qml + mobile/NewAccount.qml mobile/NetView.qml mobile/Page.qml mobile/MainViewBase.qml diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml new file mode 100644 index 0000000..435c2cb --- /dev/null +++ b/guis/mobile/NewAccount.qml @@ -0,0 +1,146 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee + +Page { + id: root + + headerText: qsTr("New Bitcoin Cash Wallet") + headerButtonVisible: true + headerButtonText: qsTr("Next") + + onHeaderButtonClicked: { + if (selector.selectedKey === 0) + var page = newBaseWalletScreen; + if (selector.selectedKey === 1) + page = newHDWalletScreen; + if (selector.selectedKey === 2) + page = importWalletScreen; + thePile.push(page); + } + + ColumnLayout { + id: selector + width: parent.width + property int selectedKey: 1 + + Flowee.Label { + text: qsTr("Create a New Wallet") + ":" + } + Flow { + width: parent.width + property int selectorWidth: (width - spacing * 2) / 2; + property alias selectedKey: selector.selectedKey + + Flowee.CardTypeSelector { + id: accountTypeBasic + key: 0 + title: qsTr("Basic") + width: parent.selectorWidth + onClicked: selector.selectedKey = key + + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] + } + Flowee.CardTypeSelector { + id: accountTypePreferred + key: 1 + title: qsTr("HD wallet") + width: parent.selectorWidth + onClicked: selector.selectedKey = key + + features: [ + qsTr("Seed-phrase based", "Context: wallet type"), + qsTr("Easy to backup", "Context: wallet type"), + qsTr("Most compatible", "The most compatible wallet type") + ] + } + } + + Item { width: 10; height: 15 } + + Flowee.Label { + text: qsTr("Import Existing Wallet") + ":" + } + Item { + width: parent.width + height: accountTypeImport.height + property alias selectedKey: selector.selectedKey + Flowee.CardTypeSelector { + id: accountTypeImport + key: 2 + title: qsTr("Import") + width: Math.min(parent.width / 3 * 2, 250) + onClicked: selector.selectedKey = key + anchors.horizontalCenter: parent.horizontalCenter + + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] + } + } + + // ------- the next screens + + Component { + id: newBaseWalletScreen + + Page { + headerText: qsTr("New Wallet") + Rectangle { + width: parent.width + implicitHeight: 100 + color: "red" + } + } + } + Component { + id: newHDWalletScreen + + Page { + headerText: qsTr("New HD-Wallet") + Rectangle { + width: parent.width + implicitHeight: 100 + color: "blue" + + } + } + } + Component { + id: importWalletScreen + + Page { + headerText: qsTr("Import Wallet") + Rectangle { + width: parent.width + implicitHeight: 100 + color: "yellow" + + } + } + } + } +} diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index dd9ece2..6ad3407 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -28,6 +28,10 @@ QQC2.Control { default property alias content: child.children property alias headerText: headerLabel.text + property alias headerButtonVisible: headerButton.visible + property alias headerButtonText: headerButton.text + property alias headerButtonEnabled: headerButton.enabled + signal headerButtonClicked Rectangle { id: header @@ -52,7 +56,18 @@ QQC2.Control { QQC2.Label { id: headerLabel color: "white" - anchors.centerIn: parent + anchors.left: backButton.right + anchors.right: headerButton.left + horizontalAlignment: Text.AlignHCenter + anchors.verticalCenter: parent.verticalCenter + } + + Flowee.Button { + id: headerButton + visible: false + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + onClicked: root.headerButtonClicked() } } -- 2.54.0 From 17296d731a0f7681c5deb94f74a9bc60ba03736f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Nov 2022 20:34:34 +0100 Subject: [PATCH 0081/1428] Use our Label class with fixes. --- guis/Flowee/BitcoinAmountLabel.qml | 14 +++++++------- guis/Flowee/BitcoinValueField.qml | 2 +- guis/Flowee/CheckBox.qml | 4 ++-- guis/Flowee/CheckBoxLabel.qml | 2 +- guis/Flowee/Dialog.qml | 4 ++-- guis/Flowee/FiatValueField.qml | 2 +- guis/Flowee/LabelWithClipboard.qml | 2 +- guis/Flowee/MoneyValueField.qml | 4 ++-- guis/Flowee/TabBar.qml | 2 +- guis/Flowee/WarningLabel.qml | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index c2b1c1a..099beb2 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -66,7 +66,7 @@ QQC2.Control { amountString = Pay.amountToString(sats) } - QQC2.Label { + Label { id: main text: { var s = row.amountString @@ -90,7 +90,7 @@ QQC2.Control { Layout.alignment: Qt.AlignBaseline } - QQC2.Label { + Label { text: { var s = row.amountString var pos = s.length - 5 @@ -98,11 +98,11 @@ QQC2.Control { } font.pointSize: satsLabel.font.pointSize color: main.color - opacity: (satsLabel.opacity !== 1 && text == "000") ? 0.3 : 1 + opacity: (satsLabel.opacity !== 1 && text === "000") ? 0.3 : 1 Layout.alignment: Qt.AlignBaseline visible: Pay.unitAllowedDecimals === 8 } - QQC2.Label { + Label { id: satsLabel text: { var s = row.amountString @@ -110,19 +110,19 @@ QQC2.Control { } font.pointSize: main.font.pointSize / 10 * 8 color: main.color - opacity: text == "00" ? 0.3 : 1 + opacity: text === "00" ? 0.3 : 1 Layout.alignment: Qt.AlignBaseline visible: Pay.unitAllowedDecimals >= 2 } - QQC2.Label { + Label { text: Pay.unitName color: main.color visible: root.includeUnit Layout.alignment: Qt.AlignBaseline } - QQC2.Label { + Label { visible: root.showFiat Layout.alignment: Qt.AlignBaseline text: { diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 289a19c..d84741b 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -42,7 +42,7 @@ MoneyValueField { showFiat: false } - QQC2.Label { + Label { id: unit text: Pay.unitName y: 6 diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 2f7df2a..1c7c0ab 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -82,7 +82,7 @@ Item { visible: mousy.containsMouse && root.tooltipText !== "" } } - QQC2.Label { + Label { id: title anchors.left: slider.right anchors.verticalCenter: parent.verticalCenter @@ -100,7 +100,7 @@ Item { anchors.leftMargin: 16 radius: width color: q.palette.text - QQC2.Label { + Label { id: q text: "?" color: palette.base diff --git a/guis/Flowee/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml index 85d2e5b..411a742 100644 --- a/guis/Flowee/CheckBoxLabel.qml +++ b/guis/Flowee/CheckBoxLabel.qml @@ -25,7 +25,7 @@ import QtQuick.Controls 2.11 as QQC2 * a text value, it will then become a clickable label which triggers * the checkbox when clicked. */ -QQC2.Label { +Label { id: root property var buddy: null property string toolTipText: "" diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 3265ca6..74ffcfc 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -71,12 +71,12 @@ QQC2.Popup { return max / 2 * 3; } spacing: 10 - QQC2.Label { + Label { id: titleLabel font.bold: true anchors.horizontalCenter: parent.horizontalCenter } - QQC2.Label { + Label { id: mainTextLabel // this next line will always create a binding loop. But its harmless, so ignore that comment. width: parent.width diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index 127e0bd..571f453 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -32,7 +32,7 @@ MoneyValueField { property double baselineOffset: balance.baselineOffset + balance.y valueObject.maxFractionalDigits: Fiat.displayCents ? 2 : 0 - QQC2.Label { + Label { id: balance x: 6 y: 6 diff --git a/guis/Flowee/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml index 3531693..be542ca 100644 --- a/guis/Flowee/LabelWithClipboard.qml +++ b/guis/Flowee/LabelWithClipboard.qml @@ -18,7 +18,7 @@ import QtQuick 2.11 import QtQuick.Controls 2.11 as QQC2 -QQC2.Label { +Label { id: root elide: Text.ElideMiddle diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index 85fcdcc..42723d6 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -65,7 +65,7 @@ FocusScope { visible: root.activeFocus x: 8 y: 6 - QQC2.Label { + Label { id: begin text: privValue.enteredString.substring(0, privValue.cursorPos) } @@ -84,7 +84,7 @@ FocusScope { onTriggered: parent.cursorVisible = !parent.cursorVisible } } - QQC2.Label { + Label { anchors.left: begin.right text: privValue.enteredString.substring(privValue.cursorPos) } diff --git a/guis/Flowee/TabBar.qml b/guis/Flowee/TabBar.qml index 361511a..dbb8e55 100644 --- a/guis/Flowee/TabBar.qml +++ b/guis/Flowee/TabBar.qml @@ -70,7 +70,7 @@ FocusScope { width: height height: payTabButtonText.height } - QQC2.Label { + Label { id: payTabButtonText anchors.verticalCenter: parent.verticalCenter font.bold: true diff --git a/guis/Flowee/WarningLabel.qml b/guis/Flowee/WarningLabel.qml index e0ebe92..eec49dd 100644 --- a/guis/Flowee/WarningLabel.qml +++ b/guis/Flowee/WarningLabel.qml @@ -35,7 +35,7 @@ Item { height: 32 anchors.verticalCenter: parent.verticalCenter } - QQC2.Label { + Label { id: warningText anchors.left: warningIcon.right anchors.leftMargin: 5 -- 2.54.0 From 49a6154d795a402294fa7f7d3840aa2e4b7b6985 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:03:00 +0100 Subject: [PATCH 0082/1428] Sanitize ones inputs better To avoid surprises, do some more cleanups of user-input before we use it. --- FloweePay.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index db9d8f2..d6fab22 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -734,7 +734,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons wallet->setSingleAddressWallet(true); if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; - wallet->addPrivateKey(privateKey, startHeight); + wallet->addPrivateKey(privateKey.trimmed().remove('\n'), startHeight); saveData(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -777,7 +777,8 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons std::vector derivationPath = HDMasterKey::deriveFromString(derivationPathStr.toStdString()); if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; - wallet->createHDMasterKey(mnemonic, password, derivationPath, startHeight); + wallet->createHDMasterKey(mnemonic.trimmed().remove('\n'), password.trimmed().remove('\n'), + derivationPath, startHeight); wallet->segment()->blockSynched(startHeight); wallet->segment()->blockSynched(startHeight); // yes, twice saveData(); @@ -803,7 +804,7 @@ bool FloweePay::checkDerivation(const QString &path) const WalletEnums::StringType FloweePay::identifyString(const QString &string) const { - const QString string_ = string.trimmed(); + const QString string_ = string.trimmed().remove('\n'); const std::string s = string_.toStdString(); if (string_.isEmpty()) { m_hdSeedValidator.clearSelectedLanguage(); -- 2.54.0 From 456f03339f43b91e19d9f1d103ee523c117b9181 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:03:32 +0100 Subject: [PATCH 0083/1428] Add namespace to this usage too. --- guis/Flowee/CheckBox.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 1c7c0ab..cd6279b 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -110,7 +110,7 @@ Item { anchors.margins: -7 cursorShape: Qt.PointingHandCursor id: clicky - onClicked: ToolTip.show(root.tooltipText, 15000); + onClicked: QQC2.ToolTip.show(root.tooltipText, 15000); } } } -- 2.54.0 From d255f8ede79a1686dfe64f035f9cb5b4ca0a5117 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:04:04 +0100 Subject: [PATCH 0084/1428] Properly break text-lines that have no spaces A privatekey should be broken over two lines instead of forcing a horizontl scroll, for better UX> --- guis/Flowee/MultilineTextField.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 6f72eb2..6b6958a 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -73,7 +73,7 @@ QQC2.Control { selectedTextColor: root.palette.highlightedText selectionColor: root.palette.highlight selectByMouse: true - wrapMode: TextEdit.WordWrap + wrapMode: TextEdit.Wrap property bool showingPlaceholder: false onActiveFocusChanged: { -- 2.54.0 From 34614cb6463b9f3583e03c3fa86ab1b7611bf91e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:04:21 +0100 Subject: [PATCH 0085/1428] Fix default value --- guis/desktop/NewAccountImportAccount.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 509e2b8..7dc7890 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -158,7 +158,7 @@ GridLayout { } Flowee.TextField { id: derivationPath - text: "m/44'/0'/0'/0" // What most BCH wallets are created with + text: "m/44'/0'/0" // What most BCH wallets are created with visible: !importAccount.isPrivateKey color: Pay.checkDerivation(text) ? palette.text : "red" } -- 2.54.0 From 82e9130c6a3f39acdc97faed5212fcebed12cb62 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:04:48 +0100 Subject: [PATCH 0086/1428] UX: make the clickable area wider than the button. --- guis/mobile/MenuOverlay.qml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index bd0f849..788c8cf 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -114,11 +114,14 @@ Item { width: 20 height: 7 - MouseArea { - anchors.fill: parent - anchors.margins: -10 - onClicked: baseArea.openAccounts = !baseArea.openAccounts - } + } + MouseArea { + anchors.top: currentAccountName.top + anchors.bottom: openButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: -10 + onClicked: baseArea.openAccounts = !baseArea.openAccounts } ColumnLayout { -- 2.54.0 From 1b2f07bcaf3aefdfec4c8975baf7ccd4cb7dc353 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:09:31 +0100 Subject: [PATCH 0087/1428] Make page have default margins. To avoid having to do so in each child-page. --- guis/mobile/Page.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 6ad3407..9f634a7 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -28,6 +28,7 @@ QQC2.Control { default property alias content: child.children property alias headerText: headerLabel.text + property alias columns: child.columns property alias headerButtonVisible: headerButton.visible property alias headerButtonText: headerButton.text property alias headerButtonEnabled: headerButton.enabled @@ -66,6 +67,7 @@ QQC2.Control { id: headerButton visible: false anchors.right: parent.right + anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter onClicked: root.headerButtonClicked() } @@ -76,11 +78,12 @@ QQC2.Control { y: header.height height: parent.height - y clip: true - contentHeight: child.height + contentHeight: child.implicitHeight contentWidth: width GridLayout { id: child - width: parent.width + width: root.width - 20 + x: 10 // default margin height: implicitHeight columns: 1 } -- 2.54.0 From 44afb0ed40b81ae5b6aed32ac6b3efc4078c9c85 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Nov 2022 23:11:33 +0100 Subject: [PATCH 0088/1428] Add sub-pages for new wallets. Could probably do with some TLC on the UX front, but lets focus on actual functionality first. --- guis/mobile/NewAccount.qml | 198 ++++++++++++++++++++++++++++++++++--- 1 file changed, 185 insertions(+), 13 deletions(-) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 435c2cb..25b6443 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls as QQC2 import "../Flowee" as Flowee +import Flowee.org.pay Page { id: root @@ -108,11 +109,53 @@ Page { id: newBaseWalletScreen Page { + id: newWalletPage headerText: qsTr("New Wallet") - Rectangle { - width: parent.width - implicitHeight: 100 - color: "red" + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 + onHeaderButtonClicked: { + var options = Pay.createNewBasicWallet(accountName.text); + options.forceSingleAddress = singleAddress.checked; + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + + Flowee.Label { + id: title + text: qsTr("This creates a new empty wallet with simple multi-address capability") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + Item { width: 10; height: 10 } + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + } + Flowee.Label { + text: qsTr("Derivation") + ":" + } + Flowee.TextField { + id: derivationPath + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most wallets use to import by default + color: Pay.checkDerivation(text) ? palette.text : "red" } } } @@ -121,11 +164,34 @@ Page { Page { headerText: qsTr("New HD-Wallet") - Rectangle { - width: parent.width - implicitHeight: 100 - color: "blue" - + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 + onHeaderButtonClicked: { + var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + Flowee.Label { + id: title + Layout.fillWidth: true + text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") + wrapMode: Text.WordWrap + } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true } } } @@ -133,12 +199,118 @@ Page { id: importWalletScreen Page { + id: importAccount headerText: qsTr("Import Wallet") - Rectangle { - width: parent.width - implicitHeight: 100 - color: "yellow" + columns: 2 + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 && finished + property var typedData: Pay.identifyString(secrets.text) + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool isPrivateKey: typedData === Wallet.PrivateKey + + onHeaderButtonClicked: { + var sh = parseInt("0" + startHeight.text, 10); + if (sh === 0) // the genesis was block 1, zero doesn't exist + sh = 1; + if (importAccount.isMnemonic) + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + else + options = Pay.createImportedWallet(secrets.text, accountName.text, sh) + + options.forceSingleAddress = singleAddress.checked; + + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + + Flowee.Label { + text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") + Layout.fillWidth: true + Layout.columnSpan: 2 + wrapMode: Text.Wrap + } + Flowee.Label { + text: qsTr("Secret", "The seed-phrase or private key") + ":" + Layout.columnSpan: 2 + } + Flowee.MultilineTextField { + id: secrets + focus: true + Layout.fillWidth: true + nextFocusTarget: accountName + clip: true + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + Layout.alignment: Qt.AlignTop + } + Flowee.Label { + text: qsTr("Name") + ":" + Layout.columnSpan: 2 + } + Flowee.TextField { + id: accountName + Layout.columnSpan: 2 + Layout.fillWidth: true + } + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + visible: importAccount.isPrivateKey + Layout.columnSpan: 2 + } + Flowee.Label { + text: qsTr("Alternate phrase") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + Layout.columnSpan: 2 + } + Flowee.Label { + Layout.columnSpan: 2 + text: qsTr("Start Height") + ":" + } + Flowee.TextField { + id: startHeight + Layout.columnSpan: 2 + Layout.fillWidth: true + validator: IntValidator{bottom: 0; top: 999999} + } + Flowee.Label { + text: qsTr("Derivation") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + id: derivationPath + Layout.columnSpan: 2 + Layout.fillWidth: true + text: "m/44'/0'/0" // What most BCH wallets are created with + visible: !importAccount.isPrivateKey + color: Pay.checkDerivation(text) ? palette.text : "red" } } } -- 2.54.0 From f3a05f7eb78af29b1ca27c190eec794943b7ac87 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 10:33:18 +0100 Subject: [PATCH 0089/1428] Remove unneeded ColumnLayout The Page gives us a GridLayout, so no need to nest a ColumnLayout inside of that. --- guis/mobile/NewAccount.qml | 521 ++++++++++++++++++------------------- 1 file changed, 260 insertions(+), 261 deletions(-) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 25b6443..3eb27d2 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -23,296 +23,295 @@ import Flowee.org.pay Page { id: root + columns: 1 headerText: qsTr("New Bitcoin Cash Wallet") headerButtonVisible: true headerButtonText: qsTr("Next") onHeaderButtonClicked: { - if (selector.selectedKey === 0) + if (selectedKey === 0) var page = newBaseWalletScreen; - if (selector.selectedKey === 1) + if (selectedKey === 1) page = newHDWalletScreen; - if (selector.selectedKey === 2) + if (selectedKey === 2) page = importWalletScreen; thePile.push(page); } - ColumnLayout { - id: selector + property int selectedKey: 1 + + Flowee.Label { + text: qsTr("Create a New Wallet") + ":" + } + Flow { width: parent.width - property int selectedKey: 1 + property int selectorWidth: (width - spacing * 2) / 2; + property alias selectedKey: root.selectedKey - Flowee.Label { - text: qsTr("Create a New Wallet") + ":" + Flowee.CardTypeSelector { + id: accountTypeBasic + key: 0 + title: qsTr("Basic") + width: parent.selectorWidth + onClicked: root.selectedKey = key + + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] } - Flow { - width: parent.width - property int selectorWidth: (width - spacing * 2) / 2; - property alias selectedKey: selector.selectedKey + Flowee.CardTypeSelector { + id: accountTypePreferred + key: 1 + title: qsTr("HD wallet") + width: parent.selectorWidth + onClicked: root.selectedKey = key - Flowee.CardTypeSelector { - id: accountTypeBasic - key: 0 - title: qsTr("Basic") - width: parent.selectorWidth - onClicked: selector.selectedKey = key - - features: [ - qsTr("Private keys based", "Property of a wallet"), - qsTr("Difficult to backup", "Context: wallet type"), - qsTr("Great for brief usage", "Context: wallet type") - ] - } - Flowee.CardTypeSelector { - id: accountTypePreferred - key: 1 - title: qsTr("HD wallet") - width: parent.selectorWidth - onClicked: selector.selectedKey = key - - features: [ - qsTr("Seed-phrase based", "Context: wallet type"), - qsTr("Easy to backup", "Context: wallet type"), - qsTr("Most compatible", "The most compatible wallet type") - ] - } + features: [ + qsTr("Seed-phrase based", "Context: wallet type"), + qsTr("Easy to backup", "Context: wallet type"), + qsTr("Most compatible", "The most compatible wallet type") + ] } + } - Item { width: 10; height: 15 } + Item { width: 10; height: 15 } - Flowee.Label { - text: qsTr("Import Existing Wallet") + ":" + Flowee.Label { + text: qsTr("Import Existing Wallet") + ":" + } + Item { + width: parent.width + height: accountTypeImport.height + property alias selectedKey: root.selectedKey + Flowee.CardTypeSelector { + id: accountTypeImport + key: 2 + title: qsTr("Import") + width: Math.min(parent.width / 3 * 2, 250) + onClicked: root.selectedKey = key + anchors.horizontalCenter: parent.horizontalCenter + + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] } - Item { - width: parent.width - height: accountTypeImport.height - property alias selectedKey: selector.selectedKey - Flowee.CardTypeSelector { - id: accountTypeImport - key: 2 - title: qsTr("Import") - width: Math.min(parent.width / 3 * 2, 250) - onClicked: selector.selectedKey = key - anchors.horizontalCenter: parent.horizontalCenter + } - features: [ - qsTr("Imports seed-phrase"), - qsTr("Imports private key") - ] - } - } + // ------- the next screens - // ------- the next screens + Item { // dummy item to allow components to be added to the parents GridLayout + Component { + id: newBaseWalletScreen - Component { - id: newBaseWalletScreen - - Page { - id: newWalletPage - headerText: qsTr("New Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 - onHeaderButtonClicked: { - var options = Pay.createNewBasicWallet(accountName.text); - options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } + Page { + id: newWalletPage + headerText: qsTr("New Wallet") + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 + onHeaderButtonClicked: { + var options = Pay.createNewBasicWallet(accountName.text); + options.forceSingleAddress = singleAddress.checked; + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; } - thePile.pop(); - thePile.pop(); - } - - Flowee.Label { - id: title - text: qsTr("This creates a new empty wallet with simple multi-address capability") - Layout.fillWidth: true - wrapMode: Text.WordWrap - } - Flowee.Label { - text: qsTr("Name") + ":"; - } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - } - Item { width: 10; height: 10 } - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") - } - Flowee.Label { - text: qsTr("Derivation") + ":" - } - Flowee.TextField { - id: derivationPath - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most wallets use to import by default - color: Pay.checkDerivation(text) ? palette.text : "red" } + thePile.pop(); + thePile.pop(); } - } - Component { - id: newHDWalletScreen - Page { - headerText: qsTr("New HD-Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 - onHeaderButtonClicked: { - var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } - Flowee.Label { - id: title - Layout.fillWidth: true - text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") - wrapMode: Text.WordWrap - } - Flowee.Label { - text: qsTr("Name") + ":"; - } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - } + Flowee.Label { + id: title + text: qsTr("This creates a new empty wallet with simple multi-address capability") + Layout.fillWidth: true + wrapMode: Text.WordWrap } - } - Component { - id: importWalletScreen - - Page { - id: importAccount - headerText: qsTr("Import Wallet") - columns: 2 - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 && finished - - property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; - property bool isPrivateKey: typedData === Wallet.PrivateKey - - onHeaderButtonClicked: { - var sh = parseInt("0" + startHeight.text, 10); - if (sh === 0) // the genesis was block 1, zero doesn't exist - sh = 1; - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - - options.forceSingleAddress = singleAddress.checked; - - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } - - Flowee.Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.fillWidth: true - Layout.columnSpan: 2 - wrapMode: Text.Wrap - } - Flowee.Label { - text: qsTr("Secret", "The seed-phrase or private key") + ":" - Layout.columnSpan: 2 - } - Flowee.MultilineTextField { - id: secrets - focus: true - Layout.fillWidth: true - nextFocusTarget: accountName - clip: true - } - Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - Layout.alignment: Qt.AlignTop - } - Flowee.Label { - text: qsTr("Name") + ":" - Layout.columnSpan: 2 - } - Flowee.TextField { - id: accountName - Layout.columnSpan: 2 - Layout.fillWidth: true - } - - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") - checked: true - visible: importAccount.isPrivateKey - Layout.columnSpan: 2 - } - Flowee.Label { - text: qsTr("Alternate phrase") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - Layout.columnSpan: 2 - } - Flowee.Label { - Layout.columnSpan: 2 - text: qsTr("Start Height") + ":" - } - Flowee.TextField { - id: startHeight - Layout.columnSpan: 2 - Layout.fillWidth: true - validator: IntValidator{bottom: 0; top: 999999} - } - Flowee.Label { - text: qsTr("Derivation") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - id: derivationPath - Layout.columnSpan: 2 - Layout.fillWidth: true - text: "m/44'/0'/0" // What most BCH wallets are created with - visible: !importAccount.isPrivateKey - color: Pay.checkDerivation(text) ? palette.text : "red" - } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + Item { width: 10; height: 10 } + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + } + Flowee.Label { + text: qsTr("Derivation") + ":" + } + Flowee.TextField { + id: derivationPath + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most wallets use to import by default + color: Pay.checkDerivation(text) ? palette.text : "red" } } } + Component { + id: newHDWalletScreen + + Page { + headerText: qsTr("New HD-Wallet") + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 + onHeaderButtonClicked: { + var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + Flowee.Label { + id: title + Layout.fillWidth: true + text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") + wrapMode: Text.WordWrap + } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + } + } + Component { + id: importWalletScreen + + Page { + id: importAccount + headerText: qsTr("Import Wallet") + columns: 2 + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 && finished + + property var typedData: Pay.identifyString(secrets.text) + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool isPrivateKey: typedData === Wallet.PrivateKey + + onHeaderButtonClicked: { + var sh = parseInt("0" + startHeight.text, 10); + if (sh === 0) // the genesis was block 1, zero doesn't exist + sh = 1; + if (importAccount.isMnemonic) + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + else + options = Pay.createImportedWallet(secrets.text, accountName.text, sh) + + options.forceSingleAddress = singleAddress.checked; + + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + + Flowee.Label { + text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") + Layout.fillWidth: true + Layout.columnSpan: 2 + wrapMode: Text.Wrap + } + Flowee.Label { + text: qsTr("Secret", "The seed-phrase or private key") + ":" + Layout.columnSpan: 2 + } + Flowee.MultilineTextField { + id: secrets + focus: true + Layout.fillWidth: true + nextFocusTarget: accountName + clip: true + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + Layout.alignment: Qt.AlignTop + } + Flowee.Label { + text: qsTr("Name") + ":" + Layout.columnSpan: 2 + } + Flowee.TextField { + id: accountName + Layout.columnSpan: 2 + Layout.fillWidth: true + } + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + visible: importAccount.isPrivateKey + Layout.columnSpan: 2 + } + Flowee.Label { + text: qsTr("Alternate phrase") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + Layout.columnSpan: 2 + } + Flowee.Label { + Layout.columnSpan: 2 + text: qsTr("Start Height") + ":" + } + Flowee.TextField { + id: startHeight + Layout.columnSpan: 2 + Layout.fillWidth: true + validator: IntValidator{bottom: 0; top: 999999} + } + Flowee.Label { + text: qsTr("Derivation") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + id: derivationPath + Layout.columnSpan: 2 + Layout.fillWidth: true + text: "m/44'/0'/0" // What most BCH wallets are created with + visible: !importAccount.isPrivateKey + color: Pay.checkDerivation(text) ? palette.text : "red" + } + } + } + } } -- 2.54.0 From d8cfc9767d3a51ffc1ea1853e33cb9ee9d04ed6f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 10:35:31 +0100 Subject: [PATCH 0090/1428] Provide a means to properly move keyboard input As most of our app will be based on Pages pushed to the stack, a simple structure to move focus forcefully to the new child has been added. Children would basically only need to mark the first item as taking focus. --- guis/mobile/Loading.qml | 4 ++++ guis/mobile/MainViewBase.qml | 9 +++++---- guis/mobile/Page.qml | 19 +++++++++++++------ guis/mobile/main.qml | 1 + 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/guis/mobile/Loading.qml b/guis/mobile/Loading.qml index 84bdae3..7c86994 100644 --- a/guis/mobile/Loading.qml +++ b/guis/mobile/Loading.qml @@ -21,6 +21,10 @@ Rectangle { color: "#0b1088" width: parent.width height: parent.height + function takeFocus() { + // method called from main.qml + // We don't need focus, though. Empty method. + } Image { source: "qrc:/FloweePay-light.svg" diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index fca3771..5b26aaa 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -32,18 +32,20 @@ QQC2.Control { Component.onCompleted: setOpacities() function setOpacities() { - var visibleChild = null; for (let i = 0; i < stack.children.length; ++i) { let on = i === currentIndex; let child = stack.children[i]; child.visible = on; - if (on) - visibleChild = child; } + takeFocus(); + } + // called from main when this page becomes active, as well as when we change tabs + function takeFocus() { // try to make sure the current tab gets keyboard focus properly. // We do some extra work in case the tab itself is a loader. forceActiveFocus(); + let visibleChild = stack.children[currentIndex]; visibleChild.focus = true if (visibleChild instanceof Loader) { var child = visibleChild.item; @@ -51,7 +53,6 @@ QQC2.Control { child.focus = true; } } - Rectangle { id: header width: parent.width diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 9f634a7..e51144d 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -34,6 +34,10 @@ QQC2.Control { property alias headerButtonEnabled: headerButton.enabled signal headerButtonClicked + function takeFocus() { + focusScope.forceActiveFocus(); + } + Rectangle { id: header width: parent.width @@ -80,12 +84,15 @@ QQC2.Control { clip: true contentHeight: child.implicitHeight contentWidth: width - GridLayout { - id: child - width: root.width - 20 - x: 10 // default margin - height: implicitHeight - columns: 1 + FocusScope { + id: focusScope + GridLayout { + id: child + width: root.width - 20 + x: 10 // default margin + height: implicitHeight + columns: 1 + } } } Keys.onPressed: (event)=> { diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index b4a0429..c6818fa 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -53,6 +53,7 @@ ApplicationWindow { id: thePile anchors.fill: parent initialItem: "./Loading.qml"; + onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); } MenuOverlay { id: menuOverlay -- 2.54.0 From 215f7650a070689ff1bf73a199c852b055097ca1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 10:44:53 +0100 Subject: [PATCH 0091/1428] Add focus support --- guis/Flowee/MultilineTextField.qml | 6 ++++++ guis/mobile/NewAccount.qml | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 6b6958a..32e5893 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -41,6 +41,12 @@ QQC2.Control { width: 100 height: implicitHeight + onActiveFocusChanged: { + // delegate our focus to the child textedit. + if (activeFocus) + textEdit.forceActiveFocus(); + } + Connections { function onTextChanged() { textEdit.text = text; } function onPlaceholderTextChanged() { diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 3eb27d2..d18b927 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -139,6 +139,7 @@ Page { } Flowee.TextField { id: accountName + focus: true Layout.fillWidth: true } Item { width: 10; height: 10 } @@ -191,6 +192,7 @@ Page { Flowee.TextField { id: accountName Layout.fillWidth: true + focus: true } } } @@ -245,8 +247,8 @@ Page { } Flowee.MultilineTextField { id: secrets - focus: true Layout.fillWidth: true + focus: true nextFocusTarget: accountName clip: true } -- 2.54.0 From 2c606ce959cb4af45d78cc47ce0a1009d34de6db Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 12:04:43 +0100 Subject: [PATCH 0092/1428] Replace blockheight (number) with date selector Importing a key as private as possible means we don't check some central indexing server for the first time an imported key or seed is used. But, to avoid scanning the entire blockchain, we go back only as far as the first usage that the user remembers. Now, instead of asking a blockheight (which most users have no clue about) we now ask for a year / month combo and start scanning from the block at the beginning of that timespan. --- FloweePay.cpp | 12 ++++++++++++ FloweePay.h | 8 +++++++- guis/mobile/NewAccount.qml | 36 +++++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index d6fab22..64a2446 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -728,6 +728,12 @@ void FloweePay::setHideBalance(bool hideBalance) appConfig.setValue(HIDEBALANCE, m_hideBalance); } +NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, const QString &walletName, const QDateTime &date) +{ + const int height = p2pNet()->blockchain().blockHeightAtTime(date.toSecsSinceEpoch()); + return createImportedWallet(privateKey, walletName, height); +} + NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, const QString &walletName, int startHeight) { auto wallet = createWallet(walletName); @@ -770,6 +776,12 @@ QObject *FloweePay::researchAddress(const QString &address, QObject *parent) return info; } +NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, const QDateTime &date) +{ + const int height = p2pNet()->blockchain().blockHeightAtTime(date.toSecsSinceEpoch()); + return createImportedHDWallet(mnemonic, password, derivationPathStr, walletName, height); +} + NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, int startHeight) { auto wallet = createWallet(walletName); diff --git a/FloweePay.h b/FloweePay.h index dce19a8..ba9c193 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -124,6 +124,8 @@ public: Q_INVOKABLE NewWalletConfig* createNewBasicWallet(const QString &walletName = QString()); /// swipe a paper wallet with an optional name Q_INVOKABLE NewWalletConfig* createImportedWallet(const QString &privateKey, const QString &walletName, int startHeight); + /// Alternative arguments version. With date instead of block-height + Q_INVOKABLE NewWalletConfig* createImportedWallet(const QString &privateKey, const QString &walletName, const QDateTime &date); /// Find out about the address and return an instance of AddressInfo if known. Q_INVOKABLE QObject* researchAddress(const QString &address, QObject *parent); @@ -139,7 +141,11 @@ public: * @param startHeight the first block we should check for transactions, or zero for "future blocks" * If you set this to 1 then we set it to a more sane value of when Bitcoin became more well known. */ - Q_INVOKABLE NewWalletConfig *createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPath, const QString &walletName, int startHeight = 0); + Q_INVOKABLE NewWalletConfig *createImportedHDWallet(const QString &mnemonic, const QString &password, + const QString &derivationPath, const QString &walletName, int startHeight = 0); + /// Alternative arguments version. With date instead of block-height + Q_INVOKABLE NewWalletConfig *createImportedHDWallet(const QString &mnemonic, const QString &password, + const QString &derivationPath, const QString &walletName, const QDateTime &date); Q_INVOKABLE bool checkDerivation(const QString &path) const; diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index d18b927..ceca6c9 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -213,9 +213,7 @@ Page { property bool isPrivateKey: typedData === Wallet.PrivateKey onHeaderButtonClicked: { - var sh = parseInt("0" + startHeight.text, 10); - if (sh === 0) // the genesis was block 1, zero doesn't exist - sh = 1; + var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); if (importAccount.isMnemonic) var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); else @@ -292,13 +290,33 @@ Page { } Flowee.Label { Layout.columnSpan: 2 - text: qsTr("Start Height") + ":" + text: qsTr("Oldest Transaction") + ":" } - Flowee.TextField { - id: startHeight - Layout.columnSpan: 2 - Layout.fillWidth: true - validator: IntValidator{bottom: 0; top: 999999} + Flow { + spacing: 10 + QQC2.ComboBox { + id: month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; + } + } + QQC2.ComboBox { + id: year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 9; + } } Flowee.Label { text: qsTr("Derivation") + ":" -- 2.54.0 From cc551366d810f74b4682eca375a15aaf6460d732 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 12:39:39 +0100 Subject: [PATCH 0093/1428] Add feedback on import type. --- guis/mobile/NewAccount.qml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index ceca6c9..2c83629 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -257,6 +257,25 @@ Page { font.pixelSize: 24 Layout.alignment: Qt.AlignTop } + + Flowee.Label { + id: detectedType + color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color + text: { + var typedData = importAccount.typedData + if (typedData === Wallet.PrivateKey) + return qsTr("Private key", "description of type") // TODO print address to go with it + if (typedData === Wallet.CorrectMnemonic) + return qsTr("BIP 39 seed-phrase", "description of type") + if (typedData === Wallet.PartialMnemonicWithTypo) + return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") + if (typedData === Wallet.MissingLexicon) + return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this + return "" + } + Layout.columnSpan: 2 + } + Flowee.Label { text: qsTr("Name") + ":" Layout.columnSpan: 2 -- 2.54.0 From 427ab30faff40fe924ec97c568630ec1da3c08a5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 16:06:37 +0100 Subject: [PATCH 0094/1428] Add 'About' screen. This improves the TextButton to be more re-usable and have an arrow to make it clear it actually is a button. Added a sub-text as well. This made it really easy to make an About page with some subpages or external links. Also increased the top-header from 40 to 50 'pixels'. --- MenuModel.cpp | 6 +- guis/mobile.qrc | 3 + guis/mobile/About.qml | 98 +++++++++++++++++++++++++++ guis/mobile/MainViewBase.qml | 2 +- guis/mobile/MenuOverlay.qml | 3 +- guis/mobile/Page.qml | 11 ++- guis/mobile/TextButton.qml | 46 ++++++++++++- guis/mobile/images/external-light.svg | 8 +++ guis/mobile/images/external.svg | 8 +++ 9 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 guis/mobile/About.qml create mode 100644 guis/mobile/images/external-light.svg create mode 100644 guis/mobile/images/external.svg diff --git a/MenuModel.cpp b/MenuModel.cpp index 7ea0975..54a7ed8 100644 --- a/MenuModel.cpp +++ b/MenuModel.cpp @@ -21,12 +21,12 @@ MenuModel::MenuModel(QObject *parent) : QAbstractListModel{parent}, m_current(&m_root) { - // m_root.children.append({tr("Add Wallet "), "NewWallet.qml", {}}); // m_root.children.append({tr("Accounts"), "AccountsList.qml", {}}); - m_root.children.append({tr("Network Details"), "NetView.qml", {}}); + m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); + m_root.children.append({tr("About"), "./About.qml", {}}); /* m_root.children.append({tr("Settings"), "", { - { tr("Settings 1"), "", {} }, + { tr("Security"), "", {} }, { tr("Settings 2"), "", {} }, }}); */ diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 7f9b4e4..90fb214 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -9,9 +9,12 @@ mobile/images/homeButtonIcon-light.svg mobile/images/smallArrow-light.svg mobile/images/smallArrow.svg + mobile/images/external.svg + mobile/images/external-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js + mobile/About.qml mobile/MenuOverlay.qml mobile/NewAccount.qml mobile/NetView.qml diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml new file mode 100644 index 0000000..d9f13e2 --- /dev/null +++ b/guis/mobile/About.qml @@ -0,0 +1,98 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + headerText: qsTr("About") + + Image { + source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" + Layout.fillWidth: true + fillMode: Image.PreserveAspectFit + } + + Flowee.Label { + text: "Flowee Pay (mobile) v" + Application.version + } + Item { width: 10; height: 10 } // spacer + + TextButton { + id: translate + text: qsTr("Help translate this app") + imageSource: Pay.useDarkSkin ? "qrc:/external-light.svg" : "qrc:/external.svg" + onClicked: Qt.openUrlExternally("https://crowdin.com/project/floweepay"); + } + TextButton { + text: qsTr("License") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://www.gnu.org/licenses/gpl-3.0") + } + TextButton { + text: qsTr("Credits") + subtext: qsTr("© 2020-2022 Tom Zander and contributors") + showPageIcon: true + onClicked: thePile.push(creditsPage) + + Component { + id: creditsPage + Page { + headerText: qsTr("Credits") + + Flowee.Label { + text: "## Author and maintainer +Tom Zander + +## Code Contributors + +You? + +## Art Contributors + +You? + +## Translations + +Nederland
+
s
+
Tom Zander
+
Polska
+
yantri
+
+" + textFormat: Text.MarkdownText + Layout.fillWidth: true + Layout.fillHeight: true + wrapMode: Text.WordWrap + } + } + } + } + TextButton { + text: qsTr("Project Home") + subtext: qsTr("With git repository and issues tracker") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://codeberg.org/Flowee/pay"); + } + TextButton { + text: qsTr("Telegram") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://t.me/Flowee_org"); + } +} diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 5b26aaa..6c59c83 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -56,7 +56,7 @@ QQC2.Control { Rectangle { id: header width: parent.width - height: 40 + height: 50 color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue Column { diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 788c8cf..ed8229c 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -141,6 +141,7 @@ Item { TextButton { id: textButton text: qsTr("Add Wallet") + showPageIcon: true onClicked: { thePile.push("./NewAccount.qml") root.open = false @@ -157,13 +158,13 @@ Item { anchors.topMargin: 10 width: parent.width - 20 x: 10 - spacing: 30 Repeater { model: MenuModel TextButton { text: model.name + showPageIcon: true onClicked: { var target = model.target if (target !== "") { diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index e51144d..a0816fc 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -41,7 +41,7 @@ QQC2.Control { Rectangle { id: header width: parent.width - height: 40 + height: 50 color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue Image { @@ -77,10 +77,17 @@ QQC2.Control { } } - Flickable { + Rectangle { width: parent.width y: header.height height: parent.height - y + color: root.palette.base + } + + Flickable { + width: parent.width + y: header.height + 10 + height: parent.height - y clip: true contentHeight: child.implicitHeight contentWidth: width diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index d34d276..056f318 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -21,19 +21,61 @@ import "../Flowee" as Flowee Item { id: root width: parent.width - height: label.height + 20 + height: label.height + (smallLabel.text === "" ? 0 : smallLabel.height + 6) + 20 signal clicked property alias text: label.text + property alias subtext: smallLabel.text + property bool showPageIcon: false + property string imageSource: "" + property int imageWidth: 16 + property int imageHeight: 16 Flowee.Label { id: label - anchors.verticalCenter: parent.verticalCenter + y: 10 width: parent.width wrapMode: Text.WordWrap } + Flowee.Label { + id: smallLabel + anchors.top: label.bottom + anchors.topMargin: 6 + font.pointSize: mainWindow.font.poinSize * 0.7 + font.bold: false + color: Qt.darker(palette.text, 1.5) + } MouseArea { anchors.fill: parent onClicked: root.clicked() } + + Loader { + sourceComponent: { + if (root.showPageIcon) + return pageIcon; + if (root.imageSource !== "") + return genericImage; + return undefined; + } + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + Component { + id: pageIcon + Image { + width: 12 + height: 8 + source: Pay.useDarkSkin ? "qrc:/smallArrow-light.svg" : "qrc:/smallArrow.svg"; + rotation: 270 + } + } + Component { + id: genericImage + Image { + source: root.imageSource + width: root.imageWidth + height: root.imageHeight + } + } } diff --git a/guis/mobile/images/external-light.svg b/guis/mobile/images/external-light.svg new file mode 100644 index 0000000..46e3751 --- /dev/null +++ b/guis/mobile/images/external-light.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/guis/mobile/images/external.svg b/guis/mobile/images/external.svg new file mode 100644 index 0000000..3d69e14 --- /dev/null +++ b/guis/mobile/images/external.svg @@ -0,0 +1,8 @@ + + + + + + + + -- 2.54.0 From a98efcbb1e20930ff7e303c0506b11da13718796 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 16:07:17 +0100 Subject: [PATCH 0095/1428] Remove the mouse-touch feedback It was neat, but probably not the best way to do it since this seems to interfere with scrolling. --- guis/mobile/main.qml | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index c6818fa..d37dc10 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -59,48 +59,4 @@ ApplicationWindow { id: menuOverlay anchors.fill: parent } - - Item { - id: touchFeedbackOverlay - anchors.fill: parent - visible: Pay.platform === "Android" - - Rectangle { - id: clickFeedback - color: mainWindow.floweeGreen - opacity: 0.3 - width: 70 - height: 70 - radius: 35 - visible: opacity > 0 - - Rectangle { - width: 7 - height: 7 - anchors.centerIn: parent - } - - Timer { - id: fadeTimer - running: parent.visible - interval: 50 - repeat: true - onTriggered: clickFeedback.opacity = 0 - } - Behavior on opacity { NumberAnimation { duration: 100 } } - } - - MouseArea { - anchors.fill: parent - propagateComposedEvents: true - onClicked: (event) => { - event.accepted = false; - fadeTimer.stop(); - clickFeedback.x = event.x - clickFeedback.width / 2; - clickFeedback.y = event.y - clickFeedback.height / 2; - clickFeedback.opacity = 0.4 - fadeTimer.start(); - } - } - } } -- 2.54.0 From 3435b6733c03d797cab80c74373969d54d9eba6b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 16:33:52 +0100 Subject: [PATCH 0096/1428] Avoid save-file writing On saving, check if the created data is the same as the data on disk, and avoid writing if so. Especially on a phone overwriting the same file every time is a bad idea. So this avoids that. Additionally, we now move saving from shutdown to the when the user makes the app inactive. This should make actual shutdown not need to save in almost all cases. --- FloweePay.cpp | 32 +++++++++++++++++++++++++++----- FloweePay.h | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 64a2446..97476d1 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -22,6 +22,7 @@ #include "PriceDataProvider.h" #include +#include #include #include #include @@ -96,6 +97,7 @@ FloweePay::FloweePay() connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) { p2pNet()->saveData(); + saveData(); } }); #endif @@ -300,11 +302,31 @@ void FloweePay::saveData() builder.addByteArray(WalletName, nameData.constData(), nameData.size()); } } - QString filebase = m_basedir + AppdataFilename; + + auto buf = builder.buffer(); + + const QString filebase = m_basedir + AppdataFilename; + QFile origFile(filebase); + if (origFile.open(QIODevice::ReadOnly)) { + CRIPEMD160 fileHasher; + auto origContent = origFile.readAll(); + fileHasher.write(origContent.data(), origContent.size()); + char fileHash[CRIPEMD160::OUTPUT_SIZE]; + fileHasher.finalize(fileHash); + + CRIPEMD160 memHasher; + memHasher.write(buf.begin(), buf.size()); + char memHash[CRIPEMD160::OUTPUT_SIZE]; + memHasher.finalize(memHash); + if (memcmp(fileHash, memHash, CRIPEMD160::OUTPUT_SIZE) == 0) { + // no changes, so don't write. + return; + } + } + QFile out(filebase + "~"); out.remove(); // avoid overwrite issues. if (out.open(QIODevice::WriteOnly)) { - auto buf = builder.buffer(); auto rc = out.write(buf.begin(), buf.size()); if (rc == -1) { logFatal() << "Failed to write. Disk full?"; @@ -312,13 +334,12 @@ void FloweePay::saveData() return; } out.close(); - QFile::remove(filebase); if (!out.rename(filebase)) { - logFatal() << "Failed to write to" << filebase; + logFatal() << "Failed to rename to" << filebase; // TODO have an app-wide error } } else { - logFatal() << "Failed to create data file. Disk full?"; + logFatal() << "Failed to create data file. Permissions issue?"; // TODO have an app-wide error } } @@ -502,6 +523,7 @@ Wallet *FloweePay::createWallet(const QString &name) m_wallets.append(w); emit walletsChanged(); + QTimer::singleShot(1000, this, SLOT(saveData())); return w; } diff --git a/FloweePay.h b/FloweePay.h index ba9c193..35bb62b 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -271,10 +271,10 @@ signals: private slots: void loadingCompleted(); + void saveData(); private: void init(); - void saveData(); void saveAll(); // create wallet and add to list. Please consider calling saveData after Wallet *createWallet(const QString &name); -- 2.54.0 From 47ae115fe1ae134c44586fb9c77cb0f2c7a1c9f5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 16:45:45 +0100 Subject: [PATCH 0097/1428] add emit keywoard As suggested by the cpp linter. --- NewWalletConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewWalletConfig.cpp b/NewWalletConfig.cpp index ef80661..1da9a37 100644 --- a/NewWalletConfig.cpp +++ b/NewWalletConfig.cpp @@ -31,7 +31,7 @@ NewWalletConfig::NewWalletConfig(Wallet *wallet) NewWalletConfig::~NewWalletConfig() { if (m_wallet) - m_wallet->startDelayedSave(); + emit m_wallet->startDelayedSave(); m_wallet = nullptr; } -- 2.54.0 From 68cf693b15274460d2c043b0330cb8fdedf2a86a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 19:04:03 +0100 Subject: [PATCH 0098/1428] Add a simple AccountSyncState gui. To show we are actually synchronizing with the peers, show a nice progress display and a string with the progress. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 6 +++ guis/mobile/AccountSyncState.qml | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 guis/mobile/AccountSyncState.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 90fb214..360e13f 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -25,5 +25,6 @@ mobile/Loading.qml mobile/IconButton.qml mobile/TextButton.qml + mobile/AccountSyncState.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 284561d..d0ddd6b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -215,6 +215,12 @@ ListView { } } + AccountSyncState { + account: portfolio.current + hideWhenDone: true + width: parent.width + } + /* TODO "Is archive" / "Unrchive"" diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml new file mode 100644 index 0000000..46b9289 --- /dev/null +++ b/guis/mobile/AccountSyncState.qml @@ -0,0 +1,81 @@ +import QtQuick +import QtQuick.Controls as QQC2 +// import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; +import QtQuick.Shapes // for shape-path + +Item { + id: root + height: indicator.height + 3 + (circleShape.visible ? circleShape.height : 0) + property QtObject account: null + property bool hideWhenDone: false + property int startPos: 1 + + Component.onCompleted: { + // remember the position we started the sync at. + startPos = root.account.lastBlockSynched + } + + // The 'progress' circle. + Shape { + id: circleShape + + property int goalHeight: Pay.expectedChainHeight + visible: goalHeight - root.startPos > 500 + + anchors.horizontalCenter: parent.horizontalCenter + width: 200 + height: 100 + antialiasing: true + smooth: true + ShapePath { + strokeColor: mainWindow.floweeBlue + strokeWidth: 3 + fillColor: mainWindow.floweeGreen + startX: 0; startY: 100 + + PathAngleArc { + id: progressCircle + centerX: 100 + centerY: 100 + radiusX: 100; radiusY: 100 + startAngle: -180 + sweepAngle: { + let end = circleShape.goalHeight; + let startPos = root.startPos; + if (startPos == 0) + return 0; + let currentPos = root.account.lastBlockSynched; + let totalDistance = end - startPos; + if (totalDistance == 0) + return 180; // done + let ourProgress = currentPos - startPos; + return 180 * (ourProgress / totalDistance); + } + Behavior on sweepAngle { NumberAnimation { duration: 50 } } + } + PathLine { x: 100; y: 100 } + PathLine { x: 0; y: 100 } + } + } + Flowee.Label { + id: indicator + width: parent.width + anchors.top: circleShape.bottom + anchors.topMargin: 3 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: { + if (isLoading) + return ""; + var account = portfolio.current; + if (account === null) + return ""; + if (account.needsPinToOpen && !account.isDecrypted) + return qsTr("Offline"); + return account.timeBehind; + } + font.italic: true + } +} -- 2.54.0 From 41fc639b268428d7591238a804b190d679a5f41c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Nov 2022 23:25:08 +0100 Subject: [PATCH 0099/1428] Apply filtering also on incremental growth of the model --- WalletHistoryModel.cpp | 34 ++++++++++++++++++++++------------ WalletHistoryModel.h | 4 ++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index 59f2c65..80c2e54 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "WalletHistoryModel.h" -#include "Wallet.h" #include "FloweePay.h" #include @@ -313,12 +312,19 @@ QString WalletHistoryModel::dateForItem(qreal offset) const void WalletHistoryModel::appendTransactions(int firstNew, int count) { - // lets assume sorting newest to oldest here. - beginInsertRows(QModelIndex(), 0, count - 1); + auto oldCount = m_rowsProxy.size(); for (auto i = firstNew; i < firstNew + count; ++i) { + auto iter = m_wallet->m_walletTransactions.find(i); + if (filterTransaction(iter->second)) + continue; + // lets assume sorting newest to oldest here. m_rowsProxy.insert(0, i); } - endInsertRows(); + auto insertedCount = m_rowsProxy.size() - oldCount; + if (insertedCount) { + beginInsertRows(QModelIndex(), 0, insertedCount - 1); + endInsertRows(); + } } void WalletHistoryModel::transactionChanged(int txIndex) @@ -342,14 +348,7 @@ void WalletHistoryModel::createMap() // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. for (const auto &iter : m_wallet->m_walletTransactions) { - if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) - && iter.second.isUnconfirmed()) - continue; - if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) - && iter.second.isRejected()) - continue; - if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) - && !iter.second.isUnconfirmed()) + if (filterTransaction(iter.second)) continue; m_rowsProxy.push_back(iter.first); } @@ -377,6 +376,17 @@ void WalletHistoryModel::createMap() m_groupCache = { -1 , 0 }; } +bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) const +{ + if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) + return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) && wtx.isRejected()) + return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) && !wtx.isUnconfirmed()) + return false; + return true; +} + const QFlags &WalletHistoryModel::includeFlags() const { return m_includeFlags; diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index adc5fb0..56b8c13 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -19,6 +19,7 @@ #define WALLETHSTORYMODEL_H #include +#include "Wallet.h" #include "WalletEnums.h" class Wallet; @@ -76,6 +77,9 @@ private slots: void createMap(); private: + /// returns true if the filters indicate the transaction should stay. + bool filterTransaction(const Wallet::WalletTransaction &wtx) const; + QVector m_rowsProxy; Wallet *m_wallet; QFlags m_includeFlags = WalletEnums::IncludeAll; -- 2.54.0 From 919ad7a18c0f4ae31ed3d83a7a63b3217d2bb2e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Nov 2022 10:36:35 +0100 Subject: [PATCH 0100/1428] Fix color on android Just like the Label, the color doesn't seem to automatically follow the palette. This works around that bug. --- guis/Flowee/TextField.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 7707ef5..82ec9a4 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -26,6 +26,7 @@ QQC2.TextField { selectByMouse: true // In Qt6.3 this is just always black, so adjust to color placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#3e3e3e" + color: palette.text background: Rectangle { implicitHeight: root.contentHeight + 2 -- 2.54.0 From ad5bc744dbe74cf98b813537e615960307246d1c Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Nov 2022 12:15:27 +0100 Subject: [PATCH 0101/1428] Refactor WalletHistoryModel Instead of storing the internet data in reverse, we now simply take the view-requests and revert those. This makes the amount of places where we have to account for the reversal much less (now just one) which helps maintainability. Additionally, bring back the section-is-an-enum idea to avoid a string-malloc for each row when one per section is enough. --- WalletEnums.h | 4 +- WalletHistoryModel.cpp | 255 ++++++++++++++------------------- WalletHistoryModel.h | 26 ++-- guis/mobile/AccountHistory.qml | 16 +-- 4 files changed, 137 insertions(+), 164 deletions(-) diff --git a/WalletEnums.h b/WalletEnums.h index 9f156aa..09e1d19 100644 --- a/WalletEnums.h +++ b/WalletEnums.h @@ -52,13 +52,13 @@ public: Q_FLAG(Includes) /// used by the WalletHistoryModel to group items visually - enum ItemGroupInfo { + enum PlacementInGroup { GroupStart, GroupMiddle, GroupEnd, Ungrouped }; - Q_ENUM(ItemGroupInfo) + Q_ENUM(PlacementInGroup) // Grouping period enum GroupingPeriod { Today, diff --git a/WalletHistoryModel.cpp b/WalletHistoryModel.cpp index 80c2e54..900bf6d 100644 --- a/WalletHistoryModel.cpp +++ b/WalletHistoryModel.cpp @@ -24,118 +24,58 @@ #include #include -namespace { - -struct TransactionGroup; -class TransactionGroupingResolver -{ -public: - std::vector m_groups; - - void add(int index, int blockheight); -}; - -struct TransactionGroup -{ - enum GroupingPeriod { - Today, - Yesterday, - EarlierThisWeek, // this week, but we grouped some in the previous category(s) - Week, - EarlierThisMonth, // this month, but we grouped some in the previous category(s) - Month, - Unset - }; - - GroupingPeriod period = Unset; - int startTxIndex = -1; - int endTxIndex = -1; - uint32_t startTime = 0; - uint32_t endTime = 0; - - bool add(int index, uint32_t timestamp); -}; - -void TransactionGroupingResolver::add(int index, int blockheight) -{ - if (m_groups.empty()) - m_groups.push_back(TransactionGroup()); - - if (blockheight <= 0) { - assert(m_groups.size() == 1); - // not (yet) confirmed. - m_groups.back().add(index, 0); - return; - } - - const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); - uint32_t timestamp = bc.block(blockheight).nTime; - assert(timestamp > 0); - - if (!m_groups.back().add(index, timestamp)) { - TransactionGroup newGroup; - newGroup.period = m_groups.back().period; - bool ok = newGroup.add(index, timestamp); - assert (ok); - m_groups.push_back(newGroup); - } -} - /* * Attempt to add a transaction to this group. + * Retuns false if the txIndex is not meant for this group */ -bool TransactionGroup::add(int index, uint32_t timestamp) +bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) { if (startTxIndex == -1) { - endTxIndex = index; + startTxIndex = txIndex; // first one in this group. Now we need to decide which period we area actually looking at. QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); int days = 0; - - if (period != Month) { - QDate today = QDate::currentDate(); - if (date == today) { - period = Today; - days = 1; - } - else if (date == today.addDays(-1)) { - period = Yesterday; - date = date.addDays(-1); - days = 1; - } - else if (date > today.addDays(-1 * today.dayOfWeek() + 1)) { - // this week - period = period == Unset ? Week : EarlierThisWeek; - date = date.addDays(-1 * date.dayOfWeek()); - days = 7; - } - else if (date > today.addDays(-1 * today.day() + 1)) { - // this month - period = EarlierThisMonth; - date = date.addDays(-1 * date.day() + 1); - days = date.daysInMonth(); - } + QDate today = QDate::currentDate(); + if (date == today) { + period = WalletEnums::Today; + days = 1; } - if (days == 0) { // any (other) month - period = Month; + else if (date == today.addDays(-1)) { + period = WalletEnums::Yesterday; + date = date.addDays(-1); + days = 1; + } + else if (date > today.addDays(-1 * today.dayOfWeek() + 1)) { + // this week + period = WalletEnums::EarlierThisWeek; + date = date.addDays(-1 * date.dayOfWeek()); + days = 7; + } + else if (date > today.addDays(-1 * today.day() + 1)) { + // this month + period = WalletEnums::EarlierThisMonth; + date = date.addDays(-1 * date.day() + 1); + days = date.daysInMonth(); + } + else { // any (other) month + period = WalletEnums::Month; date = date.addDays(-1 * date.day() + 1); days = date.daysInMonth(); } assert(days > 0); const QDateTime dt(date, QTime()); - startTime = dt.toSecsSinceEpoch(); endTime = dt.addDays(days).toSecsSinceEpoch() - 1; } - else if (timestamp < startTime) { + else if (timestamp > endTime) { // doesn't fit in our time-period. return false; } - startTxIndex = index; + endTxIndex = txIndex; return true; } -}; +// ------- WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) @@ -169,7 +109,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const assert(m_rowsProxy.size() > index.row()); assert(index.row() >= 0); - const int txIndex = m_rowsProxy.at(index.row()); + const int txIndex = m_rowsProxy.at(m_rowsProxy.size() - index.row() - 1); // reverse index to make the VIEW have newest at the top // logDebug() << " getting" << index.row() << "=>" << txIndex; auto itemIter = m_wallet->m_walletTransactions.find(txIndex); assert(itemIter != m_wallet->m_walletTransactions.end()); @@ -211,7 +151,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(double(value)); } case WalletIndex: - return QVariant(m_rowsProxy.at(index.row())); + return QVariant(txIndex); case NewTransaction: return QVariant(item.minedBlockHeight > m_lastSyncIndicator || item.isUnconfirmed()); case IsCoinbase: @@ -220,40 +160,21 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(item.isCashFusionTx); case Comment: return QVariant(item.userComment); - case ItemGroupInfo: { + case PlacementInGroup: { + // notice that since we invert the ordering, the end/start get inverted too const auto &group = m_groups.at(groupIdForTxIndex(txIndex)); const bool start = group.startTxIndex == txIndex; const bool end = group.endTxIndex == txIndex; if (start && end) return WalletEnums::Ungrouped; - // notice that since we invert the ordering, the end/start get inverted too if (start) return WalletEnums::GroupEnd; if (end) return WalletEnums::GroupStart; return WalletEnums::GroupMiddle; } - case ItemGroupPeriod: - switch (m_groups.at(groupIdForTxIndex(txIndex)).period) { - case WalletEnums::Today: - return tr("Today"); - case WalletEnums::Yesterday: - return tr("Yesterday"); - case WalletEnums::EarlierThisWeek: - return tr("Earlier this week"); - case WalletEnums::Week: - return tr("This week"); - case WalletEnums::EarlierThisMonth: - return tr("Earlier this month"); - case WalletEnums::Month: - default: { - const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); - uint32_t timestamp = bc.block(itemIter->second.minedBlockHeight).nTime; - assert(timestamp > 0); - QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); - return date.toString("MMMM"); - } - } + case GroupId: + return groupIdForTxIndex(txIndex); } return QVariant(); @@ -274,6 +195,33 @@ int WalletHistoryModel::groupIdForTxIndex(int txIndex) const return m_groupCache.groupId; } + +QString WalletHistoryModel::groupingPeriod(int groupId) const +{ + if (groupId < 0 || groupId >= static_cast(m_groups.size())) + return "bad groupId"; + switch (m_groups.at(groupId).period) { + case WalletEnums::Today: + return tr("Today"); + case WalletEnums::Yesterday: + return tr("Yesterday"); + case WalletEnums::EarlierThisWeek: + return tr("Earlier this week"); + case WalletEnums::Week: + return tr("This week"); + case WalletEnums::EarlierThisMonth: + return tr("Earlier this month"); + case WalletEnums::Month: + default: { + uint32_t timestamp = m_groups.at(groupId).endTime; + QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); + if (date.year() == QDate::currentDate().year()) + return date.toString("MMMM"); + return date.toString("MMMM yyyy"); + } + } +} + QHash WalletHistoryModel::roleNames() const { QHash answer; @@ -287,8 +235,8 @@ QHash WalletHistoryModel::roleNames() const answer[IsCoinbase] = "isCoinbase"; answer[IsCashFusion] = "isCashFusion"; answer[Comment] = "comment"; - answer[ItemGroupInfo] = "groupType"; - answer[ItemGroupPeriod] = "grouping"; + answer[PlacementInGroup] = "placementInGroup"; + answer[GroupId] = "grouping"; // answer[SavedFiatRate] = "savedFiatRate"; return answer; @@ -296,11 +244,11 @@ QHash WalletHistoryModel::roleNames() const QString WalletHistoryModel::dateForItem(qreal offset) const { - if (std::isnan(offset)) + if (m_rowsProxy.isEmpty()) + return QString(); + if (std::isnan(offset) || offset < 0 || offset > 1.0) return QString(); const size_t row = std::round(offset * m_rowsProxy.size()); - if (row >= m_wallet->m_walletTransactions.size()) - return QString(); auto item = m_wallet->m_walletTransactions.at(m_rowsProxy.at(row)); if (item.minedBlockHeight <= 0) return QString(); @@ -312,16 +260,25 @@ QString WalletHistoryModel::dateForItem(qreal offset) const void WalletHistoryModel::appendTransactions(int firstNew, int count) { - auto oldCount = m_rowsProxy.size(); + const auto oldCount = m_rowsProxy.size(); for (auto i = firstNew; i < firstNew + count; ++i) { auto iter = m_wallet->m_walletTransactions.find(i); - if (filterTransaction(iter->second)) + if (!filterTransaction(iter->second)) continue; - // lets assume sorting newest to oldest here. - m_rowsProxy.insert(0, i); + m_rowsProxy.push_back(i); + addTxIndexToGroups(iter->first, iter->second.minedBlockHeight); } auto insertedCount = m_rowsProxy.size() - oldCount; if (insertedCount) { + if (oldCount) { + // Due to grouping being drawn in relation, we need to force + // a change in the previously top item + // remove the top one + beginRemoveRows(QModelIndex(), 0, 0); + endRemoveRows(); + ++insertedCount; + } + // and insert the new ones, plus the one we just removed. beginInsertRows(QModelIndex(), 0, insertedCount - 1); endInsertRows(); } @@ -342,37 +299,19 @@ void WalletHistoryModel::createMap() m_recreateTriggered = false; m_rowsProxy.clear(); m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); - - TransactionGroupingResolver resolver; + m_groups.clear(); // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. + // This is oldest to newest, which is how our model is also structured. for (const auto &iter : m_wallet->m_walletTransactions) { - if (filterTransaction(iter.second)) + if (!filterTransaction(iter.second)) continue; m_rowsProxy.push_back(iter.first); - } - // the simplest form; reverse order. This assumes the last entry is the newest one - std::reverse(m_rowsProxy.begin(), m_rowsProxy.end()); - - // Last, resolve grouping - for (int id : m_rowsProxy) { - auto iter = m_wallet->m_walletTransactions.find(id); - assert(iter != m_wallet->m_walletTransactions.end()); - resolver.add(iter->first, iter->second.minedBlockHeight); + // Last, resolve grouping + addTxIndexToGroups(iter.first, iter.second.minedBlockHeight); } - // copy out the resolved groups to the lower-overhead ones. - m_groups.clear(); - for (auto g : resolver.m_groups) { - GroupInfo groupInfo; - groupInfo.startTxIndex = g.startTxIndex; - groupInfo.endTxIndex = g.endTxIndex; - assert(g.period != TransactionGroup::Unset); - static_assert(static_cast(TransactionGroup::Week) == WalletEnums::Week); - groupInfo.period = static_cast(g.period); - m_groups.push_back(groupInfo); - } m_groupCache = { -1 , 0 }; } @@ -387,6 +326,30 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) return true; } +void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) +{ + if (m_groups.empty()) + m_groups.push_back(TransactionGroup()); + + uint32_t timestamp; + if (blockheight <= 0) { + timestamp = time(nullptr); + } else { + const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); + timestamp = bc.block(blockheight).nTime; + } + assert(timestamp > 0); + + if (!m_groups.back().add(txIndex, timestamp)) { + // didn't fit, make a new group and add it there. + TransactionGroup newGroup; + newGroup.period = m_groups.back().period; + bool ok = newGroup.add(txIndex, timestamp); + assert (ok); + m_groups.push_back(newGroup); + } +} + const QFlags &WalletHistoryModel::includeFlags() const { return m_includeFlags; diff --git a/WalletHistoryModel.h b/WalletHistoryModel.h index 56b8c13..2e52a2e 100644 --- a/WalletHistoryModel.h +++ b/WalletHistoryModel.h @@ -44,8 +44,8 @@ public: IsCoinbase, IsCashFusion, Comment, - ItemGroupInfo, ///< Is an enum WalletEnums::ItemGroupInfo to help with painting outlines. - ItemGroupPeriod, ///< Is a string describing/identifying the group + PlacementInGroup, ///< Is an enum WalletEnums::PlacementInGroup to help with painting outlines. + GroupId ///< The index in the m_groups vector // SavedFiatRate, // TODO }; @@ -59,6 +59,7 @@ public: * The value 0.0 is the most recent item. */ Q_INVOKABLE QString dateForItem(qreal offset) const; + Q_INVOKABLE QString groupingPeriod(int groupId) const; int lastSyncIndicator() const; void setLastSyncIndicator(int x); @@ -67,6 +68,18 @@ public: const QFlags &includeFlags() const; void setIncludeFlags(const QFlags &flags); + // Indicates a grouping of transactions, specifically based on a time-period + struct TransactionGroup + { + WalletEnums::GroupingPeriod period = WalletEnums::Month; + int startTxIndex = -1; + int endTxIndex = -1; + uint32_t endTime = 0; + + /// returns true if added, false if the tx-index is not for this group + bool add(int txIndex, uint32_t timestamp); + }; + signals: void lastSyncIndicatorChanged(); void includeFlagsChanged(); @@ -79,17 +92,14 @@ private slots: private: /// returns true if the filters indicate the transaction should stay. bool filterTransaction(const Wallet::WalletTransaction &wtx) const; + /// Update m_groups to include this transaction. + void addTxIndexToGroups(int txIndex, int blockheight); QVector m_rowsProxy; Wallet *m_wallet; QFlags m_includeFlags = WalletEnums::IncludeAll; - struct GroupInfo { - WalletEnums::GroupingPeriod period; - int startTxIndex; - int endTxIndex; - }; - std::vector m_groups; + std::vector m_groups; struct GroupCache { int txIndex; int groupId; diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index d0ddd6b..408f3a0 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -246,7 +246,7 @@ ListView { id: label x: 10 y: 12 - text: section + text: portfolio.current.transactions.groupingPeriod(section); } /* TODO; to-top button @@ -262,7 +262,7 @@ ListView { } delegate: Item { id: transactionDelegate - property var groupType: model.groupType + property var placementInGroup: model.placementInGroup width: root.width height: 80 @@ -271,17 +271,17 @@ ListView { Rectangle { width: parent.width - 16 x: 8 - visible: transactionDelegate.groupType !== Wallet.Ungrouped; + visible: transactionDelegate.placementInGroup !== Wallet.Ungrouped; // we always have the rounded circles, but if we should not see them, we move them out of the screen. height: { var h = 80 - if (transactionDelegate.groupType !== Wallet.GroupStart) + if (transactionDelegate.placementInGroup !== Wallet.GroupStart) h += 20; - if (transactionDelegate.groupType !== Wallet.GroupEnd) + if (transactionDelegate.placementInGroup !== Wallet.GroupEnd) h += 20; return h; } - y: transactionDelegate.groupType === Wallet.GroupStart ? 0 : -20; + y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; radius: 20 color: mainWindow.palette.base @@ -354,8 +354,8 @@ ListView { } Rectangle { - visible: transactionDelegate.groupType !== Wallet.GroupEnd - && transactionDelegate.groupType !== Wallet.Ungrouped; + visible: transactionDelegate.placementInGroup !== Wallet.GroupEnd + && transactionDelegate.placementInGroup !== Wallet.Ungrouped; anchors.bottom: parent.bottom height: 1 width: parent.width - 16 -- 2.54.0 From fdaf539a0c36dcdaa373764de6e1921ac50800c1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Nov 2022 13:11:09 +0100 Subject: [PATCH 0102/1428] Fixes and add 'move to top' button We moved the initial-sync-height from the view to the model, which gives a more logical progress update when switching between accounts. Also added a button on the listview to instantly jump to the top of the list. --- AccountInfo.cpp | 16 ++++-- AccountInfo.h | 8 +++ guis/mobile/AccountHistory.qml | 88 +++++++++++++++++++++----------- guis/mobile/AccountSyncState.qml | 58 +++++++++++++++------ 4 files changed, 119 insertions(+), 51 deletions(-) diff --git a/AccountInfo.cpp b/AccountInfo.cpp index 286dda3..5bf6d9d 100644 --- a/AccountInfo.cpp +++ b/AccountInfo.cpp @@ -29,12 +29,17 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) : QObject(parent), - m_wallet(wallet) + m_wallet(wallet), + m_initialBlockHeight(wallet->segment()->lastBlockSynched()) { connect(wallet, SIGNAL(utxosChanged()), this, SIGNAL(utxosChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(balanceChanged()), this, SLOT(balanceHasChanged()), Qt::QueuedConnection); - connect(wallet, SIGNAL(lastBlockSynchedChanged()), this, SIGNAL(lastBlockSynchedChanged()), Qt::QueuedConnection); - connect(wallet, SIGNAL(lastBlockSynchedChanged()), this, SIGNAL(timeBehindChanged()), Qt::QueuedConnection); + connect(wallet, &Wallet::lastBlockSynchedChanged, m_wallet, [=]() { + if (m_initialBlockHeight < 0) + m_initialBlockHeight = m_wallet->segment()->lastBlockSynched(); + emit lastBlockSynchedChanged(); + emit timeBehindChanged(); + }, Qt::QueuedConnection); connect(wallet, SIGNAL(paymentRequestsChanged()), this, SIGNAL(paymentRequestsChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged()), Qt::QueuedConnection); connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); @@ -208,6 +213,11 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +int AccountInfo::initialBlockHeight() const +{ + return m_initialBlockHeight; +} + bool AccountInfo::hasFreshTransactions() const { return m_hasFreshTransactions; diff --git a/AccountInfo.h b/AccountInfo.h index 54b0068..643e767 100644 --- a/AccountInfo.h +++ b/AccountInfo.h @@ -41,6 +41,11 @@ class AccountInfo : public QObject Q_PROPERTY(int historicalOutputCount READ historicalOutputCount NOTIFY utxosChanged) Q_PROPERTY(int id READ id CONSTANT) Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) + /** + * This is the first block that on this instantiation we need to sync. + * This property is useful to determine how much we need to sync this session. + */ + Q_PROPERTY(int initialBlockHeight READ initialBlockHeight NOTIFY lastBlockSynchedChanged) Q_PROPERTY(QDateTime lastBlockSynchedTime READ lastBlockSynchedTime NOTIFY lastBlockSynchedChanged) /// Return a user-readable indication of the amount of time 'behind' this account is Q_PROPERTY(QString timeBehind READ timeBehind NOTIFY lastBlockSynchedChanged) @@ -150,6 +155,8 @@ public: bool needsPinToOpen() const; bool isDecrypted() const; + int initialBlockHeight() const; + signals: void balanceChanged(); void utxosChanged(); @@ -175,6 +182,7 @@ private: std::unique_ptr m_model; std::unique_ptr m_secretsModel; int m_lastTxHeight = -1; ///< last seen tx blockheight. + int m_initialBlockHeight; bool m_hasFreshTransactions = false; friend class WalletSecret; diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 408f3a0..ce146e2 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -27,6 +27,34 @@ ListView { property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); property string title: qsTr("Home") + Rectangle { + width: 60 + height: 60 + anchors.right: parent.right + y: (root.contentY > (root.height / 4)) ? 0 : height * -1 + color: "#66000000" + + Image { + id: upIcon + source: "qrc:/back-arrow.svg" + rotation: 90 + width: 15 + height: 20 + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: root.positionViewAtIndex(0, ListView.Contain); + } + + Behavior on y { + NumberAnimation { + easing.type: Easing.OutElastic + duration: 500 + } + } + } + /* The structure here is a bit funny. But we want to have the entire page scroll in order to show all the transactions we can. @@ -35,10 +63,28 @@ ListView { */ header: ColumnLayout { id: column - spacing: 20 + spacing: 10 width: root.width - 20 x: 10 y: 10 + z: 10 // make sure the wallet Selector can cover the historical items + + Row { + width: parent.width + height: 60 + IconButton { + width: parent.width / 3 + text: qsTr("Send Money") + } + IconButton { + width: parent.width / 3 + text: qsTr("Scheduled") + } + IconButton { + width: parent.width / 3 + text: qsTr("Receive") + } + } Item { id: smallAccountSelector @@ -50,7 +96,11 @@ ListView { visible: { // if there is only an initial, not user-owned wallet, there is nothing to show here. - return portfolio.current.isUserOwned + if (!portfolio.current.isUserOwned) + return false; + if (portfolio.accounts.length === 1) + return false; + return true; } Rectangle { @@ -198,23 +248,6 @@ ListView { colorize: false } - Row { - width: parent.width - height: 60 - IconButton { - width: parent.width / 3 - text: qsTr("Send Money") - } - IconButton { - width: parent.width / 3 - text: qsTr("Scheduled") - } - IconButton { - width: parent.width / 3 - text: qsTr("Receive") - } - } - AccountSyncState { account: portfolio.current hideWhenDone: true @@ -226,6 +259,8 @@ ListView { Is Encryopted / Decrypt */ + + Item { width: 10; height: 15 } // spacer } model: portfolio.current.transactions @@ -248,17 +283,6 @@ ListView { y: 12 text: portfolio.current.transactions.groupingPeriod(section); } - -/* TODO; to-top button - Image { - source: "qrc:/back-arrow.svg" - rotation: 90 - width: 15 - height: 20 - anchors.right: parent.right - anchors.rightMargin: 20 - anchors.bottom: parent.bottom - } */ } delegate: Item { id: transactionDelegate @@ -272,7 +296,7 @@ ListView { width: parent.width - 16 x: 8 visible: transactionDelegate.placementInGroup !== Wallet.Ungrouped; - // we always have the rounded circles, but if we should not see them, we move them out of the screen. + // we always have the rounded circles, but if we should not see them, we move them out of the clipped area. height: { var h = 80 if (transactionDelegate.placementInGroup !== Wallet.GroupStart) @@ -363,6 +387,8 @@ ListView { color: root.palette.highlight } } + displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } + Keys.forwardTo: Flowee.ListViewKeyHandler { target: root } diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 46b9289..bbdc1df 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -1,20 +1,44 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import QtQuick -import QtQuick.Controls as QQC2 -// import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; import QtQuick.Shapes // for shape-path Item { id: root - height: indicator.height + 3 + (circleShape.visible ? circleShape.height : 0) + height: indicator.height + 3 + (done ? 0 : circleShape.height) property QtObject account: null property bool hideWhenDone: false - property int startPos: 1 - - Component.onCompleted: { - // remember the position we started the sync at. - startPos = root.account.lastBlockSynched + property bool done: false + property int startPos: account.initialBlockHeight + onAccountChanged: { + let startPos = account.initialBlockHeight; + if (startPos === -2) { + // special number, the account is just created and waits for transactions. + done = true; + return; + } + let end = Pay.expectedChainHeight; + let currentPos = account.lastBlockSynched; + // only show the progress-circle when its more than 2 days behind + // and we are not synched + done = end - startPos < 300 || end - currentPos < 2; } // The 'progress' circle. @@ -22,8 +46,7 @@ Item { id: circleShape property int goalHeight: Pay.expectedChainHeight - visible: goalHeight - root.startPos > 500 - + visible: !root.done anchors.horizontalCenter: parent.horizontalCenter width: 200 height: 100 @@ -62,20 +85,21 @@ Item { Flowee.Label { id: indicator width: parent.width - anchors.top: circleShape.bottom - anchors.topMargin: 3 - horizontalAlignment: Text.AlignHCenter + y: root.done ? 0 : circleShape.height + 3 wrapMode: Text.Wrap text: { + var buddy = qsTr("Network Status") + ": "; if (isLoading) - return ""; + return buddy; var account = portfolio.current; if (account === null) - return ""; + return buddy; if (account.needsPinToOpen && !account.isDecrypted) - return qsTr("Offline"); - return account.timeBehind; + return buddy + qsTr("Offline"); + return buddy + account.timeBehind; } font.italic: true + Behavior on y { NumberAnimation { duration: 100 } } } + Behavior on height { NumberAnimation { duration: 100 } } } -- 2.54.0 From f69049a12a1970edf910e03e208f363053cee605 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Nov 2022 15:09:26 +0100 Subject: [PATCH 0103/1428] Make delayed save work from non-qt threads too. --- FloweePay.cpp | 12 ++++++++---- FloweePay.h | 5 ++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 97476d1..0a4841e 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -163,6 +163,12 @@ FloweePay::FloweePay() // forward signal connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); + connect (this, &FloweePay::startSaveDate_priv, this, [=]() { + // As Qt does not allow starting a timer from any thread, we first use a signal + // to move the request to save to the main thread, after which we schedule it with + // the singleshot below. + QTimer::singleShot(1000, this, SLOT(saveData())); + }, Qt::QueuedConnection); } FloweePay::~FloweePay() @@ -253,7 +259,6 @@ void FloweePay::init() m_wallets.at(0)->setUserOwnedWallet(false); m_wallets.at(0)->segment()->setPriority(PrivacySegment::Last); m_wallets.at(0)->setName(tr("Initial Wallet")); - saveData(); } emit loadComplete_priv(); // move execution to loadingCompleted, in a Qt thread @@ -334,6 +339,7 @@ void FloweePay::saveData() return; } out.close(); + QFile::remove(filebase); if (!out.rename(filebase)) { logFatal() << "Failed to rename to" << filebase; // TODO have an app-wide error @@ -523,7 +529,7 @@ Wallet *FloweePay::createWallet(const QString &name) m_wallets.append(w); emit walletsChanged(); - QTimer::singleShot(1000, this, SLOT(saveData())); + emit startSaveDate_priv(); // schedule a save of the new wallet return w; } @@ -763,7 +769,6 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; wallet->addPrivateKey(privateKey.trimmed().remove('\n'), startHeight); - saveData(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -815,7 +820,6 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons derivationPath, startHeight); wallet->segment()->blockSynched(startHeight); wallet->segment()->blockSynched(startHeight); // yes, twice - saveData(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. diff --git a/FloweePay.h b/FloweePay.h index 35bb62b..4a7cc56 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -254,11 +254,11 @@ signals: void loadComplete(); /// \internal void loadComplete_priv(); - + /// \internal + void startSaveDate_priv(); void unitChanged(); void walletsChanged(); void darkSkinChanged(); - void windowWidthChanged(); void windowHeightChanged(); void headerChainHeightChanged(); @@ -266,7 +266,6 @@ signals: void dspTimeoutChanged(); void hideBalanceChanged(); void newBlockMutedChanged(); - void fontScalingChanged(); private slots: -- 2.54.0 From 9b737871dc11e9964cb5ae1d7a13761e2a6cdaa5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Nov 2022 20:00:53 +0100 Subject: [PATCH 0104/1428] Add network logging client When doing development on Android we don't really have a good way to see what is going on or run a second application showing the log-lines. So the simplest solution is to just use what we already have in Flowee: a logging system with pluggable channels. Now, we only want this during debugging, so this is a CMake option and the file is compiled and included only when you set that. Additionally, the code won't do anything unless there is a config file included in the APK (assets dir) with the host etc. --- CMakeLists.txt | 9 ++++ FloweePay.h | 2 +- NetworkLogClient.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++++ NetworkLogClient.h | 47 +++++++++++++++++++++ main.cpp | 7 ++++ 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 NetworkLogClient.cpp create mode 100644 NetworkLogClient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e6fe1c..fac412b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) find_package(QREncode REQUIRED) option(local_qml "Allow local QML loading" OFF) +option(networkLog "Include network-logging client" OFF) add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) # The cmake system name will hold values like Android, Linux or others. @@ -85,6 +86,10 @@ set (PAY_SOURCES WalletEnums.cpp ) +if (NetworkLogClient) + list(APPEND PAY_SOURCES NetworkLogClient.cpp) +endif() + if (ANDROID) list(APPEND PAY_SOURCES main_utils_android.cpp) else () @@ -278,6 +283,10 @@ message("") message("Configuration results:") message("----------------------") message("Target OS: ${CMAKE_SYSTEM_NAME}") +if (NetworkLogClient) + message ("-> Including network-logging capability") + add_compile_definitions(NETWORK_LOGGER) +endif() if (${BUILD_DESKTOP_PAY}) message ("-> Building Desktop-Pay...") if (${Qt6DBus_FOUND}) diff --git a/FloweePay.h b/FloweePay.h index 4a7cc56..2381e38 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -46,7 +46,7 @@ class PriceHistoryDataProvider; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); -class FloweePay : public QObject, WorkerThreads, P2PNetInterface +class FloweePay : public QObject, public WorkerThreads, P2PNetInterface { Q_OBJECT Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) diff --git a/NetworkLogClient.cpp b/NetworkLogClient.cpp new file mode 100644 index 0000000..943a17f --- /dev/null +++ b/NetworkLogClient.cpp @@ -0,0 +1,99 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "NetworkLogClient.h" +#include "streaming/BufferPools.h" + +#include +#include + +#include + +enum NetworkTags { + Locator = 1, // a byte-array + Content, // a byte-array +}; + +enum ContentPayloadTags { + LogSection = 0, + LogLevel, + LogLine +}; + +NetworkLogClient::NetworkLogClient(boost::asio::io_service &ioService) + : Log::Channel(NoTime), + m_network(ioService) +{ + QString filename; +#if TARGET_OS_Android + filename = QLatin1String("assets:/netlog.conf"); +#else + filename = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "netlog.conf"); +#endif + QSettings settings(filename, QSettings::IniFormat); + const QString server = settings.value("server").toString(); + const int port = settings.value("port", "21712").toInt(); + m_logPath = settings.value("logpath").toString().toStdString(); + if (!server.isEmpty() && port > 0 && port < 0xFFFF) { + m_enabled = true; + if (m_logPath.size() < 1 || m_logPath[0] != '/' || m_logPath[m_logPath.size() - 1] != '/') + throw std::runtime_error("Incorrect or missing logpath config. Needs to start and end with a slash"); + m_con = m_network.connection({server.toStdString(), uint16_t(port)}); + } +} + +void NetworkLogClient::pushLog(int64_t timeMillis, std::string *timestamp, const std::string &line, const char *filename, int lineNumber, const char *methodName, short logSection, short logLevel) +{ + /* + * since this is primarily meant to be used on Android, we expect a release build to be used. + * As a result things like filename, methodname and linenumber will simply not be present. + * Additionally, in the constructor we stated we use "NoTime" which will cause the timestamp not to be built for us. + */ + Q_UNUSED(timestamp); + Q_UNUSED(filename); + Q_UNUSED(methodName); + + if (!m_enabled) + return; + + // first build the message content. + Streaming::MessageBuilder payloadBuilder(Streaming::pool(line.size() + 20)); + payloadBuilder.add(LogSection, logSection); + payloadBuilder.add(LogLevel, logLevel); + payloadBuilder.add(LogLine, line); + auto payload = payloadBuilder.message(); + + // then build the locator + auto &pool = Streaming::pool(m_logPath.size() + 20); + pool.write(m_logPath); + pool.write(std::to_string(timeMillis)); + auto locator = pool.commit(); + + pool.reserve(payload.body().size() + locator.size() + 20); + Streaming::MessageBuilder builder(pool); + builder.add(Locator, locator); + builder.add(Content, payload.body()); + try { + m_con.send(builder.message(124, 0)); + } catch (const std::exception &e) { + // most likely this is hit when the network layer ran out of buffers + // and the connection is too slow or could never be established. + m_enabled = false; + logFatal() << "Failed to log due to" << e; + } +} diff --git a/NetworkLogClient.h b/NetworkLogClient.h new file mode 100644 index 0000000..c1547e5 --- /dev/null +++ b/NetworkLogClient.h @@ -0,0 +1,47 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NETWORKLOGCLIENT_H +#define NETWORKLOGCLIENT_H + +#include +#include + +#include + +/** + * This is a client for the Flowee debugging system which can log over the network. + * + * When doing development on Android we don't really have a good way to see what is going on + * or run a second application showing the log-lines. So the simplest solution is to just use + * what we already have in Flowee: a logging system with pluggable channels. + */ +class NetworkLogClient : public Log::Channel +{ +public: + NetworkLogClient(boost::asio::io_service& ioService); + + void pushLog(int64_t timeMillis, std::string *timestamp, const std::string &line, const char *filename, int lineNumber, const char *methodName, short logSection, short logLevel); + +private: + NetworkManager m_network; + bool m_enabled = false; + NetworkConnection m_con; + std::string m_logPath; +}; + +#endif diff --git a/main.cpp b/main.cpp index da9e3f4..06bd61f 100644 --- a/main.cpp +++ b/main.cpp @@ -24,6 +24,9 @@ #include "PaymentRequest.h" #include "QRCreator.h" #include "MenuModel.h" +#ifdef NETWORK_LOGGER +# include "NetworkLogClient.h" +#endif #include // for ECC_Start() @@ -91,6 +94,7 @@ int main(int argc, char *argv[]) auto *logger = Log::Manager::instance(); logger->clearChannels(); logger->clearLogLevels(logVerbosity(cld)); + logger->addConsoleChannel(); auto blockheaders = handleStaticChain(cld); @@ -139,6 +143,9 @@ int main(int argc, char *argv[]) QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld]() { loadCompleteHandler(engine, cld); }); +#ifdef NETWORK_LOGGER + logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); +#endif // Clean shutdown on SIGTERM struct sigaction sa; -- 2.54.0 From 7293f917c6c224ce8be6b33882cbb7c23f4c3ab5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Nov 2022 13:55:52 +0100 Subject: [PATCH 0105/1428] Fix shutdown crash when enabling the network logger. --- NetworkLogClient.cpp | 15 ++++++++++----- NetworkLogClient.h | 1 - main.cpp | 1 - main_utils_android.cpp | 4 ++++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/NetworkLogClient.cpp b/NetworkLogClient.cpp index 943a17f..b4700c6 100644 --- a/NetworkLogClient.cpp +++ b/NetworkLogClient.cpp @@ -17,12 +17,12 @@ */ #include "NetworkLogClient.h" -#include "streaming/BufferPools.h" +#include +#include #include #include -#include enum NetworkTags { Locator = 1, // a byte-array @@ -36,8 +36,7 @@ enum ContentPayloadTags { }; NetworkLogClient::NetworkLogClient(boost::asio::io_service &ioService) - : Log::Channel(NoTime), - m_network(ioService) + : Log::Channel(NoTime) { QString filename; #if TARGET_OS_Android @@ -53,7 +52,13 @@ NetworkLogClient::NetworkLogClient(boost::asio::io_service &ioService) m_enabled = true; if (m_logPath.size() < 1 || m_logPath[0] != '/' || m_logPath[m_logPath.size() - 1] != '/') throw std::runtime_error("Incorrect or missing logpath config. Needs to start and end with a slash"); - m_con = m_network.connection({server.toStdString(), uint16_t(port)}); + + // This is a workaround for a lifetime issue. + // See, the ioService is owned by the application-singleton. + // we are owned by the logging framework. Which, sanely, has a longer lifetime than the application singleton. + // Solution; let the OS delete the network manager the hard way. + auto network = new NetworkManager(ioService); + m_con = network->connection({server.toStdString(), uint16_t(port)}); } } diff --git a/NetworkLogClient.h b/NetworkLogClient.h index c1547e5..97673dc 100644 --- a/NetworkLogClient.h +++ b/NetworkLogClient.h @@ -38,7 +38,6 @@ public: void pushLog(int64_t timeMillis, std::string *timestamp, const std::string &line, const char *filename, int lineNumber, const char *methodName, short logSection, short logLevel); private: - NetworkManager m_network; bool m_enabled = false; NetworkConnection m_con; std::string m_logPath; diff --git a/main.cpp b/main.cpp index 06bd61f..415b0fa 100644 --- a/main.cpp +++ b/main.cpp @@ -94,7 +94,6 @@ int main(int argc, char *argv[]) auto *logger = Log::Manager::instance(); logger->clearChannels(); logger->clearLogLevels(logVerbosity(cld)); - logger->addConsoleChannel(); auto blockheaders = handleStaticChain(cld); diff --git a/main_utils_android.cpp b/main_utils_android.cpp index 08ee999..83410be 100644 --- a/main_utils_android.cpp +++ b/main_utils_android.cpp @@ -38,7 +38,11 @@ CommandLineParserData* createCLD(QGuiApplication &) Log::Verbosity logVerbosity(CommandLineParserData*) { +#ifdef NETWORK_LOGGER + return Log::DebugLevel; +#else return Log::FatalLevel; +#endif } std::unique_ptr handleStaticChain(CommandLineParserData*) -- 2.54.0 From 8e175343e1289092d359afac1e35e5aecfae7a25 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Nov 2022 21:46:53 +0100 Subject: [PATCH 0106/1428] Fix android losing the wallet name --- FloweePay.cpp | 5 +++++ Wallet.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/FloweePay.cpp b/FloweePay.cpp index 0a4841e..03a2b1f 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -96,6 +96,8 @@ FloweePay::FloweePay() assert(guiApp); connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) { + logInfo() << "App went Inactive. Start saving data"; + saveAll(); p2pNet()->saveData(); saveData(); } @@ -254,11 +256,13 @@ void FloweePay::init() } if (m_wallets.isEmpty() && m_createStartWallet) { + logInfo() << "Creating startup (initial) wallet"; auto config = createNewWallet(QLatin1String(DefaultDerivationPath)); delete config; // the config was just for QML, so avoid a dangling object. m_wallets.at(0)->setUserOwnedWallet(false); m_wallets.at(0)->segment()->setPriority(PrivacySegment::Last); m_wallets.at(0)->setName(tr("Initial Wallet")); + m_wallets.at(0)->saveWallet(); } emit loadComplete_priv(); // move execution to loadingCompleted, in a Qt thread @@ -526,6 +530,7 @@ Wallet *FloweePay::createWallet(const QString &name) Wallet *w = Wallet::createWallet(m_basedir.toStdString(), id, name); dl->addDataListener(w); dl->connectionManager().addPrivacySegment(w->segment()); + w->moveToThread(thread()); m_wallets.append(w); emit walletsChanged(); diff --git a/Wallet.cpp b/Wallet.cpp index d0b9c4a..bcd70f1 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -1925,6 +1925,7 @@ void Wallet::saveWallet() outFile.write(nameBytes.constBegin(), nameBytes.size()); outFile.flush(); outFile.close(); + boost::filesystem::remove(m_basedir / "name"); boost::filesystem::rename(m_basedir / "name~", m_basedir / "name"); m_walletNameChanged = false; } catch (const std::exception &e) { -- 2.54.0 From d7524508e418b69448cbd8ac59b3db1d3760175b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Nov 2022 21:47:28 +0100 Subject: [PATCH 0107/1428] Support logging at finer than millisecond resolution. --- NetworkLogClient.cpp | 10 ++++++++++ NetworkLogClient.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/NetworkLogClient.cpp b/NetworkLogClient.cpp index b4700c6..3c4439e 100644 --- a/NetworkLogClient.cpp +++ b/NetworkLogClient.cpp @@ -72,6 +72,7 @@ void NetworkLogClient::pushLog(int64_t timeMillis, std::string *timestamp, const Q_UNUSED(timestamp); Q_UNUSED(filename); Q_UNUSED(methodName); + Q_UNUSED(lineNumber); if (!m_enabled) return; @@ -87,6 +88,15 @@ void NetworkLogClient::pushLog(int64_t timeMillis, std::string *timestamp, const auto &pool = Streaming::pool(m_logPath.size() + 20); pool.write(m_logPath); pool.write(std::to_string(timeMillis)); + if (timeMillis == m_lastTimestamp) { + // if we have multiple items with the same timetamp, differntiate to avoid the remote thinking we repeat outselves + pool.write("/"); + pool.write(std::to_string(m_milliIndex++)); + } + else { + m_lastTimestamp = timeMillis; + m_milliIndex = 1; + } auto locator = pool.commit(); pool.reserve(payload.body().size() + locator.size() + 20); diff --git a/NetworkLogClient.h b/NetworkLogClient.h index 97673dc..c1e1614 100644 --- a/NetworkLogClient.h +++ b/NetworkLogClient.h @@ -41,6 +41,8 @@ private: bool m_enabled = false; NetworkConnection m_con; std::string m_logPath; + int64_t m_lastTimestamp = 0; + int m_milliIndex = 1; }; #endif -- 2.54.0 From 59e0130ed0c6b47c8e46f3492b1d7cb1369b90a5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Nov 2022 22:00:52 +0100 Subject: [PATCH 0108/1428] Avoid timing issues. First connect, then start the action. --- PriceHistoryDataProvider.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PriceHistoryDataProvider.cpp b/PriceHistoryDataProvider.cpp index d0a4ed0..3bfa144 100644 --- a/PriceHistoryDataProvider.cpp +++ b/PriceHistoryDataProvider.cpp @@ -271,9 +271,8 @@ bool PriceHistoryDataProvider::allowLogCompression() const void PriceHistoryDataProvider::initialPopulate() { if (m_currencies.empty()) { - logFatal() << "populate!"; + logCritical() << "populate!"; InitialHistoryFetcher *f = new InitialHistoryFetcher(this); - f->fetch(m_basedir, m_currency); connect (f, &InitialHistoryFetcher::success, f, [=](const QString ¤cy) { // load this file into a currency object. Currency *data = currencyData(currency, FetchOrCreate); @@ -285,6 +284,7 @@ void PriceHistoryDataProvider::initialPopulate() data->valueBlob = pool.commit(fileSize); } }); + f->fetch(m_basedir, m_currency); } } @@ -304,7 +304,7 @@ InitialHistoryFetcher::InitialHistoryFetcher(QObject *parent) void InitialHistoryFetcher::fetch(const QString &path, const QString ¤cy) { assert(!path.isEmpty()); - QNetworkRequest req(QUrl("http://flowee.org/products/pay/fiat/" + currency)); + QNetworkRequest req(QUrl("https://flowee.org/products/pay/fiat/" + currency)); auto app = QCoreApplication::instance(); QString useragent = QString("%1%2/%3") .arg(app->organizationName(), -- 2.54.0 From f4d92b0d3f5c0c4849d4089e1e7a2e3dbc06a59a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Nov 2022 23:00:05 +0100 Subject: [PATCH 0109/1428] Don't use false-positives to mark a wallet active A peer sending us transactions via our bloom filter first need to be checked to actually match our private keys before we should mark the wallet to have become active. --- Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Wallet.cpp b/Wallet.cpp index bcd70f1..e2cbf92 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -387,7 +387,6 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s { QMutexLocker locker(&m_lock); firstNewTransaction = m_nextWalletTransactionId; - setUserOwnedWallet(true); for (auto &tx: transactions) { const uint256 txid = tx.createHash(); WalletTransaction wtx; @@ -544,6 +543,7 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s } // mutex scope if (!transactionsToSave.empty()) { + setUserOwnedWallet(true); emit utxosChanged(); emit appendedTransactions(firstNewTransaction, transactionsToSave.size()); for (auto &tx : transactionsToSave) { // save the Tx to disk. -- 2.54.0 From 4d83152ae3a570b9e114264b11795f0b1aeeedc0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 26 Nov 2022 10:44:52 +0100 Subject: [PATCH 0110/1428] Remove version numbers in imports This idea has been removed in Qt6. --- guis/mobile/AccountHistory.qml | 6 +++--- guis/mobile/MainView.qml | 6 +++--- guis/mobile/MainViewBase.qml | 6 +++--- guis/mobile/NetView.qml | 6 +++--- guis/mobile/Page.qml | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index ce146e2..714e47b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 2.15 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index d3ffdcf..1c84cc2 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 2.15 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layout import "../Flowee" as Flowee MainViewBase { diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 6c59c83..4195a5e 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 2.15 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee QQC2.Control { diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index c8d5d5d..20a9fff 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.11 as QQC2 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee Page { diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index a0816fc..66ddecc 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 2.15 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee QQC2.Control { -- 2.54.0 From 50ca6c112fda699c7bc2b9f325bbc975b8699be3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 26 Nov 2022 10:46:57 +0100 Subject: [PATCH 0111/1428] Remove version numbers in imports This idea has been removed in Qt6. --- guis/desktop/AccountConfigMenu.qml | 4 ++-- guis/desktop/AccountDetails.qml | 6 +++--- guis/desktop/AccountListItem.qml | 6 +++--- guis/desktop/ConfigItem.qml | 4 ++-- guis/desktop/NetView.qml | 6 +++--- guis/desktop/NewAccountCreateBasicAccount.qml | 6 +++--- guis/desktop/NewAccountCreateHDAccount.qml | 6 +++--- guis/desktop/NewAccountImportAccount.qml | 8 ++++---- guis/desktop/NewAccountPane.qml | 6 +++--- guis/desktop/PaymentTweakingPanel.qml | 4 ++-- guis/desktop/ReceiveTransactionPane.qml | 10 +++++----- guis/desktop/SendTransactionPane.qml | 12 ++++++------ guis/desktop/SettingsPane.qml | 6 +++--- guis/desktop/WalletEncryption.qml | 6 +++--- guis/desktop/WalletEncryptionStatus.qml | 4 ++-- guis/desktop/WalletTransaction.qml | 4 ++-- guis/desktop/WalletTransactionDetails.qml | 6 +++--- guis/desktop/main.qml | 2 +- 18 files changed, 53 insertions(+), 53 deletions(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index b86ca41..eeaf08c 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls ConfigItem { id: root diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 1d9afce..e5f0658 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee Item { diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 7f765c1..a32f335 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee Item { diff --git a/guis/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml index fc344af..a74de5d 100644 --- a/guis/desktop/ConfigItem.qml +++ b/guis/desktop/ConfigItem.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls Item { id: root diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index 9b83899..cb2547b 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee ApplicationWindow { diff --git a/guis/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml index edfe9a8..759d269 100644 --- a/guis/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee ColumnLayout { diff --git a/guis/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml index e6aeb16..d85f228 100644 --- a/guis/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee ColumnLayout { diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 7dc7890..ca4b584 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -15,11 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee -import Flowee.org.pay 1.0 +import Flowee.org.pay GridLayout { id: importAccount diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index e2a887b..c4ca381 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -14,9 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee FocusScope { diff --git a/guis/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml index f86d54f..6133409 100644 --- a/guis/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls import "../Flowee" as Flowee Item { diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index c99229f..c6230c9 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -15,11 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 -import QtQuick.Shapes 1.11 // for shape-path -import Flowee.org.pay 1.0 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Shapes +import Flowee.org.pay import "../Flowee" as Flowee Pane { diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 0803835..7f7e65f 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -15,14 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 -import QtQuick.Window 2.11 -import QtQuick.Shapes 1.11 // for shape-path +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Shapes import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors -import Flowee.org.pay 1.0 +import Flowee.org.pay Item { id: sendPanel diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index de920f1..b3c43a4 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../ControlColors.js" as ControlColors import "../Flowee" as Flowee diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index d88d7ec..e648289 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors diff --git a/guis/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml index 1298230..8927521 100644 --- a/guis/desktop/WalletEncryptionStatus.qml +++ b/guis/desktop/WalletEncryptionStatus.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls Item { id: root diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index f4d61b9..6ad830c 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 +import QtQuick +import QtQuick.Controls import "../Flowee" as Flowee Item { diff --git a/guis/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml index 43d5332..9d5dc14 100644 --- a/guis/desktop/WalletTransactionDetails.qml +++ b/guis/desktop/WalletTransactionDetails.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import "../Flowee" as Flowee GridLayout { diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 28545c1..790a9a7 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Controls -import QtQuick.Layouts 1.11 +import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors -- 2.54.0 From 7724e61ea946e0cc42a9b9148331f47f804e2d00 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Nov 2022 11:15:07 +0100 Subject: [PATCH 0112/1428] Upgrade docker to qt641 --- android/build-pay.sh | 2 +- android/docker/Dockerfile | 4 +-- android/docker/build-docker.sh | 2 +- android/docker/scripts/buildQt.sh | 57 +++++++++++++++---------------- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 3e91630..c0b7e21 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -49,7 +49,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="flowee/buildenv-android:v6.4.0" + _docker_name_="flowee/buildenv-android:v6.4.1" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index d6bfd17..32f9233 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.4.0 +ARG QtVersion=v6.4.1 FROM archlinux:latest ARG QtVersion @@ -8,7 +8,7 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -# copy mirrorlist /etc/pacman.d/ +copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Suy --noconfirm --noprogressbar \ && pacman -Sy --noconfirm --noprogressbar --needed base-devel \ diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index cfdb452..46fc57d 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.4.0 +QtVersion=v6.4.1 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index b5ac539..40bad25 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -8,40 +8,28 @@ fi echo "Based on Qt version $TAG" >> /etc/versions source /etc/profile -for i in qtbase qtshadertools qtdeclarative qtsvg -do - cd /usr/local/cache - if ! test -d $i.git; then - git clone --bare https://code.qt.io/qt/$i.git +function checkout ( + (cd /usr/local/cache + if ! test -d $1.git; then + git clone --bare https://code.qt.io/qt/$1.git fi cd ~builduser - git clone -l /usr/local/cache/$i.git -b $TAG -done + git clone -l /usr/local/cache/$1.git -b $TAG) +) -### Native build -# QtBase -mkdir -p ~builduser/build-native/qtbase -(cd ~builduser/build-native/qtbase && \ +# The QtBase builds are different. +checkout qtbase +mkdir -p ~builduser/build/qtbase +(cd ~builduser/build/qtbase && \ ~builduser/qtbase/configure \ -prefix /usr/local \ -no-openssl \ -nomake examples \ -no-dbus && \ ninja install) - -# Others -for i in qtshadertools qtdeclarative qtsvg -do - cd ~builduser/build-native - mkdir -p $i - cd $i - /usr/local/bin/qt-configure-module ~builduser/$i - ninja install -done +rm -rf ~builduser/build/* ### Android build -# QtBase - mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ @@ -57,18 +45,27 @@ cd ~builduser/build/qtbase -DOPENSSL_USE_STATIC_LIBS=ON \ -DOPENSSL_ROOT_DIR=/opt/android-ssl ninja install +rm -rf ~builduser/build/* -# free up some 6GB -rm -rf ~builduser/qtbase/ ~builduser/build/qtbase/ ~builduser/build-native/qtbase/ -# Others -for i in qtshadertools qtdeclarative qtsvg +# All the others. +for i in qtshadertools qtdeclarative qtsvg qtmultimedia do - cd ~builduser/build - mkdir -p $i - cd $i + checkout $i + mkdir -p ~builduser/build/$i + cd ~builduser/build/$i + /usr/local/bin/qt-configure-module ~builduser/$i + ninja install + cd ~builduser + rm -rf build/* + + # Android + mkdir -p ~builduser/build/$i + cd ~builduser/build/$i /opt/android-qt6/bin/qt-configure-module ~builduser/$i ninja install + cd ~builduser + rm -rf build/* done cd /opt/android-qt6/ -- 2.54.0 From 48b47305b53c224549179c6aa9a322f74b71e95b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 29 Nov 2022 10:53:36 +0100 Subject: [PATCH 0113/1428] Downgrade the AURs for SDK. Qt hardcodes a gradle version that doesn't like the newest version of Android. --- android/docker/scripts/aurs.sh | 36 ++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index 281be6d..402daa7 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -1,20 +1,36 @@ #!/bin/bash # this runs as root. -for i in android-ndk android-sdk-platform-tools android-sdk-cmdline-tools-latest android-platform android-sdk-build-tools -do +function makeAur( cd ~builduser - git clone https://aur.archlinux.org/$i.git - chown builduser:builduser -R $i - cd "$i" - for f in /usr/local/cache/$i*zst; do - ln -s "$f" . + pkg="$1" + git clone https://aur.archlinux.org/$pkg.git + cd "$pkg" + commit=$2 + if test -n "$commit" + then + git checkout $commit + fi + chown builduser:builduser -R . .git + for i in /usr/local/cache/$pkg*zst; do + ln -s "$i" . done - for f in /usr/local/cache/*zip; do - ln -s "$f" . + for i in /usr/local/cache/*zip; do + if test -f $i; then + ln -s "$i" . + fi done su builduser -c makepkg find . -type f -name '*zst' -exec ln "{}" /usr/local/cache ';' -done +) + + +makeAur android-ndk +# For now we hardcode the 31.0.3 release of the sdk, seeing as Qt hardcodes gradle 7.4.2, which doesn't like a newer sdk. +makeAur android-sdk-platform-tools 89f6840df092ea2f1fc3d446c41be78cf9b66339 +makeAur android-sdk-build-tools 87aea36e5aef112d7af0c2ae5db154e96ab633c3 +makeAur android-sdk-cmdline-tools-latest +makeAur android-platform pacman -U --noconfirm /usr/local/cache/*zst + -- 2.54.0 From fda10032ff574271f10e64b8a27cdf64e68e2098 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 29 Nov 2022 10:54:45 +0100 Subject: [PATCH 0114/1428] Remove duplicate --- android/docker/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 32f9233..61c347e 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -39,7 +39,6 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ libb2 \ md4c \ jdk11-openjdk \ - git \ && pacman -Sc --noconfirm \ && rm -rf /var/cache/pacman/pkg/* \ && /usr/local/bin/createRootPwd -- 2.54.0 From 83451ac648b6c2ba3bab5d83aa6230a80b62ca2c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 29 Nov 2022 11:47:13 +0100 Subject: [PATCH 0115/1428] Remove unneeded import --- guis/mobile/MainView.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 1c84cc2..a257a73 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -17,7 +17,6 @@ */ import QtQuick import QtQuick.Controls as QQC2 -import QtQuick.Layout import "../Flowee" as Flowee MainViewBase { -- 2.54.0 From a733c4e0922ab3af9eeab4fd2084b86eb5a236dd Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 29 Nov 2022 17:33:50 +0100 Subject: [PATCH 0116/1428] Finetune how we respond to bad QML. As final deployments never had bad QML, this should be seen as a tool for developers and for debugging. So, according to the fail-fast concept we now abort() on detection of unparsable QML. Additionally we log the line, which means we can see the result on mobile should it have logging compiled in. --- main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 415b0fa..eb5f0a7 100644 --- a/main.cpp +++ b/main.cpp @@ -122,7 +122,10 @@ int main(int argc, char *argv[]) QQmlApplicationEngine engine; #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) // quit on error in the QMLs - QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &qapp, QCoreApplication::quit, Qt::QueuedConnection); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &qapp, [=]() { + logFatal() << "QML has errors, aborting on purpose now"; + abort(); + }, Qt::QueuedConnection); #endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); -- 2.54.0 From 143cf3fef87659e2a7ebcf23d7ac093777ae9e2f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 26 Nov 2022 18:01:11 +0100 Subject: [PATCH 0117/1428] Slowly moving to the extra tab --- guis/mobile.qrc | 1 + guis/mobile/MainView.qml | 10 +-- guis/mobile/SendTransactionsTab.qml | 124 ++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 guis/mobile/SendTransactionsTab.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 360e13f..6dea4ef 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -26,5 +26,6 @@ mobile/IconButton.qml mobile/TextButton.qml mobile/AccountSyncState.qml + mobile/SendTransactionsTab.qml diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index a257a73..9854c83 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -16,23 +16,19 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 import "../Flowee" as Flowee MainViewBase { AccountHistory { anchors.fill: parent } - Item { - id: sendScreen - property string icon: "qrc:/bla" + SendTransactionsTab { anchors.fill: parent - } Item { id: receiveScreen - property string icon: "qrc:/bla" + property string icon: Pay.useDarkSkin ? "qrc:/external-light.svg" : "qrc:/external.svg" + property string title: qsTr("Bla") anchors.fill: parent - } } diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml new file mode 100644 index 0000000..f1ed9eb --- /dev/null +++ b/guis/mobile/SendTransactionsTab.qml @@ -0,0 +1,124 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +// import QtQuick.Layouts +import "../Flowee" as Flowee +// import Flowee.org.pay; + +FocusScope { + id: root + property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string title: qsTr("Send") + + Component.onCompleted: setOpacities() + + ListView { + id: tabHeader + width: parent.width + height: 50 + clip: true + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + + model: stack.children.length + delegate: Rectangle { + width: Math.max(130, tabLabel.width) + height: tabLabel.height + 25 + color: { + if (index === tabHeader.currentIndex) + return Pay.useDarkSkin ? "darkgreen" : "green"; + return Pay.useDarkSkin ? "#565f68" : "#a5b5c6" + } + Flowee.Label { + id: tabLabel + anchors.centerIn: parent + text: stack.children[index].title + } + MouseArea { + anchors.fill: parent + onClicked: { + tabHeader.currentIndex = index + root.setOpacities() + } + } + } + } + + function setOpacities() { + for (let i = 0; i < stack.children.length; ++i) { + let on = i === tabHeader.currentIndex; + let child = stack.children[i]; + child.visible = on; + } + // try to make sure the current tab gets keyboard focus properly. + forceActiveFocus(); + let visibleChild = stack.children[tabHeader.currentIndex]; + visibleChild.focus = true + } + + Item { + id: stack + width: parent.width + anchors.top: tabHeader.bottom + anchors.bottom: parent.bottom + + FocusScope { + id: tab1 + property string title: qsTr("Rejected") + /* + Payment { // the model behind the Payment logic + id: payment + fiatPrice: Fiat.price + account: portfolio.current + } */ + Rectangle { + width: 10 + height: 10 + x: 10 + color: "red" + } + } + FocusScope { + property string title: qsTr("Scan QR") + Rectangle { + width: 10 + height: 10 + x: 20 + color: "blue" + } + QQC2.TextField { + focus: true + + } + } + FocusScope { + property string title: "Tab C" + Rectangle { + width: 10 + height: 10 + x: 30 + color: "yellow" + } + QQC2.TextField { + focus: true + y: 100 + } + } + } +} -- 2.54.0 From 59b0b300db539320399d8190c0b7e8c2c976f7ae Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Nov 2022 16:21:42 +0100 Subject: [PATCH 0118/1428] Use QtMultimedia --- CMakeLists.txt | 18 +++++++++++------- guis/mobile/SendTransactionsTab.qml | 27 +++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fac412b..7d8211a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) -find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED) +find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED + OPTIONAL_COMPONENTS Multimedia) find_package(flowee REQUIRED flowee_p2p) find_package(OpenSSL REQUIRED) if (ANDROID) @@ -145,7 +146,7 @@ option (BUILD_DESKTOP_PAY "Build the desktop (dev) client of Pay" ${NOT_ANDROID}) -if(BUILD_DESKTOP_PAY) +if (BUILD_DESKTOP_PAY) # The qm files are generated in the build tree, but the qrc file is inside the # source directory and the path to resources are relative to the location of # the qrc file itself. We copy the qrc file in the build @@ -172,7 +173,7 @@ if(BUILD_DESKTOP_PAY) endif() ###### Pay-mobile executable -option (BUILD_MOBILE_PAY "Build the mobile client of Pay" TRUE) +option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ${Qt6Multimedia_FOUND}) if (ANDROID AND BUILD_MOBILE_PAY) # blockheaders to be included in the APK @@ -236,7 +237,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set_target_properties(pay_mobile PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - target_link_libraries(pay_mobile pay_lib Qt6::Svg) + target_link_libraries(pay_mobile pay_lib Qt6::Svg Qt6::Multimedia) install(TARGETS pay_mobile DESTINATION bin) endif() @@ -283,6 +284,9 @@ message("") message("Configuration results:") message("----------------------") message("Target OS: ${CMAKE_SYSTEM_NAME}") +if (${local_qml}) + message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") +endif () if (NetworkLogClient) message ("-> Including network-logging capability") add_compile_definitions(NETWORK_LOGGER) @@ -295,6 +299,9 @@ if (${BUILD_DESKTOP_PAY}) message(" Missing QtDBus, skipping support for desktop notifications") endif () endif() +if (${Qt6Multimedia_NOTFOUND}) +message("ww Missing QtMultimedia libs, not building Pay for mobile ") +endif () if (${BUILD_MOBILE_PAY}) message ("-> Building Pay for mobile") if (ANDROID AND DEFINED MOBILE_PAY_I18N_QRC) @@ -304,9 +311,6 @@ endif() if (${BUILD_PAY_TOOLS}) message ("-> Building Pay tools") endif() -if (${local_qml}) - message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") -endif () message("") message("") diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index f1ed9eb..8b436ce 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -20,6 +20,7 @@ import QtQuick.Controls as QQC2 // import QtQuick.Layouts import "../Flowee" as Flowee // import Flowee.org.pay; +import QtMultimedia FocusScope { id: root @@ -95,6 +96,7 @@ FocusScope { } } FocusScope { + id: scanQrTab property string title: qsTr("Scan QR") Rectangle { width: 10 @@ -102,9 +104,30 @@ FocusScope { x: 20 color: "blue" } - QQC2.TextField { - focus: true + MediaDevices { + id: mediaDevices + } + CaptureSession { + camera: Camera { + id: camera + active: scanQrTab.focus + // focusMode: Camera.FocusModeAutoNear + // customFocusPoint: Qt.point(0.2, 0.2) // Focus relative to top-left corner + } + videoOutput: videoOutput + } + VideoOutput { + id: videoOutput + width: 320 + height: 240 + x: 20 + y: 30 + } + Flowee.Label { + text: camera.errorString + x: 10 + y: 350 } } FocusScope { -- 2.54.0 From 95d2b57cb307a74978b0165686212744afbf234c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 29 Nov 2022 23:44:32 +0100 Subject: [PATCH 0119/1428] Add basically functional camera support This makes it possible for the mobile app on Android to use the camera. We only ask for permission when the user actively goes to the 'QR-Scan' tab, and we show a simple preview of the camera feed. Notice that the permissions stuff is quite ugly right now due to Qt having that module in development and the public APIs are simply not available yet. But at least it works, which is all that matters. When the next minor Qt release comes out we can hopefully clean this up and use a version that needs no ifdefs. --- CMakeLists.txt | 9 ++++ Camera.cpp | 79 +++++++++++++++++++++++++++++ Camera.h | 55 ++++++++++++++++++++ android/AndroidManifest.xml | 1 + guis/mobile/SendTransactionsTab.qml | 50 ++++++++++-------- main.cpp | 7 ++- 6 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 Camera.cpp create mode 100644 Camera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8211a..79a63d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,10 @@ find_package(OpenSSL REQUIRED) if (ANDROID) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/android/cmake) + # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis + if (${Qt6Multimedia_FOUND}) + include_directories(${Qt6Core_PRIVATE_INCLUDE_DIRS}) + endif () else () find_package(Qt6 COMPONENTS DBus LinguistTools) endif() @@ -96,6 +100,11 @@ if (ANDROID) else () list(APPEND PAY_SOURCES main_utils.cpp) endif () + +if (${Qt6Multimedia_FOUND}) + list(APPEND PAY_SOURCES Camera.cpp) +endif() + add_library(pay_lib STATIC ${PAY_SOURCES}) target_link_libraries(pay_lib diff --git a/Camera.cpp b/Camera.cpp new file mode 100644 index 0000000..d2c90e1 --- /dev/null +++ b/Camera.cpp @@ -0,0 +1,79 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "Camera.h" + +#ifdef TARGET_OS_Android +#include +#endif + +#include + +Camera::Camera(QObject *parent) + : QObject(parent), + m_state(NotAsked) +{ +} + +bool Camera::authorized() const +{ + return m_state == Authorized; +} + +bool Camera::denied() const +{ + return m_state == Denied; +} + +void Camera::activate() +{ + if (m_state == NotAsked) { +#ifdef TARGET_OS_Android +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + // we expect that in Qt65 this API will be changed or removed as the QCoreApplication::requestPermission will then become available. + // for now, use the private APIs (since 6.5 is still 4 months from release) + + m_state = Asking; + logCritical() << "Starting to ask for permission for camera usage"; + auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); + future.then([=](QtAndroidPrivate::PermissionResult res) { + logCritical() << "Check permission returned: " << res; + if (res == QtAndroidPrivate::PermissionResult::Authorized) { + m_state = Authorized; + emit authorizationChanged(); + } + else { + logCritical() << "We are starting to request permissions"; + // then ask + auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); + future.then([=](QtAndroidPrivate::PermissionResult res) { + logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); + if (res == QtAndroidPrivate::Authorized) + m_state = Authorized; + else + m_state = Denied; + emit authorizationChanged(); + }); + } + }); +#endif // version check +#else + m_state = Authorized; + emit authorizationChanged(); +#endif + } +} diff --git a/Camera.h b/Camera.h new file mode 100644 index 0000000..087fe92 --- /dev/null +++ b/Camera.h @@ -0,0 +1,55 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CAMERA_H +#define CAMERA_H + +#include +#include + +class CheckState; + +class Camera : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool authorized READ authorized NOTIFY authorizationChanged) + Q_PROPERTY(bool denied READ denied NOTIFY authorizationChanged) +public: + explicit Camera(QObject *parent = nullptr); + + /// return true if the camera is authorized for usage. + bool authorized() const; + /// return true if the camera usage has been denied after being requested. + bool denied() const; + + Q_INVOKABLE void activate(); + +signals: + void authorizationChanged(); + +private: + // std::atomic m_checkState; + enum AskingState { + NotAsked, + Asking, + Denied, + Authorized + }; + AskingState m_state; +}; + +#endif diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index ed4d27a..babf84c 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -4,6 +4,7 @@ + . */ +#include "Camera.h" #include "BitcoinValue.h" #include "FloweePay.h" #include "NewWalletConfig.h" @@ -129,10 +130,12 @@ int main(int argc, char *argv[]) #endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); - engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); - engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); MenuModel menuModel; engine.rootContext()->setContextProperty("MenuModel", &menuModel); + Camera camera; + engine.rootContext()->setContextProperty("CameraHandler", &camera); + engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); + engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); handleLocalQml(engine); engine.load(engine.baseUrl().url() + #ifdef DESKTOP -- 2.54.0 From 31e4fc77f2d5aec3d75e8977166be5d629b41ce8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 11:16:19 +0100 Subject: [PATCH 0120/1428] Use QT_VERSION_MINOR Learned the trick to getting the version set and now printing it again also use it to only include the private stuff in the versions of Qt that we need to use the private headers. For doing dirty stuff, I just always want to have a sunsetting defined to avoid us silently depending on it forever. --- CMakeLists.txt | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79a63d7..7e37cea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,8 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) +# calling find_package Qt two times seems to be needed to get the Qt version :shrug: +find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core) find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED OPTIONAL_COMPONENTS Multimedia) find_package(flowee REQUIRED flowee_p2p) @@ -29,10 +31,6 @@ find_package(OpenSSL REQUIRED) if (ANDROID) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/android/cmake) - # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis - if (${Qt6Multimedia_FOUND}) - include_directories(${Qt6Core_PRIVATE_INCLUDE_DIRS}) - endif () else () find_package(Qt6 COMPONENTS DBus LinguistTools) endif() @@ -107,13 +105,22 @@ endif() add_library(pay_lib STATIC ${PAY_SOURCES}) +if (ANDROID) + # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis + if (${Qt6Multimedia_FOUND} AND ${QT_VERSION_MINOR} GREATER_EQUAL 2 AND ${QT_VERSION_MINOR} LESS_EQUAL 4) + set (PayLib_PRIVATE_LIBS Qt6::CorePrivate) + message(STATUS "including private QtCore APIs") + endif () +endif () + + target_link_libraries(pay_lib flowee_apputils flowee_utils flowee_p2p ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} - Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${QREncode_LIBRARIES}) + Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ${QREncode_LIBRARIES}) ###### Translations @@ -292,7 +299,8 @@ endif() message("") message("Configuration results:") message("----------------------") -message("Target OS: ${CMAKE_SYSTEM_NAME}") +message(STATUS "Target OS: ${CMAKE_SYSTEM_NAME}") +message(STATUS "Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}") if (${local_qml}) message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") endif () -- 2.54.0 From a612751e1ef3b913c830715ae0c371e1d09e7f97 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 14:19:43 +0100 Subject: [PATCH 0121/1428] Make it much easier to enable netlogging debug Now you can just drop a netlog.conf in your pay/android/ dir and this script will do the rest (assuming your build dir is fresh). --- android/build-pay.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index c0b7e21..70d45b4 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -62,6 +62,9 @@ cp -f "$_pay_native_name_"/*qm imports/ cp -f "$_pay_native_name_"/blockheaders-meta-extractor imports/ floweePaySrcDir=`dirname $0`/.. +if test -f $floweePaySrcDir/android/netlog.conf; then + netLogSwitch="-DNetworkLogClient=ON" +fi if test ! -f .config; then cat << HERE > .config @@ -77,7 +80,7 @@ if ! test -f build.ninja; then -DOPENSSL_CRYPTO_LIBRARY=/opt/android-ssl/lib/libcrypto.a \\ -DOPENSSL_SSL_LIBRARY=/opt/android-ssl/lib/libssl.a \\ -DOPENSSL_INCLUDE_DIR=/opt/android-ssl/include/ \\ - -DCMAKE_BUILD_TYPE=Release \\ + -DCMAKE_BUILD_TYPE=Release $netLogSwitch \\ -G Ninja \\ /home/builds/src fi @@ -123,6 +126,9 @@ fi if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockheaders.info; then imports/blockheaders-meta-extractor android-build/assets fi +if test -f $floweePaySrcDir/android/netlog.conf; then + cp $floweePaySrcDir/android/netlog.conf android-build/assets/ +fi if test "\$1" = "sign" -o "\$2" = "sign" then -- 2.54.0 From bcec1a1157e24703af9d59eb270ac6412caf875f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 16:49:08 +0100 Subject: [PATCH 0122/1428] Make copying headers more robust This copies more agressively and fixes the issue that QFile::copy() would not do anything if the targetfile already exist. Also add some log lines. --- main_utils_android.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/main_utils_android.cpp b/main_utils_android.cpp index 83410be..b630cc1 100644 --- a/main_utils_android.cpp +++ b/main_utils_android.cpp @@ -67,15 +67,24 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) abort(); } - bool install = !target.exists() || !infTarget.exists() || orig.size() != target.size(); + bool install = !target.exists() || !infTarget.exists() || orig.size() != target.size() + || infOrig.size() != infTarget.size(); if (install) { // make sure we have a local up-to-date copy - QFile::copy(orig.filePath(), target.absoluteFilePath()); - QFile::copy(orig.filePath() + ".info", infTarget.absoluteFilePath()); + QFile::remove(target.absoluteFilePath()); + bool ok = QFile::copy(orig.filePath(), target.absoluteFilePath()); + if (!ok) { + logFatal() << "Failed copying blockheaders"; + abort(); + } + QFile::remove(infTarget.absoluteFilePath()); + ok = QFile::copy(orig.filePath() + ".info", infTarget.absoluteFilePath()); + if (!ok) { + logFatal() << "Failed copying blockheaders.info"; + abort(); + } } - if (!target.exists()) - abort(); - if (!infTarget.exists()) + if (!target.exists() || !infTarget.exists()) // should be impossible... abort(); blockheaders.reset(new QFile(target.absoluteFilePath())); @@ -83,6 +92,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) blockheaders.reset(); return blockheaders; } + Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), infTarget.absoluteFilePath().toStdString()); blockheaders->close(); -- 2.54.0 From b0e33b381bd04fb3b0e4a92d954c8b831c01e692 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 16:51:58 +0100 Subject: [PATCH 0123/1428] Separate app singleton creation and p2p init As we need to prepare the blockheaders file before we load the p2p net, it is much cleaner to separate that from the construction of the singleton. This also allows us to move the creation of the network logger to an earlier point in time to also be able to log the startup sequence. --- FloweePay.cpp | 9 ++++++--- FloweePay.h | 3 +++ main.cpp | 20 +++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/FloweePay.cpp b/FloweePay.cpp index 03a2b1f..42a2baf 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -104,9 +104,6 @@ FloweePay::FloweePay() }); #endif - // start creation of downloadmanager and loading of data in a different thread - ioService().post(std::bind(&FloweePay::init, this)); - QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); m_unit = static_cast(defaultConfig.value(UNIT_TYPE, BCH).toInt()); m_windowHeight = defaultConfig.value(WINDOW_HEIGHT, -1).toInt(); @@ -377,6 +374,12 @@ QString FloweePay::platform() const return "unknown"; // TODO for other platforms check which names CMake uses. } +void FloweePay::startP2PInit() +{ + // start creation of downloadmanager and loading of data in a different thread + ioService().post(std::bind(&FloweePay::init, this)); +} + QString FloweePay::amountToStringPretty(double price) const { QString answer = FloweePay::amountToString(static_cast(price), m_unit); diff --git a/FloweePay.h b/FloweePay.h index 2381e38..7756897 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -102,6 +102,9 @@ public: /// returns platform name, Linux / Android / etc QString platform() const; + /// Load p2p layer. + void startP2PInit(); + /// for a price, in satoshis, return a formatted string in unitName(). Q_INVOKABLE inline QString amountToString(double price) const { return FloweePay::amountToString(static_cast(price), m_unit); diff --git a/main.cpp b/main.cpp index aacba61..d6ecfdd 100644 --- a/main.cpp +++ b/main.cpp @@ -96,8 +96,9 @@ int main(int argc, char *argv[]) logger->clearChannels(); logger->clearLogLevels(logVerbosity(cld)); logger->addConsoleChannel(); - - auto blockheaders = handleStaticChain(cld); +#ifdef NETWORK_LOGGER + logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); +#endif static const char* languagePacks[] = { #ifdef DESKTOP @@ -130,12 +131,14 @@ int main(int argc, char *argv[]) #endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); - MenuModel menuModel; - engine.rootContext()->setContextProperty("MenuModel", &menuModel); - Camera camera; - engine.rootContext()->setContextProperty("CameraHandler", &camera); engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); + MenuModel menuModel; + engine.rootContext()->setContextProperty("MenuModel", &menuModel); +#if MOBILE + Camera camera; + engine.rootContext()->setContextProperty("CameraHandler", &camera); +#endif handleLocalQml(engine); engine.load(engine.baseUrl().url() + #ifdef DESKTOP @@ -145,12 +148,11 @@ int main(int argc, char *argv[]) #endif "/main.qml"); + auto blockheaders = handleStaticChain(cld); QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld]() { loadCompleteHandler(engine, cld); }); -#ifdef NETWORK_LOGGER - logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); -#endif + FloweePay::instance()->startP2PInit(); // Clean shutdown on SIGTERM struct sigaction sa; -- 2.54.0 From 218fa034ce02f565ca155acb0f5374c5080b87e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 17:51:10 +0100 Subject: [PATCH 0124/1428] Move Camera.cpp to the apps compiles We no longer compile this one together with the static lib as that pulled in qtMultimedia for all users of the lib. --- CMakeLists.txt | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e37cea..972bba6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,21 +99,8 @@ else () list(APPEND PAY_SOURCES main_utils.cpp) endif () -if (${Qt6Multimedia_FOUND}) - list(APPEND PAY_SOURCES Camera.cpp) -endif() - add_library(pay_lib STATIC ${PAY_SOURCES}) -if (ANDROID) - # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis - if (${Qt6Multimedia_FOUND} AND ${QT_VERSION_MINOR} GREATER_EQUAL 2 AND ${QT_VERSION_MINOR} LESS_EQUAL 4) - set (PayLib_PRIVATE_LIBS Qt6::CorePrivate) - message(STATUS "including private QtCore APIs") - endif () -endif () - - target_link_libraries(pay_lib flowee_apputils flowee_utils @@ -211,6 +198,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) set (SOURCES_PAY_MOBILE main.cpp + Camera.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -224,7 +212,13 @@ if (ANDROID AND BUILD_MOBILE_PAY) endif() qt6_add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) - target_link_libraries(pay_mobile pay_lib Qt6::Svg) + + # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis + if (${Qt6Multimedia_FOUND} AND ${QT_VERSION_MINOR} GREATER_EQUAL 2 AND ${QT_VERSION_MINOR} LESS_EQUAL 4) + set (PAY_MOBILE_LIBS Qt6::CorePrivate) + message(STATUS "including private QtCore APIs for Android support") + endif () + target_link_libraries(pay_mobile pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" @@ -242,6 +236,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set (SOURCES_PAY_MOBILE main.cpp + Camera.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE -- 2.54.0 From 52e6f85cbba7d81e3289106ba6655cb646ee30b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Nov 2022 17:53:47 +0100 Subject: [PATCH 0125/1428] Move camera init to C++ --- Camera.cpp | 55 +++++++++++++++++++++++++++++ Camera.h | 8 +++++ guis/mobile/SendTransactionsTab.qml | 42 ++++++++++++++-------- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Camera.cpp b/Camera.cpp index d2c90e1..44a0d27 100644 --- a/Camera.cpp +++ b/Camera.cpp @@ -17,6 +17,8 @@ */ #include "Camera.h" +#include +#include #ifdef TARGET_OS_Android #include #endif @@ -27,6 +29,15 @@ Camera::Camera(QObject *parent) : QObject(parent), m_state(NotAsked) { + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationInactive) { + // when the user leaves the app screen, the permissions granted to us + // may have changed, so we need to re-ask. + m_state = NotAsked; + } + }); } bool Camera::authorized() const @@ -77,3 +88,47 @@ void Camera::activate() #endif } } + +void Camera::setQmlCamera(QObject *object) +{ + if (object == m_qmlCamera) + return; + m_qmlCamera = object; + emit qmlCameraChanged(); + fetchCameraDetails(); +} + +QObject *Camera::qmlCamera() const +{ + return m_qmlCamera; +} + +void Camera::fetchCameraDetails() +{ + QCamera *camera = qobject_cast(m_qmlCamera); + if (!camera) + return; + QCameraFormat preferred; + for (const auto &format : camera->cameraDevice().videoFormats()) { + if (preferred.isNull()) { + preferred = format; + } + else { + auto size = format.resolution(); + auto oldSize = preferred.resolution(); + // avoid going for the biggest feed, but not too small either. + if (oldSize.width() < 190 || (size.width() < oldSize.width() && size.width() >= 190)) { + preferred = format; + } + else if (size == oldSize && format.maxFrameRate() < preferred.maxFrameRate()) { + preferred = format; + } + } + } + logInfo().nospace() << "Changing camera resolution to " << preferred.resolution().width() << "x" << preferred.resolution().height(); + camera->setCameraFormat(preferred); + camera->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. + camera->setWhiteBalanceMode(QCamera::WhiteBalanceShade); // avoid flash + + camera->start(); +} diff --git a/Camera.h b/Camera.h index 087fe92..73ad5f9 100644 --- a/Camera.h +++ b/Camera.h @@ -28,6 +28,7 @@ class Camera : public QObject Q_OBJECT Q_PROPERTY(bool authorized READ authorized NOTIFY authorizationChanged) Q_PROPERTY(bool denied READ denied NOTIFY authorizationChanged) + Q_PROPERTY(QObject* qmlCamera READ qmlCamera WRITE setQmlCamera NOTIFY qmlCameraChanged) public: explicit Camera(QObject *parent = nullptr); @@ -38,10 +39,16 @@ public: Q_INVOKABLE void activate(); + void setQmlCamera(QObject *object); + QObject *qmlCamera() const; + signals: void authorizationChanged(); + void qmlCameraChanged(); private: + void fetchCameraDetails(); + // std::atomic m_checkState; enum AskingState { NotAsked, @@ -50,6 +57,7 @@ private: Authorized }; AskingState m_state; + QObject *m_qmlCamera = nullptr; }; #endif diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 680c5fa..093059d 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -81,6 +81,7 @@ FocusScope { FocusScope { id: tab1 + anchors.fill: parent property string title: qsTr("Rejected") /* Payment { // the model behind the Payment logic @@ -97,6 +98,7 @@ FocusScope { } FocusScope { id: scanQrTab + anchors.fill: parent property string title: qsTr("Scan QR") onFocusChanged: { console.log("focus received " + focus) @@ -106,42 +108,54 @@ FocusScope { // only load this after authorization has become available. Loader { id: cameraStufff - sourceComponent: CameraHandler.authorized ? cameraStuffComponent : undefined - width: 320 - height: 240 - x: 20 - y: 30 + sourceComponent: CameraHandler.authorized ? videoFeedPanel : undefined + anchors.fill: parent + anchors.margins: 6 } Component { - id: cameraStuffComponent + id: videoFeedPanel Item { anchors.fill: parent - /* - MediaDevices { - id: mediaDevices - }*/ + + Component.onCompleted: CameraHandler.qmlCamera = camera CaptureSession { id: captureSession videoOutput: videoOutput - camera: Camera { active: true } + camera: Camera { id: camera; } } VideoOutput { id: videoOutput - anchors.fill: parent + fillMode: VideoOutput.Stretch + width: parent.width + height: { + var feedSize = camera.cameraFormat.resolution; + // on all phones I tried, the width and height are swapped. (Qt641) + // This fixed it, but makes it look weird on Desktop or when Qt gets fixed. + return width * (feedSize.width / feedSize.height); + } } } - } Flowee.Label { - text: CameraHandler.denied ? qsTr("Camera permission denied. Change permissions to scan") : camera.errorString + wrapMode: Text.Wrap + text: { + if (CameraHandler.denied) + return qsTr("Camera permission denied. Change permissions to scan"); + var cam = CameraHandler.qmlCamera; + if (cam == null) + return "waiting for permission"; + return cam.errorString + } x: 10 y: 350 + width: parent.width } } FocusScope { property string title: "Tab C" + anchors.fill: parent Rectangle { width: 10 height: 10 -- 2.54.0 From a398d731dc2d0a621842427da86c2260ca80fd48 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Dec 2022 10:48:09 +0100 Subject: [PATCH 0126/1428] Hardcode QML theme in desktop app --- guis/desktop/main.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 790a9a7..05d6842 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -21,6 +21,8 @@ import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors +import QtQuick.Controls.Basic + ApplicationWindow { id: mainWindow visible: true -- 2.54.0 From 46d34df2a021e89bec80cf5549eab9786d28a2b4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Dec 2022 21:22:59 +0100 Subject: [PATCH 0127/1428] Actually scan for QRs. This takes the camera feed and pipes it through the ZXing (pronounced zebra-crossing) library which detects the barcodes in the image. When we find it, we pluck out the text. --- CMakeLists.txt | 31 ++++- Camera.cpp | 169 ++++++++++++++++++++++++++++ Camera.h | 20 +++- guis/mobile/SendTransactionsTab.qml | 6 +- 4 files changed, 218 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 972bba6..72d1dfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,8 +31,23 @@ find_package(OpenSSL REQUIRED) if (ANDROID) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/android/cmake) + + # Config ZXing here. + if (EXISTS "/opt/android-zxing/include/ZXing/DecodeHints.h") + add_library(ZXing::ZXing STATIC IMPORTED) + set_property(TARGET ZXing::ZXing PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "/opt/android-zxing/include") + set_target_properties(ZXing::ZXing PROPERTIES IMPORTED_LOCATION + "/opt/android-zxing/lib/libZXing.a") + set_property(TARGET ZXing::ZXing PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES + "/opt/android-zxing/lib/libZXing.a;/opt/android-zxing/lib/libzueci-static.a") + set (ZXing_FOUND TRUE) + else () + find_package(ZXing REQUIRED) + endif() else () find_package(Qt6 COMPONENTS DBus LinguistTools) + find_package(ZXing) endif() find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) find_package(QREncode REQUIRED) @@ -59,7 +74,6 @@ function(download_file url path) endif() endfunction() - set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp @@ -176,7 +190,11 @@ if (BUILD_DESKTOP_PAY) endif() ###### Pay-mobile executable -option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ${Qt6Multimedia_FOUND}) +option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ON) + +if (${Qt6Multimedia_NOTFOUND} OR ${ZXing_NOTFOUND}) + set (BUILD_MOBILE_PAY OFF) +endif () if (ANDROID AND BUILD_MOBILE_PAY) # blockheaders to be included in the APK @@ -218,7 +236,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) set (PAY_MOBILE_LIBS Qt6::CorePrivate) message(STATUS "including private QtCore APIs for Android support") endif () - target_link_libraries(pay_mobile pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) + target_link_libraries(pay_mobile pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" @@ -248,7 +266,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set_target_properties(pay_mobile PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - target_link_libraries(pay_mobile pay_lib Qt6::Svg Qt6::Multimedia) + target_link_libraries(pay_mobile pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia) install(TARGETS pay_mobile DESTINATION bin) endif() @@ -312,7 +330,10 @@ if (${BUILD_DESKTOP_PAY}) endif () endif() if (${Qt6Multimedia_NOTFOUND}) -message("ww Missing QtMultimedia libs, not building Pay for mobile ") + message("ww Missing QtMultimedia libs, not building Pay for mobile ") +endif () +if (${ZXing_NOTFOUND}) + message("ww Missing ZXing lib, not building Pay for mobile ") endif () if (${BUILD_MOBILE_PAY}) message ("-> Building Pay for mobile") diff --git a/Camera.cpp b/Camera.cpp index 44a0d27..80d5f72 100644 --- a/Camera.cpp +++ b/Camera.cpp @@ -1,6 +1,7 @@ /* * This file is part of the Flowee project * Copyright (C) 2022 Tom Zander + * Copyright (C) 2020 Axel Waggershauser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +18,11 @@ */ #include "Camera.h" +#include + +#include +#include +#include #include #include #ifdef TARGET_OS_Android @@ -25,10 +31,125 @@ #include +namespace { + +std::vector ReadBarcodes(const QImage &img, const ZXing::DecodeHints& hints) { + auto imageFormat = img.format(); + if (imageFormat == QImage::Format_Invalid) // likely a damaged frame in the feed + return {}; + auto zxImageFormat = ZXing::ImageFormat::None; + switch (imageFormat) { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + zxImageFormat = ZXing::ImageFormat::BGRX; +#else + zxImageFormat = ImageFormat::XRGB; +#endif + break; + case QImage::Format_RGB888: zxImageFormat = ZXing::ImageFormat::RGB; break; + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: zxImageFormat = ZXing::ImageFormat::RGBX; break; + case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break; + default: break; + } + + if (zxImageFormat == ZXing::ImageFormat::None) { + QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ; + return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast(gray.bytesPerLine())}, hints); + } + + ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast(img.bytesPerLine())); + return ZXing::ReadBarcodes(buf, hints); +} + +std::vector ReadBarcodes(const QVideoFrame &frame, const ZXing::DecodeHints &hints) { + auto img = frame; // shallow copy just get access to non-const map() function + if (!frame.isValid() || !img.map(QVideoFrame::ReadOnly)){ + logDebug() << "invalid QVideoFrame: could not map memory"; + return {}; + } + auto unmap = qScopeGuard([&] { img.unmap(); }); + + ZXing::ImageFormat fmt = ZXing::ImageFormat::None; + int pixStride = 0; + int pixOffset = 0; + +#define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6 + + switch (img.pixelFormat()) { + case FORMAT(ARGB32, ARGB8888): + case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied): + case FORMAT(RGB32, RGBX8888): + fmt = ZXing::ImageFormat::BGRX; + break; + + case FORMAT(BGRA32, BGRA8888): + case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied): + case FORMAT(BGR32, BGRX8888): +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::RGBX; +#else + fmt = ImageFormat::XBGR; +#endif + break; + + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: fmt = ZXing::ImageFormat::Lum, pixStride = 1; break; + + case FORMAT(AYUV444, AYUV): + case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied): +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3; +#else + fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; +#endif + break; + + case FORMAT(YUV420P, YUV420P): + case FORMAT(NV12, NV12): + case FORMAT(NV21, NV21): + case FORMAT(IMC1, IMC1): + case FORMAT(IMC2, IMC2): + case FORMAT(IMC3, IMC3): + case FORMAT(IMC4, IMC4): + case FORMAT(YV12, YV12): fmt = ZXing::ImageFormat::Lum; break; + case FORMAT(UYVY, UYVY): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + case FORMAT(YUYV, YUYV): fmt = ZXing::ImageFormat::Lum, pixStride = 2; break; + + case FORMAT(Y8, Y8): fmt = ZXing::ImageFormat::Lum; break; + case FORMAT(Y16, Y16): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + + case FORMAT(ABGR32, ABGR8888): +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::RGBX; +#else + fmt = ZXing::ImageFormat::XBGR; +#endif + break; + case FORMAT(YUV422P, YUV422P): fmt = ZXing::ImageFormat::Lum; break; + default: break; + } + + constexpr int FirstPlane = 0; + if (fmt != ZXing::ImageFormat::None) { + return ZXing::ReadBarcodes( + {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, hints); + } else { + return ReadBarcodes(img.toImage(), hints); + } +} + +} + + Camera::Camera(QObject *parent) : QObject(parent), m_state(NotAsked) { + m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); + m_decodeHints.setTryHarder(true); + auto guiApp = qobject_cast(QCoreApplication::instance()); assert(guiApp); connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { @@ -103,6 +224,54 @@ QObject *Camera::qmlCamera() const return m_qmlCamera; } +void Camera::setVideoSink(QObject *object) +{ + if (m_videoSink == object) + return; + auto old = qobject_cast(m_videoSink); + if (old) + disconnect(old, nullptr, this, nullptr); + m_videoSink = object; + emit videoSinkChanged(); + + auto sink = qobject_cast(m_videoSink); + if (sink) { + connect(sink, &QVideoSink::videoFrameChanged, this, [=](const QVideoFrame &image) { + auto res = ReadBarcodes(image, m_decodeHints); + if (res.empty()) + return; + const auto &bytes = res.at(0).bytes(); + setText(QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size())); + }); + } +} + +QObject *Camera::videoSink() const +{ + return m_videoSink; +} + +QString Camera::text() const +{ + return m_text; +} + +void Camera::setText(const QString &text) +{ + if (m_text == text) + return; + m_text = text; + emit textChanged(); + logFatal() << "Scanned: " << text; + + // TODO check barcode for usefulness (encoding etc). + + QCamera *camera = qobject_cast(m_qmlCamera); + if (!camera) + return; + camera->stop(); +} + void Camera::fetchCameraDetails() { QCamera *camera = qobject_cast(m_qmlCamera); diff --git a/Camera.h b/Camera.h index 73ad5f9..d91b949 100644 --- a/Camera.h +++ b/Camera.h @@ -19,8 +19,11 @@ #define CAMERA_H #include +#include #include +#include + class CheckState; class Camera : public QObject @@ -28,7 +31,9 @@ class Camera : public QObject Q_OBJECT Q_PROPERTY(bool authorized READ authorized NOTIFY authorizationChanged) Q_PROPERTY(bool denied READ denied NOTIFY authorizationChanged) + Q_PROPERTY(QString text READ text NOTIFY textChanged) Q_PROPERTY(QObject* qmlCamera READ qmlCamera WRITE setQmlCamera NOTIFY qmlCameraChanged) + Q_PROPERTY(QObject* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) public: explicit Camera(QObject *parent = nullptr); @@ -41,10 +46,18 @@ public: void setQmlCamera(QObject *object); QObject *qmlCamera() const; - + + void setVideoSink(QObject *object); + QObject *videoSink() const; + + QString text() const; + void setText(const QString &text); + signals: void authorizationChanged(); void qmlCameraChanged(); + void videoSinkChanged(); + void textChanged(); private: void fetchCameraDetails(); @@ -58,6 +71,11 @@ private: }; AskingState m_state; QObject *m_qmlCamera = nullptr; + QObject *m_videoSink = nullptr; + + QString m_text; + + ZXing::DecodeHints m_decodeHints; }; #endif diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 093059d..d8808fa 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -118,9 +118,11 @@ FocusScope { Item { anchors.fill: parent - Component.onCompleted: CameraHandler.qmlCamera = camera + Component.onCompleted: { + CameraHandler.qmlCamera = camera + CameraHandler.videoSink = videoOutput.videoSink + } CaptureSession { - id: captureSession videoOutput: videoOutput camera: Camera { id: camera; } } -- 2.54.0 From 2bf63d6579a5bced8dda361572ca128cf60ca73c Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Dec 2022 22:01:50 +0100 Subject: [PATCH 0128/1428] Add the ZXing library to the Android docker build To use this library we now also add it to the docker image where we cross-compile it for Android. This creates a static-build of the library for easy includsion. --- android/docker/Dockerfile | 3 +++ android/docker/scripts/buildZXing.sh | 31 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100755 android/docker/scripts/buildZXing.sh diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 61c347e..211d321 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -38,6 +38,7 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ brotli \ libb2 \ md4c \ + wget \ jdk11-openjdk \ && pacman -Sc --noconfirm \ && rm -rf /var/cache/pacman/pkg/* \ @@ -57,5 +58,7 @@ RUN mkdir -p /usr/local/cache \ && rm -rf ~builduser/* \ && /usr/local/bin/buildQrEncode.sh \ && rm -rf ~builduser/* \ + && /usr/local/bin/buildZXing.sh \ + && rm -rf ~builduser/* \ && rm -rf /usr/local/cache diff --git a/android/docker/scripts/buildZXing.sh b/android/docker/scripts/buildZXing.sh new file mode 100755 index 0000000..3a33738 --- /dev/null +++ b/android/docker/scripts/buildZXing.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +VERSION=1.4.0 + +echo "Based on zxing version $VERSION" >> /etc/versions +source /etc/profile + +cd /usr/local/cache +if ! test -f zxing-cpp-v${VERSION}.tar.gz; then + wget https://github.com/zxing-cpp/zxing-cpp/archive/refs/tags/v${VERSION}.tar.gz +fi +cd ~builduser +tar xf /usr/local/cache/zxing-cpp-v${VERSION}.tar.gz +cd zxing-cpp-${VERSION} +mkdir build +cd build +cmake -DCMAKE_TOOLCHAIN_FILE=/opt/android-qt6/lib/cmake/Qt6/qt.toolchain.cmake \ + -DCMAKE_INSTALL_PREFIX=/opt/android-zxing \ + -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DBUILD_BLACKBOX_TESTS=OFF \ + -DBUILD_UNIT_TESTS=OFF \ + -DBUILD_PYTHON_MODULE=OFF \ + -G Ninja \ + .. + +cd .. +ninja install + + + -- 2.54.0 From b346f21871ffd72e3dadf1314d068f594abc78c3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 Dec 2022 13:51:09 +0100 Subject: [PATCH 0129/1428] Bugfixes in camera code. Also upstreamed in ZXing-cpp (pr 451 + 453). Also, re-enable clear-whitespace. --- Camera.cpp | 146 ++++++++++++++++------------ Camera.h | 6 ++ guis/mobile/SendTransactionsTab.qml | 15 ++- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/Camera.cpp b/Camera.cpp index 80d5f72..5478443 100644 --- a/Camera.cpp +++ b/Camera.cpp @@ -18,7 +18,7 @@ */ #include "Camera.h" -#include +#include #include #include @@ -53,10 +53,10 @@ std::vector ReadBarcodes(const QImage &img, const ZXing::DecodeHi case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break; default: break; } - + if (zxImageFormat == ZXing::ImageFormat::None) { QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ; - return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast(gray.bytesPerLine())}, hints); + return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast(gray.bytesPerLine())}, hints); } ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast(img.bytesPerLine())); @@ -64,80 +64,80 @@ std::vector ReadBarcodes(const QImage &img, const ZXing::DecodeHi } std::vector ReadBarcodes(const QVideoFrame &frame, const ZXing::DecodeHints &hints) { - auto img = frame; // shallow copy just get access to non-const map() function - if (!frame.isValid() || !img.map(QVideoFrame::ReadOnly)){ - logDebug() << "invalid QVideoFrame: could not map memory"; - return {}; - } - auto unmap = qScopeGuard([&] { img.unmap(); }); - ZXing::ImageFormat fmt = ZXing::ImageFormat::None; - int pixStride = 0; - int pixOffset = 0; + int pixStride = 0; + int pixOffset = 0; #define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6 - switch (img.pixelFormat()) { - case FORMAT(ARGB32, ARGB8888): - case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied): - case FORMAT(RGB32, RGBX8888): - fmt = ZXing::ImageFormat::BGRX; - break; + switch (frame.pixelFormat()) { + case FORMAT(ARGB32, ARGB8888): + case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied): + case FORMAT(RGB32, RGBX8888): + fmt = ZXing::ImageFormat::BGRX; + break; - case FORMAT(BGRA32, BGRA8888): - case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied): - case FORMAT(BGR32, BGRX8888): + case FORMAT(BGRA32, BGRA8888): + case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied): + case FORMAT(BGR32, BGRX8888): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::RGBX; + fmt = ZXing::ImageFormat::RGBX; #else - fmt = ImageFormat::XBGR; + fmt = ImageFormat::XBGR; #endif - break; + break; - case QVideoFrameFormat::Format_P010: - case QVideoFrameFormat::Format_P016: fmt = ZXing::ImageFormat::Lum, pixStride = 1; break; + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: fmt = ZXing::ImageFormat::Lum, pixStride = 1; break; - case FORMAT(AYUV444, AYUV): - case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied): + case FORMAT(AYUV444, AYUV): + case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3; + fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3; #else - fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; + fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; #endif - break; + break; - case FORMAT(YUV420P, YUV420P): - case FORMAT(NV12, NV12): - case FORMAT(NV21, NV21): - case FORMAT(IMC1, IMC1): - case FORMAT(IMC2, IMC2): - case FORMAT(IMC3, IMC3): - case FORMAT(IMC4, IMC4): - case FORMAT(YV12, YV12): fmt = ZXing::ImageFormat::Lum; break; - case FORMAT(UYVY, UYVY): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; - case FORMAT(YUYV, YUYV): fmt = ZXing::ImageFormat::Lum, pixStride = 2; break; + case FORMAT(YUV420P, YUV420P): + case FORMAT(NV12, NV12): + case FORMAT(NV21, NV21): + case FORMAT(IMC1, IMC1): + case FORMAT(IMC2, IMC2): + case FORMAT(IMC3, IMC3): + case FORMAT(IMC4, IMC4): + case FORMAT(YV12, YV12): fmt = ZXing::ImageFormat::Lum; break; + case FORMAT(UYVY, UYVY): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + case FORMAT(YUYV, YUYV): fmt = ZXing::ImageFormat::Lum, pixStride = 2; break; - case FORMAT(Y8, Y8): fmt = ZXing::ImageFormat::Lum; break; - case FORMAT(Y16, Y16): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + case FORMAT(Y8, Y8): fmt = ZXing::ImageFormat::Lum; break; + case FORMAT(Y16, Y16): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; - case FORMAT(ABGR32, ABGR8888): + case FORMAT(ABGR32, ABGR8888): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::RGBX; + fmt = ZXing::ImageFormat::RGBX; #else - fmt = ZXing::ImageFormat::XBGR; + fmt = ZXing::ImageFormat::XBGR; #endif - break; - case FORMAT(YUV422P, YUV422P): fmt = ZXing::ImageFormat::Lum; break; - default: break; - } + break; + case FORMAT(YUV422P, YUV422P): fmt = ZXing::ImageFormat::Lum; break; + default: break; + } - constexpr int FirstPlane = 0; - if (fmt != ZXing::ImageFormat::None) { - return ZXing::ReadBarcodes( - {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, hints); - } else { - return ReadBarcodes(img.toImage(), hints); - } + if (fmt != ZXing::ImageFormat::None) { + auto img = frame; // shallow copy just get access to non-const map() function + if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){ + logDebug() << "invalid QVideoFrame: could not map memory"; + return {}; + } + QScopeGuard unmap([&] { img.unmap(); }); + + constexpr int FirstPlane = 0; + return ZXing::ReadBarcodes( + {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, hints); + } else { + return ReadBarcodes(frame.toImage(), hints); + } } } @@ -149,7 +149,7 @@ Camera::Camera(QObject *parent) { m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); m_decodeHints.setTryHarder(true); - + auto guiApp = qobject_cast(QCoreApplication::instance()); assert(guiApp); connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { @@ -187,6 +187,7 @@ void Camera::activate() if (res == QtAndroidPrivate::PermissionResult::Authorized) { m_state = Authorized; emit authorizationChanged(); + setShowCamera(true); } else { logCritical() << "We are starting to request permissions"; @@ -194,10 +195,12 @@ void Camera::activate() auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); future.then([=](QtAndroidPrivate::PermissionResult res) { logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); - if (res == QtAndroidPrivate::Authorized) + if (res == QtAndroidPrivate::Authorized) { m_state = Authorized; - else + setShowCamera(true); + } else { m_state = Denied; + } emit authorizationChanged(); }); } @@ -208,6 +211,8 @@ void Camera::activate() emit authorizationChanged(); #endif } + if (m_state == Authorized) + setShowCamera(true); } void Camera::setQmlCamera(QObject *object) @@ -263,13 +268,29 @@ void Camera::setText(const QString &text) m_text = text; emit textChanged(); logFatal() << "Scanned: " << text; - + // TODO check barcode for usefulness (encoding etc). - + QCamera *camera = qobject_cast(m_qmlCamera); if (!camera) return; camera->stop(); + setQmlCamera(nullptr); + setVideoSink(nullptr); + setShowCamera(false); +} + +bool Camera::showCamera() const +{ + return m_showCamera; +} + +void Camera::setShowCamera(bool on) +{ + if (m_showCamera == on) + return; + m_showCamera = on; + emit showCameraChanged(); } void Camera::fetchCameraDetails() @@ -278,6 +299,7 @@ void Camera::fetchCameraDetails() if (!camera) return; QCameraFormat preferred; +// TODO check the reader code above about which kind of video feeds it likes and prefer those in the selection for (const auto &format : camera->cameraDevice().videoFormats()) { if (preferred.isNull()) { preferred = format; diff --git a/Camera.h b/Camera.h index d91b949..af05352 100644 --- a/Camera.h +++ b/Camera.h @@ -29,6 +29,7 @@ class CheckState; class Camera : public QObject { Q_OBJECT + Q_PROPERTY(bool showCamera READ showCamera NOTIFY showCameraChanged) Q_PROPERTY(bool authorized READ authorized NOTIFY authorizationChanged) Q_PROPERTY(bool denied READ denied NOTIFY authorizationChanged) Q_PROPERTY(QString text READ text NOTIFY textChanged) @@ -53,11 +54,15 @@ public: QString text() const; void setText(const QString &text); + bool showCamera() const; + void setShowCamera(bool on); + signals: void authorizationChanged(); void qmlCameraChanged(); void videoSinkChanged(); void textChanged(); + void showCameraChanged(); private: void fetchCameraDetails(); @@ -74,6 +79,7 @@ private: QObject *m_videoSink = nullptr; QString m_text; + bool m_showCamera = false; // this is us telling QML to show the camera. ZXing::DecodeHints m_decodeHints; }; diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index d8808fa..7a89c03 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -100,22 +100,24 @@ FocusScope { id: scanQrTab anchors.fill: parent property string title: qsTr("Scan QR") + // on activate the CameraHandler will check if we have permissions and if so, turn on 'showCamera' onFocusChanged: { - console.log("focus received " + focus) - CameraHandler.activate(); + console.log("focus changed: " + focus) + if (focus) CameraHandler.activate(); } // only load this after authorization has become available. Loader { id: cameraStufff - sourceComponent: CameraHandler.authorized ? videoFeedPanel : undefined + sourceComponent: CameraHandler.showCamera ? videoFeedPanel : undefined anchors.fill: parent anchors.margins: 6 } Component { id: videoFeedPanel - Item { + Rectangle { + color: "red" anchors.fill: parent Component.onCompleted: { @@ -145,8 +147,11 @@ FocusScope { text: { if (CameraHandler.denied) return qsTr("Camera permission denied. Change permissions to scan"); + var txt = CameraHandler.text; + if (txt !== "") + return txt; var cam = CameraHandler.qmlCamera; - if (cam == null) + if (cam === null) return "waiting for permission"; return cam.errorString } -- 2.54.0 From 324255c0ea740ebb38229aa8b8f49acd945d3b91 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 3 Dec 2022 13:44:21 +0100 Subject: [PATCH 0130/1428] Refactor camera support After working on android and the qtmultimedia library for a week, I have a much better understanding of what works and what doesn't. The resulting design is thus presented. We will have one CameraController class which refers to one overlay panel in QML and in different places in the GUI objects based on QRScanner can be created to initiate a scan. This additionally moves the actual scanning out of the GUI thread since blocking that is a no-no. Now to move towards actually supporting scanning usecases. --- CMakeLists.txt | 6 +- Camera.cpp | 325 ------------------- Camera.h | 87 ------ CameraController.cpp | 462 ++++++++++++++++++++++++++++ CameraController.h | 87 ++++++ FloweePay.cpp | 10 + FloweePay.h | 6 + QRScanner.cpp | 31 ++ QRScanner.h | 37 +++ guis/mobile.qrc | 1 + guis/mobile/QRScannerOverlay.qml | 60 ++++ guis/mobile/SendTransactionsTab.qml | 69 +---- guis/mobile/main.qml | 6 + main.cpp | 20 +- main_utils_android.cpp | 14 +- 15 files changed, 735 insertions(+), 486 deletions(-) delete mode 100644 Camera.cpp delete mode 100644 Camera.h create mode 100644 CameraController.cpp create mode 100644 CameraController.h create mode 100644 QRScanner.cpp create mode 100644 QRScanner.h create mode 100644 guis/mobile/QRScannerOverlay.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 72d1dfd..7e7c37c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,7 +216,8 @@ if (ANDROID AND BUILD_MOBILE_PAY) configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) set (SOURCES_PAY_MOBILE main.cpp - Camera.cpp + CameraController.cpp + QRScanner.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -254,7 +255,8 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set (SOURCES_PAY_MOBILE main.cpp - Camera.cpp + CameraController.cpp + QRScanner.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE diff --git a/Camera.cpp b/Camera.cpp deleted file mode 100644 index 5478443..0000000 --- a/Camera.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander - * Copyright (C) 2020 Axel Waggershauser - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "Camera.h" - -#include - -#include -#include -#include -#include -#include -#ifdef TARGET_OS_Android -#include -#endif - -#include - -namespace { - -std::vector ReadBarcodes(const QImage &img, const ZXing::DecodeHints& hints) { - auto imageFormat = img.format(); - if (imageFormat == QImage::Format_Invalid) // likely a damaged frame in the feed - return {}; - auto zxImageFormat = ZXing::ImageFormat::None; - switch (imageFormat) { - case QImage::Format_ARGB32: - case QImage::Format_RGB32: -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - zxImageFormat = ZXing::ImageFormat::BGRX; -#else - zxImageFormat = ImageFormat::XRGB; -#endif - break; - case QImage::Format_RGB888: zxImageFormat = ZXing::ImageFormat::RGB; break; - case QImage::Format_RGBX8888: - case QImage::Format_RGBA8888: zxImageFormat = ZXing::ImageFormat::RGBX; break; - case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break; - default: break; - } - - if (zxImageFormat == ZXing::ImageFormat::None) { - QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ; - return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast(gray.bytesPerLine())}, hints); - } - - ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast(img.bytesPerLine())); - return ZXing::ReadBarcodes(buf, hints); -} - -std::vector ReadBarcodes(const QVideoFrame &frame, const ZXing::DecodeHints &hints) { - ZXing::ImageFormat fmt = ZXing::ImageFormat::None; - int pixStride = 0; - int pixOffset = 0; - -#define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6 - - switch (frame.pixelFormat()) { - case FORMAT(ARGB32, ARGB8888): - case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied): - case FORMAT(RGB32, RGBX8888): - fmt = ZXing::ImageFormat::BGRX; - break; - - case FORMAT(BGRA32, BGRA8888): - case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied): - case FORMAT(BGR32, BGRX8888): -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::RGBX; -#else - fmt = ImageFormat::XBGR; -#endif - break; - - case QVideoFrameFormat::Format_P010: - case QVideoFrameFormat::Format_P016: fmt = ZXing::ImageFormat::Lum, pixStride = 1; break; - - case FORMAT(AYUV444, AYUV): - case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied): -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3; -#else - fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; -#endif - break; - - case FORMAT(YUV420P, YUV420P): - case FORMAT(NV12, NV12): - case FORMAT(NV21, NV21): - case FORMAT(IMC1, IMC1): - case FORMAT(IMC2, IMC2): - case FORMAT(IMC3, IMC3): - case FORMAT(IMC4, IMC4): - case FORMAT(YV12, YV12): fmt = ZXing::ImageFormat::Lum; break; - case FORMAT(UYVY, UYVY): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; - case FORMAT(YUYV, YUYV): fmt = ZXing::ImageFormat::Lum, pixStride = 2; break; - - case FORMAT(Y8, Y8): fmt = ZXing::ImageFormat::Lum; break; - case FORMAT(Y16, Y16): fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; - - case FORMAT(ABGR32, ABGR8888): -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - fmt = ZXing::ImageFormat::RGBX; -#else - fmt = ZXing::ImageFormat::XBGR; -#endif - break; - case FORMAT(YUV422P, YUV422P): fmt = ZXing::ImageFormat::Lum; break; - default: break; - } - - if (fmt != ZXing::ImageFormat::None) { - auto img = frame; // shallow copy just get access to non-const map() function - if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){ - logDebug() << "invalid QVideoFrame: could not map memory"; - return {}; - } - QScopeGuard unmap([&] { img.unmap(); }); - - constexpr int FirstPlane = 0; - return ZXing::ReadBarcodes( - {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, hints); - } else { - return ReadBarcodes(frame.toImage(), hints); - } -} - -} - - -Camera::Camera(QObject *parent) - : QObject(parent), - m_state(NotAsked) -{ - m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); - m_decodeHints.setTryHarder(true); - - auto guiApp = qobject_cast(QCoreApplication::instance()); - assert(guiApp); - connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { - if (state == Qt::ApplicationInactive) { - // when the user leaves the app screen, the permissions granted to us - // may have changed, so we need to re-ask. - m_state = NotAsked; - } - }); -} - -bool Camera::authorized() const -{ - return m_state == Authorized; -} - -bool Camera::denied() const -{ - return m_state == Denied; -} - -void Camera::activate() -{ - if (m_state == NotAsked) { -#ifdef TARGET_OS_Android -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - // we expect that in Qt65 this API will be changed or removed as the QCoreApplication::requestPermission will then become available. - // for now, use the private APIs (since 6.5 is still 4 months from release) - - m_state = Asking; - logCritical() << "Starting to ask for permission for camera usage"; - auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); - future.then([=](QtAndroidPrivate::PermissionResult res) { - logCritical() << "Check permission returned: " << res; - if (res == QtAndroidPrivate::PermissionResult::Authorized) { - m_state = Authorized; - emit authorizationChanged(); - setShowCamera(true); - } - else { - logCritical() << "We are starting to request permissions"; - // then ask - auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); - future.then([=](QtAndroidPrivate::PermissionResult res) { - logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); - if (res == QtAndroidPrivate::Authorized) { - m_state = Authorized; - setShowCamera(true); - } else { - m_state = Denied; - } - emit authorizationChanged(); - }); - } - }); -#endif // version check -#else - m_state = Authorized; - emit authorizationChanged(); -#endif - } - if (m_state == Authorized) - setShowCamera(true); -} - -void Camera::setQmlCamera(QObject *object) -{ - if (object == m_qmlCamera) - return; - m_qmlCamera = object; - emit qmlCameraChanged(); - fetchCameraDetails(); -} - -QObject *Camera::qmlCamera() const -{ - return m_qmlCamera; -} - -void Camera::setVideoSink(QObject *object) -{ - if (m_videoSink == object) - return; - auto old = qobject_cast(m_videoSink); - if (old) - disconnect(old, nullptr, this, nullptr); - m_videoSink = object; - emit videoSinkChanged(); - - auto sink = qobject_cast(m_videoSink); - if (sink) { - connect(sink, &QVideoSink::videoFrameChanged, this, [=](const QVideoFrame &image) { - auto res = ReadBarcodes(image, m_decodeHints); - if (res.empty()) - return; - const auto &bytes = res.at(0).bytes(); - setText(QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size())); - }); - } -} - -QObject *Camera::videoSink() const -{ - return m_videoSink; -} - -QString Camera::text() const -{ - return m_text; -} - -void Camera::setText(const QString &text) -{ - if (m_text == text) - return; - m_text = text; - emit textChanged(); - logFatal() << "Scanned: " << text; - - // TODO check barcode for usefulness (encoding etc). - - QCamera *camera = qobject_cast(m_qmlCamera); - if (!camera) - return; - camera->stop(); - setQmlCamera(nullptr); - setVideoSink(nullptr); - setShowCamera(false); -} - -bool Camera::showCamera() const -{ - return m_showCamera; -} - -void Camera::setShowCamera(bool on) -{ - if (m_showCamera == on) - return; - m_showCamera = on; - emit showCameraChanged(); -} - -void Camera::fetchCameraDetails() -{ - QCamera *camera = qobject_cast(m_qmlCamera); - if (!camera) - return; - QCameraFormat preferred; -// TODO check the reader code above about which kind of video feeds it likes and prefer those in the selection - for (const auto &format : camera->cameraDevice().videoFormats()) { - if (preferred.isNull()) { - preferred = format; - } - else { - auto size = format.resolution(); - auto oldSize = preferred.resolution(); - // avoid going for the biggest feed, but not too small either. - if (oldSize.width() < 190 || (size.width() < oldSize.width() && size.width() >= 190)) { - preferred = format; - } - else if (size == oldSize && format.maxFrameRate() < preferred.maxFrameRate()) { - preferred = format; - } - } - } - logInfo().nospace() << "Changing camera resolution to " << preferred.resolution().width() << "x" << preferred.resolution().height(); - camera->setCameraFormat(preferred); - camera->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. - camera->setWhiteBalanceMode(QCamera::WhiteBalanceShade); // avoid flash - - camera->start(); -} diff --git a/Camera.h b/Camera.h deleted file mode 100644 index af05352..0000000 --- a/Camera.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef CAMERA_H -#define CAMERA_H - -#include -#include -#include - -#include - -class CheckState; - -class Camera : public QObject -{ - Q_OBJECT - Q_PROPERTY(bool showCamera READ showCamera NOTIFY showCameraChanged) - Q_PROPERTY(bool authorized READ authorized NOTIFY authorizationChanged) - Q_PROPERTY(bool denied READ denied NOTIFY authorizationChanged) - Q_PROPERTY(QString text READ text NOTIFY textChanged) - Q_PROPERTY(QObject* qmlCamera READ qmlCamera WRITE setQmlCamera NOTIFY qmlCameraChanged) - Q_PROPERTY(QObject* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) -public: - explicit Camera(QObject *parent = nullptr); - - /// return true if the camera is authorized for usage. - bool authorized() const; - /// return true if the camera usage has been denied after being requested. - bool denied() const; - - Q_INVOKABLE void activate(); - - void setQmlCamera(QObject *object); - QObject *qmlCamera() const; - - void setVideoSink(QObject *object); - QObject *videoSink() const; - - QString text() const; - void setText(const QString &text); - - bool showCamera() const; - void setShowCamera(bool on); - -signals: - void authorizationChanged(); - void qmlCameraChanged(); - void videoSinkChanged(); - void textChanged(); - void showCameraChanged(); - -private: - void fetchCameraDetails(); - - // std::atomic m_checkState; - enum AskingState { - NotAsked, - Asking, - Denied, - Authorized - }; - AskingState m_state; - QObject *m_qmlCamera = nullptr; - QObject *m_videoSink = nullptr; - - QString m_text; - bool m_showCamera = false; // this is us telling QML to show the camera. - - ZXing::DecodeHints m_decodeHints; -}; - -#endif diff --git a/CameraController.cpp b/CameraController.cpp new file mode 100644 index 0000000..5d2eef9 --- /dev/null +++ b/CameraController.cpp @@ -0,0 +1,462 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * Copyright (C) 2020 Axel Waggershauser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "CameraController.h" +#include "QRScanner.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef TARGET_OS_Android +#include +#endif + +#include + +enum AskingState { + NotAsked, + Asking, + Denied, + Authorized +}; + +#include +class QRScanningThread; + +class CameraControllerPrivate +{ +public: + explicit CameraControllerPrivate(CameraController *qq); + ~CameraControllerPrivate(); + // Configure the camera + void initCamera(); + // check if we need to load the camera. + void checkState(); + + AskingState state; + QObject *camera = nullptr; + QObject *videoSink = nullptr; + + QPointer scanRequest; + + mutable QMutex lock; + QVideoFrame currentFrame; + + bool cameraLoaded = false; + bool cameraStarted = false; + bool visible = false; + int streamWidth = -1; + int streamHeight = -1; + + QRScanningThread *m_scanningThread = nullptr; + + CameraController *q; +}; + +class QRScanningThread : public QThread +{ +public: + explicit QRScanningThread(CameraControllerPrivate *parent); + + QString text; + +protected: + void run(); +private: + std::vector readBarcodes(const QImage &img) const; + std::vector readBarcodes(const QVideoFrame &frame) const; + + CameraControllerPrivate *m_parent; + ZXing::DecodeHints m_decodeHints; +}; + + +// -------------------------------------------------------------------- + +CameraControllerPrivate::CameraControllerPrivate(CameraController *qq) + : state(NotAsked), + q(qq) +{ +#ifdef TARGET_OS_Android + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + QObject::connect(guiApp, &QGuiApplication::applicationStateChanged, qq, [=](Qt::ApplicationState appState) { + if (appState == Qt::ApplicationInactive) { + // when the user leaves the app screen, the permissions granted to us + // may have changed, so we need to re-ask. + state = NotAsked; + if (cameraStarted) { + // QtMultimedia doesn't like us not turning off the camera when we get small. + // so make sure we do. + cameraStarted = false; + emit q->cameraActiveChanged(); + + // TODO update state, probably want to cancel the request. + } + } + }); +#endif +} + +void CameraControllerPrivate::initCamera() +{ + QCamera *cam = qobject_cast(camera); + if (!cam) + return; + QCameraFormat preferred; + bool preferredIsCheap = false; + for (const auto &format : cam->cameraDevice().videoFormats()) { + bool formatIsCheap; // if true, we don't need to go via QImage (we avoid double conversion) + switch (format.pixelFormat()) { + case QVideoFrameFormat::Format_Invalid: + case QVideoFrameFormat::Format_XRGB8888: + case QVideoFrameFormat::Format_XBGR8888: + case QVideoFrameFormat::Format_RGBA8888: + case QVideoFrameFormat::Format_SamplerExternalOES: + case QVideoFrameFormat::Format_Jpeg: + case QVideoFrameFormat::Format_SamplerRect: + case QVideoFrameFormat::Format_YUV420P10: + formatIsCheap = false; + break; + default: + formatIsCheap = true; + break; + } + + logInfo() << " + " << format.resolution().width() << "x" << format.resolution().height() + << "::" << format.pixelFormat() << formatIsCheap << "framerate:" + << format.minFrameRate() << "-" << format.maxFrameRate(); + + if (preferred.isNull()) { + preferred = format; + preferredIsCheap = formatIsCheap; + logFatal() << "picked"; + } + else { + auto size = format.resolution(); + auto oldSize = preferred.resolution(); + if (preferredIsCheap && !formatIsCheap) + continue; + // avoid going for the biggest feed, but not too small either. + if (oldSize.width() < 210 || (size.width() < oldSize.width() && size.width() >= 210)) { + preferred = format; + logFatal() << "picked"; + } + else if (size == oldSize && format.maxFrameRate() < preferred.maxFrameRate()) { + preferred = format; + logFatal() << "picked"; + } + } + } + logCritical().nospace() << "Changing camera resolution to " << preferred.resolution().width() << "x" << preferred.resolution().height(); + cam->setCameraFormat(preferred); + cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. + cam->setWhiteBalanceMode(QCamera::WhiteBalanceShade); // avoid flash +} + +void CameraControllerPrivate::checkState() +{ + if (state != Authorized) + return; + if (!cameraLoaded) { + cameraLoaded = true; + emit q->loadCameraChanged(); + } + if (!visible) { + visible = true; + emit q->visibleChanged(); + } + if (camera && videoSink && !cameraStarted) { + logFatal() << "Camera active is now true"; + cameraStarted = true; + emit q->cameraActiveChanged(); + QCamera *cam = qobject_cast(camera); + if (cam) + logFatal() << "cam error:" << cam->errorString(); + + auto sink = qobject_cast(videoSink); + assert(sink); // likely bug in QML + QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { + currentFrame = frame; + + if (!m_scanningThread) { + m_scanningThread = new QRScanningThread(this); + QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); + m_scanningThread->start(); + } + }); + } +} + +// -------------------------------------------------------------------- + +QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) + : m_parent(parent) +{ + m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); + m_decodeHints.setTryHarder(true); +} + +void QRScanningThread::run() +{ + while (true) { + m_parent->lock.lock(); + bool exit = !m_parent->cameraStarted; + QVideoFrame frame = m_parent->currentFrame; + m_parent->lock.unlock(); + if (exit) + return; + + auto res = readBarcodes(frame); + if (!res.empty()) { + // we have a result! + const auto &bytes = res.at(0).bytes(); + logFatal() << "result: " << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + return; // makes the 'finished()' signal get emitted. + } + } +} + +std::vector QRScanningThread::readBarcodes(const QImage &img) const +{ + auto imageFormat = img.format(); + if (imageFormat == QImage::Format_Invalid) // likely a damaged frame in the feed + return {}; + auto zxImageFormat = ZXing::ImageFormat::None; + switch (imageFormat) { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + zxImageFormat = ZXing::ImageFormat::BGRX; +#else + zxImageFormat = ImageFormat::XRGB; +#endif + break; + case QImage::Format_RGB888: zxImageFormat = ZXing::ImageFormat::RGB; break; + case QImage::Format_RGBX8888: + case QImage::Format_RGBA8888: zxImageFormat = ZXing::ImageFormat::RGBX; break; + case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break; + default: break; + } + + if (zxImageFormat == ZXing::ImageFormat::None) { + QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ; + return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast(gray.bytesPerLine())}, m_decodeHints); + } + + ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast(img.bytesPerLine())); + return ZXing::ReadBarcodes(buf, m_decodeHints); +} + +std::vector QRScanningThread::readBarcodes(const QVideoFrame &frame) const +{ + ZXing::ImageFormat fmt = ZXing::ImageFormat::None; + int pixStride = 0; + int pixOffset = 0; + + // note that the comments behind the values are the Qt5 formats. + switch (frame.pixelFormat()) { + case QVideoFrameFormat::Format_ARGB8888: // ARGB32 + case QVideoFrameFormat::Format_ARGB8888_Premultiplied: // ARGB32_Premultiplied + case QVideoFrameFormat::Format_RGBX8888: // RGB32 + fmt = ZXing::ImageFormat::BGRX; + break; + case QVideoFrameFormat::Format_BGRA8888: // BGRA32 + case QVideoFrameFormat::Format_BGRA8888_Premultiplied: // BGRA32_Premultiplied + case QVideoFrameFormat::Format_BGRX8888: // BGR32 +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::RGBX; +#else + fmt = ImageFormat::XBGR; +#endif + break; + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + fmt = ZXing::ImageFormat::Lum, pixStride = 1; break; + case QVideoFrameFormat::Format_AYUV: // AYUV444 + case QVideoFrameFormat::Format_AYUV_Premultiplied: // AYUV444_Premultiplied +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3; +#else + fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; +#endif + break; + case QVideoFrameFormat::Format_YUV420P: // YUV420P + case QVideoFrameFormat::Format_NV12: // NV12 + case QVideoFrameFormat::Format_NV21: // NV21 + case QVideoFrameFormat::Format_IMC1: // IMC1 + case QVideoFrameFormat::Format_IMC2: // IMC2 + case QVideoFrameFormat::Format_IMC3: // IMC3 + case QVideoFrameFormat::Format_IMC4: // IMC4 + case QVideoFrameFormat::Format_YV12: // YV12 + case QVideoFrameFormat::Format_Y8: // Y8 + case QVideoFrameFormat::Format_YUV422P: // YUV422P + fmt = ZXing::ImageFormat::Lum; break; + case QVideoFrameFormat::Format_UYVY: // UYVY + fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + case QVideoFrameFormat::Format_YUYV: // YUYV + fmt = ZXing::ImageFormat::Lum, pixStride = 2; break; + case QVideoFrameFormat::Format_Y16: // Y16 + fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; + + case QVideoFrameFormat::Format_ABGR8888: // ABGR32 +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + fmt = ZXing::ImageFormat::RGBX; +#else + fmt = ZXing::ImageFormat::XBGR; +#endif + break; + default: break; + } + + if (fmt != ZXing::ImageFormat::None) { + auto img = frame; // shallow copy just get access to non-const map() function + if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){ + logDebug() << "invalid QVideoFrame: could not map memory"; + return {}; + } + QScopeGuard unmap([&] { img.unmap(); }); + + constexpr int FirstPlane = 0; + return ZXing::ReadBarcodes( + {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, m_decodeHints); + } else { + return readBarcodes(frame.toImage()); + } +} + + +// -------------------------------------------------------------------- + +CameraController::CameraController(QObject *parent) + : QObject(parent), + d(new CameraControllerPrivate(this)) +{ +} + +void CameraController::startRequest(QRScanner *request) +{ + assert(request); + d->scanRequest = request; + if (d->state == NotAsked) { +#ifdef TARGET_OS_Android +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + // we expect that in Qt65 this API will be changed or removed as the QCoreApplication::requestPermission will then become available. + // for now, use the private APIs (since 6.5 is still 4 months from release) + + d->state = Asking; + logCritical() << "Starting to ask for permission for camera usage"; + auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); + future.then([=](QtAndroidPrivate::PermissionResult res) { + logCritical() << "Check permission returned: " << res; + if (res == QtAndroidPrivate::PermissionResult::Authorized) { + d->state = Authorized; + d->checkState(); + } + else { + logCritical() << "We are starting to request permissions"; + // then ask + auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); + future.then([=](QtAndroidPrivate::PermissionResult res) { + logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); + if (res == QtAndroidPrivate::Authorized) { + d->state = Authorized; + d->checkState(); + } else { + d->state = Denied; + } + }); + } + }); +#endif // version check +#else + d->state = Authorized; +#endif + } + d->checkState(); +} + +void CameraController::setCamera(QObject *object) +{ + if (object == d->camera) + return; + d->camera = object; + emit cameraChanged(); + d->initCamera(); +} + +QObject *CameraController::camera() const +{ + return d->camera; +} + +void CameraController::setVideoSink(QObject *object) +{ + if (d->videoSink == object) + return; + auto old = qobject_cast(d->videoSink); + if (old) + QObject::disconnect(old, nullptr, this, nullptr); + d->videoSink = object; + emit videoSinkChanged(); +} + +QObject *CameraController::videoSink() const +{ + return d->videoSink; +} + +bool CameraController::loadCamera() const +{ + return d->cameraLoaded; +} + +bool CameraController::cameraActive() const +{ + return d->cameraStarted; +} + +bool CameraController::visible() const +{ + return d->visible; +} + +void CameraController::qrScanFinished() +{ + assert(d->m_scanningThread); + logFatal() << " -> " << d->m_scanningThread->text; + + delete d->m_scanningThread; + d->m_scanningThread = nullptr; + + d->cameraStarted = false; + emit cameraActiveChanged(); + d->visible = false; + emit visibleChanged(); + d->scanRequest = nullptr; + QObject::disconnect(d->videoSink, nullptr, this, nullptr); +} diff --git a/CameraController.h b/CameraController.h new file mode 100644 index 0000000..46e4650 --- /dev/null +++ b/CameraController.h @@ -0,0 +1,87 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include +#include +#include + +class CameraControllerPrivate; +class QRScanner; + +/** + * This class works together with the QRScannerOverlay QML file. + * + * a. user action causes a QRScanner QML object to be populated and activated, + which is backed by a cpp class of the same name. + * b. we get a 'startRequest' call, we set visible to true. + * c. we check for authorization, if granted we set loadCamera to true. + * d. the QML populates our qmlCamera and videoSink properties + * e. we check the camera setup, setting the best resolution and stuff. + * f. we 'start' the camera by setting the cameraActive bool to true, which the QML uses. + * g. when we finish scanning the QR we turn the camera off in the same manner. + * we also set visible to false. + */ +class CameraController : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) + Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) + Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) + Q_PROPERTY(QObject* camera READ camera WRITE setCamera NOTIFY cameraChanged) + Q_PROPERTY(QObject* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) +public: + explicit CameraController(QObject *parent = nullptr); + + /// return true if the camera is authorized for usage. + // bool authorized() const; + /// return true if the camera usage has been denied after being requested. + // bool denied() const; + + void startRequest(QRScanner *request); + // void activate(); + + void setCamera(QObject *object); + QObject *camera() const; + + void setVideoSink(QObject *object); + QObject *videoSink() const; + + bool loadCamera() const; + bool cameraActive() const; + bool visible() const; + +signals: + // void authorizationChanged(); + void cameraChanged(); + void videoSinkChanged(); + void textChanged(); + // void showCameraChanged(); + void loadCameraChanged(); + void cameraActiveChanged(); + void visibleChanged(); + +private slots: + void qrScanFinished(); + +private: + CameraControllerPrivate * d; +}; + +#endif diff --git a/FloweePay.cpp b/FloweePay.cpp index 42a2baf..d1efeb0 100644 --- a/FloweePay.cpp +++ b/FloweePay.cpp @@ -736,6 +736,16 @@ void FloweePay::setNewBlockMuted(bool mute) m_notifications.setNewBlockMuted(mute); } +CameraController *FloweePay::cameraController() +{ + return m_cameraController; +} + +void FloweePay::setCameraController(CameraController *cc) +{ + m_cameraController = cc; +} + QString FloweePay::version() const { return QCoreApplication::instance()->applicationVersion(); diff --git a/FloweePay.h b/FloweePay.h index 7756897..adf1b40 100644 --- a/FloweePay.h +++ b/FloweePay.h @@ -42,6 +42,7 @@ class NewWalletConfig; class KeyId; class PriceDataProvider; class PriceHistoryDataProvider; +class CameraController; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); @@ -249,6 +250,10 @@ public: /// If true, no notifications about new blocks will be shown void setNewBlockMuted(bool mute); + // return the CameraController previously set on this app singleton + CameraController *cameraController(); + // set the cameraController for the singleton to take ownership of. + void setCameraController(CameraController *cc); int fontScaling() const; void setFontScaling(int newFontScaling); @@ -292,6 +297,7 @@ private: std::unique_ptr m_prices; std::unique_ptr m_priceHistory; NotificationManager m_notifications; + CameraController* m_cameraController; QList m_wallets; int m_dspTimeout = 5000; int m_windowWidth = 500; diff --git a/QRScanner.cpp b/QRScanner.cpp new file mode 100644 index 0000000..1b6c069 --- /dev/null +++ b/QRScanner.cpp @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "QRScanner.h" +#include "FloweePay.h" +#include "CameraController.h" + +QRScanner::QRScanner(QObject *parent) + : QObject(parent) +{ +} + +void QRScanner::start() +{ + FloweePay::instance()->cameraController()->startRequest(this); +} diff --git a/QRScanner.h b/QRScanner.h new file mode 100644 index 0000000..45057df --- /dev/null +++ b/QRScanner.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QRSCANNER_H +#define QRSCANNER_H + +#include +#include +#include + +class QRScanner : public QObject +{ + Q_OBJECT + // Q_PROPERTY(bool showCamera READ showCamera NOTIFY showCameraChanged) +public: + explicit QRScanner(QObject *parent = nullptr); + + Q_INVOKABLE void start(); + +private: +}; + +#endif diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6dea4ef..c685132 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -27,5 +27,6 @@ mobile/TextButton.qml mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml + mobile/QRScannerOverlay.qml diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml new file mode 100644 index 0000000..6cfd468 --- /dev/null +++ b/guis/mobile/QRScannerOverlay.qml @@ -0,0 +1,60 @@ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import QtMultimedia +import "../Flowee" as Flowee + +Item { + id: root + + // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the + // feature is actually requisted by the user. + Loader { + sourceComponent: videoFeedPanel + active: CameraController.loadCamera + anchors.fill: parent + } + + Component { + id: videoFeedPanel + Rectangle { + color: "red" + anchors.left: parent.left + anchors.right: parent.right + height: 300 + + Component.onCompleted: { + CameraController.camera = camera + CameraController.videoSink = videoOutput.videoSink + } + Connections { + target: CameraController + function onCameraActiveChanged() { + console.log("CameraActive now: " + CameraController.cameraActive) + if (CameraController.cameraActive) { + camera.start(); + } else { + // camera.stop(); + } + } + } + + Camera { + id: camera + // active: CameraController.cameraActive + } + CaptureSession { + camera: camera + videoOutput: videoOutput + } + VideoOutput { + id: videoOutput + visible: CameraController.cameraActive + fillMode: VideoOutput.Stretch + // TODO use width/height from CameraController + width: parent.width + height: 400 + } + } + } +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 7a89c03..c2ec53d 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -19,8 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 // import QtQuick.Layouts import "../Flowee" as Flowee -// import Flowee.org.pay; -import QtMultimedia +import Flowee.org.pay; FocusScope { id: root @@ -101,64 +100,18 @@ FocusScope { anchors.fill: parent property string title: qsTr("Scan QR") // on activate the CameraHandler will check if we have permissions and if so, turn on 'showCamera' - onFocusChanged: { - console.log("focus changed: " + focus) - if (focus) CameraHandler.activate(); + onFocusChanged: if (focus) scanner.start(); + + QRScanner { + id: scanner } - // only load this after authorization has become available. - Loader { - id: cameraStufff - sourceComponent: CameraHandler.showCamera ? videoFeedPanel : undefined - anchors.fill: parent - anchors.margins: 6 - } - - Component { - id: videoFeedPanel - Rectangle { - color: "red" - anchors.fill: parent - - Component.onCompleted: { - CameraHandler.qmlCamera = camera - CameraHandler.videoSink = videoOutput.videoSink - } - CaptureSession { - videoOutput: videoOutput - camera: Camera { id: camera; } - } - VideoOutput { - id: videoOutput - fillMode: VideoOutput.Stretch - width: parent.width - height: { - var feedSize = camera.cameraFormat.resolution; - // on all phones I tried, the width and height are swapped. (Qt641) - // This fixed it, but makes it look weird on Desktop or when Qt gets fixed. - return width * (feedSize.width / feedSize.height); - } - } - } - } - - Flowee.Label { - wrapMode: Text.Wrap - text: { - if (CameraHandler.denied) - return qsTr("Camera permission denied. Change permissions to scan"); - var txt = CameraHandler.text; - if (txt !== "") - return txt; - var cam = CameraHandler.qmlCamera; - if (cam === null) - return "waiting for permission"; - return cam.errorString - } - x: 10 - y: 350 - width: parent.width - } + /* + Also consider to fetch the actual resolution from the actual cpp stream and report that to QML for better scaling / preview. + And try to move the actual parsing of the QR to a different thread. + A simple member of the latest frame can exist in the gui thread, protected by a mutex. + Then the other thread will take the latest one, scan for the QR and finish. Meaning it will basically skip all the frames without blocking the GUI + } */ } FocusScope { property string title: "Tab C" diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index d37dc10..fe827bf 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -54,9 +54,15 @@ ApplicationWindow { anchors.fill: parent initialItem: "./Loading.qml"; onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); + enabled: !menuOverlay.open } MenuOverlay { id: menuOverlay anchors.fill: parent } + + QRScannerOverlay { + anchors.fill: parent + visible: CameraController.visible + } } diff --git a/main.cpp b/main.cpp index d6ecfdd..c847d81 100644 --- a/main.cpp +++ b/main.cpp @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "Camera.h" #include "BitcoinValue.h" #include "FloweePay.h" #include "NewWalletConfig.h" @@ -28,6 +27,10 @@ #ifdef NETWORK_LOGGER # include "NetworkLogClient.h" #endif +#ifdef MOBILE +#include "CameraController.h" +#include "QRScanner.h" +#endif #include // for ECC_Start() @@ -131,14 +134,19 @@ int main(int argc, char *argv[]) #endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); - engine.rootContext()->setContextProperty("Pay", FloweePay::instance()); - engine.rootContext()->setContextProperty("Fiat", FloweePay::instance()->prices()); + auto app = FloweePay::instance(); + engine.rootContext()->setContextProperty("Pay", app); + engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel; engine.rootContext()->setContextProperty("MenuModel", &menuModel); -#if MOBILE - Camera camera; - engine.rootContext()->setContextProperty("CameraHandler", &camera); + +#ifdef MOBILE + qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); + CameraController *cc = new CameraController(app); + app->setCameraController(cc); + engine.rootContext()->setContextProperty("CameraController", cc); #endif + handleLocalQml(engine); engine.load(engine.baseUrl().url() + #ifdef DESKTOP diff --git a/main_utils_android.cpp b/main_utils_android.cpp index b630cc1..f166eb0 100644 --- a/main_utils_android.cpp +++ b/main_utils_android.cpp @@ -15,17 +15,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "qqmlcontext.h" -#include -#include +#include "FloweePay.h" +#include "PortfolioDataProvider.h" +#include "NetDataProvider.h" #include #include -#include -#include #include -#include -#include +#include + +#include struct CommandLineParserData { @@ -102,7 +101,6 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) { FloweePay *app = FloweePay::instance(); - NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); app->p2pNet()->addP2PNetListener(netData); netData->startRefreshTimer(); -- 2.54.0 From 09b5f887f7ee540b30b5f7e0bdbd41fbbf2b2672 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 3 Dec 2022 16:30:08 +0100 Subject: [PATCH 0131/1428] Add option to have faster deployments. (default off) This allows a developer to avoid including the headers file in the APK so it becomes a lot smaller and deployment is faster. This only works on reinstalls, it won't be able to run on a phone where Flowee Pay has never been installed. --- CMakeLists.txt | 7 +++++-- android/build-pay.sh | 4 ++-- main_utils_android.cpp | 12 ++++-------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e7c37c..e92ebd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED find_package(flowee REQUIRED flowee_p2p) find_package(OpenSSL REQUIRED) if (ANDROID) + option(quick_deploy "Do not include the blockchain in the APK, for a smaller (developer ONLY) deployment" OFF) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/android/cmake) @@ -199,8 +200,10 @@ endif () if (ANDROID AND BUILD_MOBILE_PAY) # blockheaders to be included in the APK set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) - download_file(https://flowee.org/products/pay/blockheaders - ${ASSETS_DIR}/blockheaders) + if (NOT quick_deploy) + download_file(https://flowee.org/products/pay/blockheaders + ${ASSETS_DIR}/blockheaders) + endif() file(COPY ${CMAKE_SOURCE_DIR}/data/bip39-english ${CMAKE_SOURCE_DIR}/data/bip39-chinese_simplified ${CMAKE_SOURCE_DIR}/data/bip39-chinese_traditional diff --git a/android/build-pay.sh b/android/build-pay.sh index 70d45b4..a0a7438 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -63,7 +63,7 @@ cp -f "$_pay_native_name_"/blockheaders-meta-extractor imports/ floweePaySrcDir=`dirname $0`/.. if test -f $floweePaySrcDir/android/netlog.conf; then - netLogSwitch="-DNetworkLogClient=ON" + developerSwitches="-DNetworkLogClient=ON -Dquick_deploy=ON" fi if test ! -f .config; then @@ -80,7 +80,7 @@ if ! test -f build.ninja; then -DOPENSSL_CRYPTO_LIBRARY=/opt/android-ssl/lib/libcrypto.a \\ -DOPENSSL_SSL_LIBRARY=/opt/android-ssl/lib/libssl.a \\ -DOPENSSL_INCLUDE_DIR=/opt/android-ssl/include/ \\ - -DCMAKE_BUILD_TYPE=Release $netLogSwitch \\ + -DCMAKE_BUILD_TYPE=Release $developerSwitches \\ -G Ninja \\ /home/builds/src fi diff --git a/main_utils_android.cpp b/main_utils_android.cpp index f166eb0..b9df643 100644 --- a/main_utils_android.cpp +++ b/main_utils_android.cpp @@ -61,13 +61,9 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) QFileInfo orig("assets:/blockheaders"); QFileInfo infOrig("assets:/blockheaders.info"); - if (!orig.exists() || !infOrig.exists()) { - logFatal() << "MIssing blockheader assets"; - abort(); - } - - bool install = !target.exists() || !infTarget.exists() || orig.size() != target.size() - || infOrig.size() != infTarget.size(); + bool install = orig.exists() && infOrig.exists() + && (!target.exists() || !infTarget.exists() || orig.size() != target.size() + || infOrig.size() != infTarget.size()); if (install) { // make sure we have a local up-to-date copy QFile::remove(target.absoluteFilePath()); @@ -83,7 +79,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) abort(); } } - if (!target.exists() || !infTarget.exists()) // should be impossible... + if (!target.exists() || !infTarget.exists()) // We didn't find a source for the headers abort(); blockheaders.reset(new QFile(target.absoluteFilePath())); -- 2.54.0 From 7272f62f6409ae27bfd991bcaff27300f857f88d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 3 Dec 2022 22:37:17 +0100 Subject: [PATCH 0132/1428] Make libs linking private --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e92ebd7..04ce3aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,7 @@ if (BUILD_DESKTOP_PAY) ) add_executable(pay ${SOURCES_PAY}) set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") - target_link_libraries(pay pay_lib Qt6::Svg) + target_link_libraries(pay PRIVATE pay_lib Qt6::Svg) install(TARGETS pay DESTINATION bin) endif() @@ -240,7 +240,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) set (PAY_MOBILE_LIBS Qt6::CorePrivate) message(STATUS "including private QtCore APIs for Android support") endif () - target_link_libraries(pay_mobile pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) + target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" @@ -271,7 +271,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set_target_properties(pay_mobile PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - target_link_libraries(pay_mobile pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia) + target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia) install(TARGETS pay_mobile DESTINATION bin) endif() -- 2.54.0 From 48be8b6df64ead9a1d0b215cc7261deb439f4949 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Dec 2022 14:21:21 +0100 Subject: [PATCH 0133/1428] Better detect not installed packages --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04ce3aa..308e5fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -193,7 +193,7 @@ endif() ###### Pay-mobile executable option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ON) -if (${Qt6Multimedia_NOTFOUND} OR ${ZXing_NOTFOUND}) +if (NOT "${Qt6Multimedia_FOUND}" OR NOT "${ZXing_FOUND}") set (BUILD_MOBILE_PAY OFF) endif () @@ -334,10 +334,10 @@ if (${BUILD_DESKTOP_PAY}) message(" Missing QtDBus, skipping support for desktop notifications") endif () endif() -if (${Qt6Multimedia_NOTFOUND}) +if (NOT ${Qt6Multimedia_FOUND}) message("ww Missing QtMultimedia libs, not building Pay for mobile ") endif () -if (${ZXing_NOTFOUND}) +if (NOT ${ZXing_FOUND}) message("ww Missing ZXing lib, not building Pay for mobile ") endif () if (${BUILD_MOBILE_PAY}) -- 2.54.0 From 15ae94ad550697cdb114344048ccde024c51620b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Dec 2022 14:43:44 +0100 Subject: [PATCH 0134/1428] Add missing quote Seems a copy/paste issue. --- guis/desktop/NewAccountImportAccount.qml | 2 +- guis/mobile/NewAccount.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index ca4b584..3165c4a 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -158,7 +158,7 @@ GridLayout { } Flowee.TextField { id: derivationPath - text: "m/44'/0'/0" // What most BCH wallets are created with + text: "m/44'/0'/0'" // What most BCH wallets are created with visible: !importAccount.isPrivateKey color: Pay.checkDerivation(text) ? palette.text : "red" } diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 2c83629..55ba19c 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -346,7 +346,7 @@ Page { id: derivationPath Layout.columnSpan: 2 Layout.fillWidth: true - text: "m/44'/0'/0" // What most BCH wallets are created with + text: "m/44'/0'/0'" // What most BCH wallets are created with visible: !importAccount.isPrivateKey color: Pay.checkDerivation(text) ? palette.text : "red" } -- 2.54.0 From 7ca5838b417be2594ae66ca76a7ddf22725691f8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 18:32:30 +0100 Subject: [PATCH 0135/1428] Move bip21 parsing and fish out comment as well. --- Payment.cpp | 35 +++++++++++++++++++++++++++++++++-- PaymentDetailOutput.cpp | 21 --------------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Payment.cpp b/Payment.cpp index 1b73857..542ca1f 100644 --- a/Payment.cpp +++ b/Payment.cpp @@ -26,6 +26,7 @@ #include #include #include +#include // #define DEBUG_TX_CREATION #ifdef DEBUG_TX_CREATION @@ -80,9 +81,39 @@ double Payment::paymentAmount() return static_cast(sats); } -void Payment::setTargetAddress(const QString &address) +void Payment::setTargetAddress(const QString &address_) { - soleOut()->setAddress(address); + QString address = address_.trimmed(); + + auto out = soleOut(); + /* + * Users may paste an address that is really a payment url. + * This basically means we may have a price added after a questionmark. + * bitcoincash:qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45?amount=2.1 + */ + int urlStart = address.indexOf('?'); + if (urlStart > 0) { + QUrl url(address); + auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); + for (const auto &item : query.queryItems()) { + if (item.first == "amount") { + bool ok; + auto amount = item.second.toLongLong(&ok); + if (ok) { + out->setPaymentAmount(amount * 1E8); + emit amountChanged(); + } + } + else if (item.first == "label") { + setUserComment(item.second); + } + } + + address = address.left(urlStart); + } + + out->setAddress(address); + emit targetAddressChanged(); } QString Payment::targetAddress() diff --git a/PaymentDetailOutput.cpp b/PaymentDetailOutput.cpp index cea7494..fdaaacd 100644 --- a/PaymentDetailOutput.cpp +++ b/PaymentDetailOutput.cpp @@ -95,28 +95,7 @@ void PaymentDetailOutput::setAddress(const QString &address_) const QString address = address_.trimmed(); if (m_address == address) return; - - /* - * Users may paste an address that is really a payment url. - * This basically means we may have a price added after a questionmark. - * bitcoincash:qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45?amount=2.1 - */ m_address = address; - int urlStart = address.indexOf('?'); - if (urlStart > 0) { - m_address = address.left(urlStart); - QUrl url(address); - auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); - for (const auto &item : query.queryItems()) { - if (item.first == "amount") { - bool ok; - auto amount = item.second.toLongLong(&ok); - if (ok) - setPaymentAmount(amount * 1E8); - } - } - } - const std::string &chainPrefixCopy = chainPrefix(); std::string encodedAddress; -- 2.54.0 From f00fb77a4a8e41ce6e9a65f57b11b9321d722c6f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 18:32:47 +0100 Subject: [PATCH 0136/1428] Move property to be next to the other properties. --- PaymentDetailOutput_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PaymentDetailOutput_p.h b/PaymentDetailOutput_p.h index 6d8cfce..4548e7d 100644 --- a/PaymentDetailOutput_p.h +++ b/PaymentDetailOutput_p.h @@ -31,6 +31,7 @@ class PaymentDetailOutput : public PaymentDetail Q_PROPERTY(bool maxAllowed READ maxAllowed WRITE setMaxAllowed NOTIFY maxAllowedChanged) Q_PROPERTY(bool fiatFollows READ fiatFollows WRITE setFiatFollows NOTIFY fiatFollowsChanged) Q_PROPERTY(bool maxSelected READ maxSelected WRITE setMaxSelected NOTIFY maxSelectedChanged) + Q_PROPERTY(bool forceLegacyOk READ forceLegacyOk WRITE setForceLegacyOk NOTIFY forceLegacyOkChanged) public: explicit PaymentDetailOutput(Payment *parent); @@ -89,7 +90,6 @@ private: bool m_forceLegacyOk = false; QString m_address; QString m_formattedTarget; - Q_PROPERTY(bool forceLegacyOk READ forceLegacyOk WRITE setForceLegacyOk NOTIFY forceLegacyOkChanged) }; inline PaymentDetailOutput* PaymentDetail::toOutput() { -- 2.54.0 From 55694bf28c6888794ad047bf62c2aeaad94be462 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 18:34:53 +0100 Subject: [PATCH 0137/1428] Make scanner part feature complete. We now make the scanner ignore barcodes that do not fit the expected content. We now detect 'seedphrase' and 'address' and bip21 type payment urls. --- CameraController.cpp | 100 +++++++++++++++++++----- CameraController.h | 9 +-- QRScanner.cpp | 48 +++++++++++- QRScanner.h | 29 ++++++- guis/mobile.qrc | 1 + guis/mobile/PayWithQR.qml | 61 +++++++++++++++ guis/mobile/SendTransactionsTab.qml | 115 ++++------------------------ 7 files changed, 232 insertions(+), 131 deletions(-) create mode 100644 guis/mobile/PayWithQR.qml diff --git a/CameraController.cpp b/CameraController.cpp index 5d2eef9..8a85fe8 100644 --- a/CameraController.cpp +++ b/CameraController.cpp @@ -90,6 +90,7 @@ private: CameraControllerPrivate *m_parent; ZXing::DecodeHints m_decodeHints; + QRScanner::ScanType m_scanType; }; @@ -109,11 +110,11 @@ CameraControllerPrivate::CameraControllerPrivate(CameraController *qq) state = NotAsked; if (cameraStarted) { // QtMultimedia doesn't like us not turning off the camera when we get small. - // so make sure we do. + // so make sure we do, we also cancel the scan request. + lock.lock(); cameraStarted = false; + lock.unlock(); emit q->cameraActiveChanged(); - - // TODO update state, probably want to cancel the request. } } }); @@ -152,7 +153,7 @@ void CameraControllerPrivate::initCamera() if (preferred.isNull()) { preferred = format; preferredIsCheap = formatIsCheap; - logFatal() << "picked"; + logInfo() << "picked"; } else { auto size = format.resolution(); @@ -162,11 +163,11 @@ void CameraControllerPrivate::initCamera() // avoid going for the biggest feed, but not too small either. if (oldSize.width() < 210 || (size.width() < oldSize.width() && size.width() >= 210)) { preferred = format; - logFatal() << "picked"; + logInfo() << "picked"; } else if (size == oldSize && format.maxFrameRate() < preferred.maxFrameRate()) { preferred = format; - logFatal() << "picked"; + logInfo() << "picked"; } } } @@ -188,16 +189,16 @@ void CameraControllerPrivate::checkState() visible = true; emit q->visibleChanged(); } - if (camera && videoSink && !cameraStarted) { + if (camera && videoSink && !cameraStarted && scanRequest.get()) { logFatal() << "Camera active is now true"; cameraStarted = true; emit q->cameraActiveChanged(); QCamera *cam = qobject_cast(camera); - if (cam) - logFatal() << "cam error:" << cam->errorString(); + if (cam && cam->error() != QCamera::NoError) + logFatal() << "CameraController found cam error:" << cam->errorString(); auto sink = qobject_cast(videoSink); - assert(sink); // likely bug in QML + assert(sink); // likely bug in your QML QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { currentFrame = frame; @@ -213,7 +214,8 @@ void CameraControllerPrivate::checkState() // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) - : m_parent(parent) + : m_parent(parent), + m_scanType(parent->scanRequest->scanType()) { m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); m_decodeHints.setTryHarder(true); @@ -229,15 +231,59 @@ void QRScanningThread::run() if (exit) return; - auto res = readBarcodes(frame); - if (!res.empty()) { - // we have a result! - const auto &bytes = res.at(0).bytes(); - logFatal() << "result: " << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + auto results = readBarcodes(frame); + for (const auto &result : results) { + const auto &bytes = result.bytes(); + logFatal() << "result:" << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); - text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); - return; // makes the 'finished()' signal get emitted. + switch (m_scanType) { + case QRScanner::Seed: { + // We can't be certain something is a seed here, we can just check the basics + // only normal characters and spaces + QString possibleSeed = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + int wordCount = 0; + bool seenSpace = false; + bool failedChecks = false; + for (auto i = 0; i < possibleSeed.size(); ++i) { + auto c = possibleSeed.at(i); + if (c.isDigit() || c.isSymbol()) + failedChecks = true; + else if (c.isSpace()) { + if (seenSpace || i == 0) + failedChecks = true; // double space or leading space + seenSpace = true; + ++wordCount; + } + else if (c.isLetter()) { + seenSpace = false; + } + if (failedChecks) + break; + } + if (!failedChecks && (wordCount == 12 || wordCount == 24)) { + text = possibleSeed; + return; // makes the 'QThread::finished()' signal get emitted. + } + break; + } + case QRScanner::PaymentDetails: + // starts with bitcoincash: (which is 12 chars, including that colon) + if (bytes.size() > 12 + 40 && memcmp("bitcoincash:", bytes.data(), 12) == 0) { + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + return; + } + break; + case QRScanner::PaymentDetailsTestnet: + assert(false); // TODO + break; + default: + logFatal() << "Unknown scanType provided"; + return; + } } + + // No matches. We go for another round. + // we assume at least 33ms has passed (at 30 frames that is the frame-rate) and we'll be parsing the next frame on loop } } @@ -400,6 +446,17 @@ void CameraController::startRequest(QRScanner *request) d->checkState(); } +void CameraController::abortRequest(QRScanner *request) +{ + if (d->scanRequest == request) { + // The scanning thread will abort and nicely shutdown on change of this variable + d->lock.lock(); + d->cameraStarted = false; + d->lock.unlock(); + emit cameraActiveChanged(); + } +} + void CameraController::setCamera(QObject *object) { if (object == d->camera) @@ -449,6 +506,7 @@ void CameraController::qrScanFinished() { assert(d->m_scanningThread); logFatal() << " -> " << d->m_scanningThread->text; + auto resultText = d->m_scanningThread->text; delete d->m_scanningThread; d->m_scanningThread = nullptr; @@ -457,6 +515,10 @@ void CameraController::qrScanFinished() emit cameraActiveChanged(); d->visible = false; emit visibleChanged(); - d->scanRequest = nullptr; QObject::disconnect(d->videoSink, nullptr, this, nullptr); + + if (d->scanRequest) { + d->scanRequest->finishedScan(resultText); + d->scanRequest = nullptr; + } } diff --git a/CameraController.h b/CameraController.h index 46e4650..1eacdab 100644 --- a/CameraController.h +++ b/CameraController.h @@ -49,13 +49,8 @@ class CameraController : public QObject public: explicit CameraController(QObject *parent = nullptr); - /// return true if the camera is authorized for usage. - // bool authorized() const; - /// return true if the camera usage has been denied after being requested. - // bool denied() const; - void startRequest(QRScanner *request); - // void activate(); + void abortRequest(QRScanner *request); void setCamera(QObject *object); QObject *camera() const; @@ -68,11 +63,9 @@ public: bool visible() const; signals: - // void authorizationChanged(); void cameraChanged(); void videoSinkChanged(); void textChanged(); - // void showCameraChanged(); void loadCameraChanged(); void cameraActiveChanged(); void visibleChanged(); diff --git a/QRScanner.cpp b/QRScanner.cpp index 1b6c069..67aaf3d 100644 --- a/QRScanner.cpp +++ b/QRScanner.cpp @@ -21,11 +21,57 @@ #include "CameraController.h" QRScanner::QRScanner(QObject *parent) - : QObject(parent) + : QObject(parent), + m_scanType(static_cast(100)) { } void QRScanner::start() { + resetScanResult(); + if (m_scanType == static_cast(100)) + throw std::runtime_error("Required property scanType not set"); FloweePay::instance()->cameraController()->startRequest(this); } + +void QRScanner::abort() +{ + FloweePay::instance()->cameraController()->abortRequest(this); +} + +QRScanner::ScanType QRScanner::scanType() const +{ + return m_scanType; +} + +void QRScanner::setScanType(ScanType type) +{ + if (m_scanType == type) + return; + m_scanType = type; + emit scanTypeChanged(); +} + +void QRScanner::finishedScan(const QString &resultString) +{ + setScanResult(resultString); + emit finished(); +} + +QString QRScanner::scanResult() const +{ + return m_scanResult; +} + +void QRScanner::setScanResult(const QString &newScanResult) +{ + if (m_scanResult == newScanResult) + return; + m_scanResult = newScanResult; + emit scanResultChanged(); +} + +void QRScanner::resetScanResult() +{ + setScanResult({}); +} diff --git a/QRScanner.h b/QRScanner.h index 45057df..1eaa98b 100644 --- a/QRScanner.h +++ b/QRScanner.h @@ -25,13 +25,40 @@ class QRScanner : public QObject { Q_OBJECT - // Q_PROPERTY(bool showCamera READ showCamera NOTIFY showCameraChanged) + Q_PROPERTY(ScanType scanType READ scanType WRITE setScanType NOTIFY scanTypeChanged REQUIRED) + Q_PROPERTY(QString scanResult READ scanResult WRITE setScanResult RESET resetScanResult NOTIFY scanResultChanged) public: explicit QRScanner(QObject *parent = nullptr); Q_INVOKABLE void start(); + Q_INVOKABLE void abort(); + + enum ScanType { + Seed, + PaymentDetails, + PaymentDetailsTestnet + // others like private key or cashId + }; + Q_ENUM(ScanType) + + ScanType scanType() const; + void setScanType(ScanType type); + + /// Notice that resultString may be empty if we didn't scan any valid QR + void finishedScan(const QString &resultString); + + QString scanResult() const; + void setScanResult(const QString &newScanResult); + void resetScanResult(); + +signals: + void finished(); + void scanTypeChanged(); + void scanResultChanged(); private: + ScanType m_scanType; + QString m_scanResult; }; #endif diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c685132..89e6bea 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -28,5 +28,6 @@ mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml mobile/QRScannerOverlay.qml + mobile/PayWithQR.qml diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml new file mode 100644 index 0000000..f094e63 --- /dev/null +++ b/guis/mobile/PayWithQR.qml @@ -0,0 +1,61 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +Page { + headerText: qsTr("Scan QR") + + columns: 2 + + Item { + id: data + Layout.columnSpan: 2 + + QRScanner { + id: scanner + scanType: QRScanner.PaymentDetails + Component.onCompleted: scanner.start(); + onFinished: payment.targetAddress = scanResult + } + Payment { + id: payment + } + } + + Flowee.Label { + text: "to:" + } + Flowee.Label { + text: payment.targetAddress + } + Flowee.Label { + text: "amount:" + } + Flowee.Label { + text: payment.paymentAmount + } + Flowee.Label { + text: "comment:" + } + Flowee.Label { + text: payment.userComment + } +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index c2ec53d..78cc793 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -16,116 +16,27 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 -// import QtQuick.Layouts -import "../Flowee" as Flowee -import Flowee.org.pay; +import QtQuick.Layouts FocusScope { id: root property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); property string title: qsTr("Send") - Component.onCompleted: setOpacities() + ColumnLayout { + width: parent.width - 20 + x: 10 - ListView { - id: tabHeader - width: parent.width - height: 50 - clip: true - orientation: ListView.Horizontal - snapMode: ListView.SnapToItem - - model: stack.children.length - delegate: Rectangle { - width: Math.max(130, tabLabel.width) - height: tabLabel.height + 25 - color: { - if (index === tabHeader.currentIndex) - return Pay.useDarkSkin ? "darkgreen" : "green"; - return Pay.useDarkSkin ? "#565f68" : "#a5b5c6" - } - Flowee.Label { - id: tabLabel - anchors.centerIn: parent - text: stack.children[index].title - } - MouseArea { - anchors.fill: parent - onClicked: { - tabHeader.currentIndex = index - root.setOpacities() - } - } + TextButton { + text: qsTr("Scan a QR code") + showPageIcon: true + onClicked: thePile.push("PayWithQR.qml") } - } - - function setOpacities() { - for (let i = 0; i < stack.children.length; ++i) { - let on = i === tabHeader.currentIndex; - let child = stack.children[i]; - child.visible = on; - } - // try to make sure the current tab gets keyboard focus properly. - forceActiveFocus(); - let visibleChild = stack.children[tabHeader.currentIndex]; - visibleChild.focus = true - } - - Item { - id: stack - width: parent.width - anchors.top: tabHeader.bottom - anchors.bottom: parent.bottom - - FocusScope { - id: tab1 - anchors.fill: parent - property string title: qsTr("Rejected") - /* - Payment { // the model behind the Payment logic - id: payment - fiatPrice: Fiat.price - account: portfolio.current - } */ - Rectangle { - width: 10 - height: 10 - x: 10 - color: "red" - } - } - FocusScope { - id: scanQrTab - anchors.fill: parent - property string title: qsTr("Scan QR") - // on activate the CameraHandler will check if we have permissions and if so, turn on 'showCamera' - onFocusChanged: if (focus) scanner.start(); - - QRScanner { - id: scanner - } - - /* - Also consider to fetch the actual resolution from the actual cpp stream and report that to QML for better scaling / preview. - And try to move the actual parsing of the QR to a different thread. - A simple member of the latest frame can exist in the gui thread, protected by a mutex. - Then the other thread will take the latest one, scan for the QR and finish. Meaning it will basically skip all the frames without blocking the GUI - } */ - } - FocusScope { - property string title: "Tab C" - anchors.fill: parent - Rectangle { - width: 10 - height: 10 - x: 30 - color: "yellow" - } - QQC2.TextField { - focus: true - y: 100 - } + TextButton { + text: qsTr("My Wallets") + subtext: qsTr("Move between wallets") + showPageIcon: true + onClicked: thePile.push("MoveBetweeWallets.qml") } } } -- 2.54.0 From ca60e7360d7d7fb770b83d62a140f57a88da0a4e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 18:35:02 +0100 Subject: [PATCH 0138/1428] Add comment. --- guis/mobile/TextButton.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 056f318..d1d13bd 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -26,6 +26,7 @@ Item { signal clicked property alias text: label.text property alias subtext: smallLabel.text + /// When true, show an arrow indicating we open a new page on click property bool showPageIcon: false property string imageSource: "" property int imageWidth: 16 -- 2.54.0 From a10604884084e00d3a121d4021d80b0c18afc172 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 20:31:00 +0100 Subject: [PATCH 0139/1428] drop the wrapping of pages in a grid layout It now feels like a premature optimization to make anything that inherets from Page be wrapped in a grid layout. Notice that a "Page" here is the thing we push on the card-stack, the Page has the back icon and the header text. --- guis/mobile/About.qml | 96 +++++---- guis/mobile/NetView.qml | 1 + guis/mobile/NewAccount.qml | 419 +++++++++++++++++++------------------ guis/mobile/Page.qml | 28 +-- 4 files changed, 278 insertions(+), 266 deletions(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index d9f13e2..748417c 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -22,41 +22,44 @@ import "../Flowee" as Flowee Page { headerText: qsTr("About") - Image { - source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" - Layout.fillWidth: true - fillMode: Image.PreserveAspectFit - } + ColumnLayout { + width: parent.width - Flowee.Label { - text: "Flowee Pay (mobile) v" + Application.version - } - Item { width: 10; height: 10 } // spacer + Image { + source: Pay.useDarkSkin ? "qrc:/FloweePay-light.svg" : "qrc:/FloweePay.svg" + Layout.fillWidth: true + fillMode: Image.PreserveAspectFit + } - TextButton { - id: translate - text: qsTr("Help translate this app") - imageSource: Pay.useDarkSkin ? "qrc:/external-light.svg" : "qrc:/external.svg" - onClicked: Qt.openUrlExternally("https://crowdin.com/project/floweepay"); - } - TextButton { - text: qsTr("License") - imageSource: translate.imageSource - onClicked: Qt.openUrlExternally("https://www.gnu.org/licenses/gpl-3.0") - } - TextButton { - text: qsTr("Credits") - subtext: qsTr("© 2020-2022 Tom Zander and contributors") - showPageIcon: true - onClicked: thePile.push(creditsPage) + Flowee.Label { + text: "Flowee Pay (mobile) v" + Application.version + } + Item { width: 10; height: 10 } // spacer - Component { - id: creditsPage - Page { - headerText: qsTr("Credits") + TextButton { + id: translate + text: qsTr("Help translate this app") + imageSource: Pay.useDarkSkin ? "qrc:/external-light.svg" : "qrc:/external.svg" + onClicked: Qt.openUrlExternally("https://crowdin.com/project/floweepay"); + } + TextButton { + text: qsTr("License") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://www.gnu.org/licenses/gpl-3.0") + } + TextButton { + text: qsTr("Credits") + subtext: qsTr("© 2020-2022 Tom Zander and contributors") + showPageIcon: true + onClicked: thePile.push(creditsPage) - Flowee.Label { - text: "## Author and maintainer + Component { + id: creditsPage + Page { + headerText: qsTr("Credits") + + Flowee.Label { + text: "## Author and maintainer Tom Zander ## Code Contributors @@ -76,23 +79,24 @@ Nederland
yantri
" - textFormat: Text.MarkdownText - Layout.fillWidth: true - Layout.fillHeight: true - wrapMode: Text.WordWrap + textFormat: Text.MarkdownText + Layout.fillWidth: true + Layout.fillHeight: true + wrapMode: Text.WordWrap + } } } } - } - TextButton { - text: qsTr("Project Home") - subtext: qsTr("With git repository and issues tracker") - imageSource: translate.imageSource - onClicked: Qt.openUrlExternally("https://codeberg.org/Flowee/pay"); - } - TextButton { - text: qsTr("Telegram") - imageSource: translate.imageSource - onClicked: Qt.openUrlExternally("https://t.me/Flowee_org"); + TextButton { + text: qsTr("Project Home") + subtext: qsTr("With git repository and issues tracker") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://codeberg.org/Flowee/pay"); + } + TextButton { + text: qsTr("Telegram") + imageSource: translate.imageSource + onClicked: Qt.openUrlExternally("https://t.me/Flowee_org"); + } } } diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index 20a9fff..67ca72a 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -25,6 +25,7 @@ Page { ListView { id: listView model: net.peers + anchors.fill: parent QQC2.ScrollBar.vertical: QQC2.ScrollBar { } focus: true diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 55ba19c..51e826d 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -23,7 +23,6 @@ import Flowee.org.pay Page { id: root - columns: 1 headerText: qsTr("New Bitcoin Cash Wallet") headerButtonVisible: true @@ -41,63 +40,68 @@ Page { property int selectedKey: 1 - Flowee.Label { - text: qsTr("Create a New Wallet") + ":" - } - Flow { - width: parent.width - property int selectorWidth: (width - spacing * 2) / 2; - property alias selectedKey: root.selectedKey - Flowee.CardTypeSelector { - id: accountTypeBasic - key: 0 - title: qsTr("Basic") - width: parent.selectorWidth - onClicked: root.selectedKey = key + ColumnLayout { + anchors.fill: parent - features: [ - qsTr("Private keys based", "Property of a wallet"), - qsTr("Difficult to backup", "Context: wallet type"), - qsTr("Great for brief usage", "Context: wallet type") - ] + Flowee.Label { + text: qsTr("Create a New Wallet") + ":" } - Flowee.CardTypeSelector { - id: accountTypePreferred - key: 1 - title: qsTr("HD wallet") - width: parent.selectorWidth - onClicked: root.selectedKey = key + Flow { + width: parent.width + property int selectorWidth: (width - spacing * 2) / 2; + property alias selectedKey: root.selectedKey - features: [ - qsTr("Seed-phrase based", "Context: wallet type"), - qsTr("Easy to backup", "Context: wallet type"), - qsTr("Most compatible", "The most compatible wallet type") - ] + Flowee.CardTypeSelector { + id: accountTypeBasic + key: 0 + title: qsTr("Basic") + width: parent.selectorWidth + onClicked: root.selectedKey = key + + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] + } + Flowee.CardTypeSelector { + id: accountTypePreferred + key: 1 + title: qsTr("HD wallet") + width: parent.selectorWidth + onClicked: root.selectedKey = key + + features: [ + qsTr("Seed-phrase based", "Context: wallet type"), + qsTr("Easy to backup", "Context: wallet type"), + qsTr("Most compatible", "The most compatible wallet type") + ] + } } - } - Item { width: 10; height: 15 } + Item { width: 10; height: 15 } - Flowee.Label { - text: qsTr("Import Existing Wallet") + ":" - } - Item { - width: parent.width - height: accountTypeImport.height - property alias selectedKey: root.selectedKey - Flowee.CardTypeSelector { - id: accountTypeImport - key: 2 - title: qsTr("Import") - width: Math.min(parent.width / 3 * 2, 250) - onClicked: root.selectedKey = key - anchors.horizontalCenter: parent.horizontalCenter + Flowee.Label { + text: qsTr("Import Existing Wallet") + ":" + } + Item { + width: parent.width + height: accountTypeImport.height + property alias selectedKey: root.selectedKey + Flowee.CardTypeSelector { + id: accountTypeImport + key: 2 + title: qsTr("Import") + width: Math.min(parent.width / 3 * 2, 250) + onClicked: root.selectedKey = key + anchors.horizontalCenter: parent.horizontalCenter - features: [ - qsTr("Imports seed-phrase"), - qsTr("Imports private key") - ] + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] + } } } @@ -128,34 +132,38 @@ Page { thePile.pop(); } - Flowee.Label { - id: title - text: qsTr("This creates a new empty wallet with simple multi-address capability") - Layout.fillWidth: true - wrapMode: Text.WordWrap - } - Flowee.Label { - text: qsTr("Name") + ":"; - } - Flowee.TextField { - id: accountName - focus: true - Layout.fillWidth: true - } - Item { width: 10; height: 10 } - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") - } - Flowee.Label { - text: qsTr("Derivation") + ":" - } - Flowee.TextField { - id: derivationPath - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most wallets use to import by default - color: Pay.checkDerivation(text) ? palette.text : "red" + ColumnLayout { + width: parent.width + + Flowee.Label { + id: title + text: qsTr("This creates a new empty wallet with simple multi-address capability") + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + focus: true + Layout.fillWidth: true + } + Item { width: 10; height: 10 } + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + } + Flowee.Label { + text: qsTr("Derivation") + ":" + } + Flowee.TextField { + id: derivationPath + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most wallets use to import by default + color: Pay.checkDerivation(text) ? palette.text : "red" + } } } } @@ -180,19 +188,23 @@ Page { thePile.pop(); thePile.pop(); } - Flowee.Label { - id: title - Layout.fillWidth: true - text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") - wrapMode: Text.WordWrap - } - Flowee.Label { - text: qsTr("Name") + ":"; - } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - focus: true + + ColumnLayout { + width: parent.width + Flowee.Label { + id: title + Layout.fillWidth: true + text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") + wrapMode: Text.WordWrap + } + Flowee.Label { + text: qsTr("Name") + ":"; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + focus: true + } } } } @@ -202,11 +214,10 @@ Page { Page { id: importAccount headerText: qsTr("Import Wallet") - columns: 2 + headerButtonVisible: true headerButtonText: qsTr("Create") headerButtonEnabled: accountName.text.length > 2 && finished - property var typedData: Pay.identifyString(secrets.text) property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; @@ -233,122 +244,126 @@ Page { thePile.pop(); } - Flowee.Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.fillWidth: true - Layout.columnSpan: 2 - wrapMode: Text.Wrap - } - Flowee.Label { - text: qsTr("Secret", "The seed-phrase or private key") + ":" - Layout.columnSpan: 2 - } - Flowee.MultilineTextField { - id: secrets - Layout.fillWidth: true - focus: true - nextFocusTarget: accountName - clip: true - } - Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - Layout.alignment: Qt.AlignTop - } - - Flowee.Label { - id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color - text: { - var typedData = importAccount.typedData - if (typedData === Wallet.PrivateKey) - return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) - return qsTr("BIP 39 seed-phrase", "description of type") - if (typedData === Wallet.PartialMnemonicWithTypo) - return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Wallet.MissingLexicon) - return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this - return "" + GridLayout { + width: parent.width + columns: 2 + Flowee.Label { + text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") + Layout.fillWidth: true + Layout.columnSpan: 2 + wrapMode: Text.Wrap + } + Flowee.Label { + text: qsTr("Secret", "The seed-phrase or private key") + ":" + Layout.columnSpan: 2 + } + Flowee.MultilineTextField { + id: secrets + Layout.fillWidth: true + focus: true + nextFocusTarget: accountName + clip: true + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + Layout.alignment: Qt.AlignTop } - Layout.columnSpan: 2 - } - Flowee.Label { - text: qsTr("Name") + ":" - Layout.columnSpan: 2 - } - Flowee.TextField { - id: accountName - Layout.columnSpan: 2 - Layout.fillWidth: true - } + Flowee.Label { + id: detectedType + color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color + text: { + var typedData = importAccount.typedData + if (typedData === Wallet.PrivateKey) + return qsTr("Private key", "description of type") // TODO print address to go with it + if (typedData === Wallet.CorrectMnemonic) + return qsTr("BIP 39 seed-phrase", "description of type") + if (typedData === Wallet.PartialMnemonicWithTypo) + return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") + if (typedData === Wallet.MissingLexicon) + return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this + return "" + } + Layout.columnSpan: 2 + } - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") - checked: true - visible: importAccount.isPrivateKey - Layout.columnSpan: 2 - } - Flowee.Label { - text: qsTr("Alternate phrase") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - Layout.columnSpan: 2 - } - Flowee.Label { - Layout.columnSpan: 2 - text: qsTr("Oldest Transaction") + ":" - } - Flow { - spacing: 10 - QQC2.ComboBox { - id: month - model: { - let locale = Qt.locale(); - var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { - list.push(locale.monthName(i)); + Flowee.Label { + text: qsTr("Name") + ":" + Layout.columnSpan: 2 + } + Flowee.TextField { + id: accountName + Layout.columnSpan: 2 + Layout.fillWidth: true + } + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + visible: importAccount.isPrivateKey + Layout.columnSpan: 2 + } + Flowee.Label { + text: qsTr("Alternate phrase") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + Layout.columnSpan: 2 + } + Flowee.Label { + Layout.columnSpan: 2 + text: qsTr("Oldest Transaction") + ":" + } + Flow { + spacing: 10 + QQC2.ComboBox { + id: month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; } - return list; + } + QQC2.ComboBox { + id: year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 9; } } - QQC2.ComboBox { - id: year - model: { - var list = []; - let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { - list.push(i); - } - return list; - } - currentIndex: 9; + Flowee.Label { + text: qsTr("Derivation") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + id: derivationPath + Layout.columnSpan: 2 + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most BCH wallets are created with + visible: !importAccount.isPrivateKey + color: Pay.checkDerivation(text) ? palette.text : "red" } - } - Flowee.Label { - text: qsTr("Derivation") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - id: derivationPath - Layout.columnSpan: 2 - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most BCH wallets are created with - visible: !importAccount.isPrivateKey - color: Pay.checkDerivation(text) ? palette.text : "red" } } } diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 66ddecc..d60e248 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -28,7 +28,6 @@ QQC2.Control { default property alias content: child.children property alias headerText: headerLabel.text - property alias columns: child.columns property alias headerButtonVisible: headerButton.visible property alias headerButtonText: headerButton.text property alias headerButtonEnabled: headerButton.enabled @@ -83,23 +82,16 @@ QQC2.Control { height: parent.height - y color: root.palette.base } - - Flickable { - width: parent.width - y: header.height + 10 - height: parent.height - y - clip: true - contentHeight: child.implicitHeight - contentWidth: width - FocusScope { - id: focusScope - GridLayout { - id: child - width: root.width - 20 - x: 10 // default margin - height: implicitHeight - columns: 1 - } + FocusScope { + id: focusScope + anchors.fill: parent + Flickable { + id: child + width: root.width - 20 + x: 10 + y: header.height + 10 + height: root.height - y + clip: true } } Keys.onPressed: (event)=> { -- 2.54.0 From 98be7d0ff09f72024a1b919ee5949858253da2f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 20:33:12 +0100 Subject: [PATCH 0140/1428] Link button --- guis/mobile/AccountHistory.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 714e47b..fc7d41b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -74,7 +74,8 @@ ListView { height: 60 IconButton { width: parent.width / 3 - text: qsTr("Send Money") + text: qsTr("Pay") + onClicked: thePile.push("PayWithQR.qml") } IconButton { width: parent.width / 3 @@ -240,6 +241,7 @@ ListView { } Flowee.BitcoinAmountLabel { opacity: Pay.hideBalance ? 0.2 : 1 + Layout.alignment: Qt.AlignRight value: { if (Pay.hideBalance) return 88888888; -- 2.54.0 From 2818cc44f98a38639096f2b2c8338cb8a04f406e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Dec 2022 21:24:32 +0100 Subject: [PATCH 0141/1428] Setup the page to have the needed components. This is mostly without user interaction right now, but all the needed stuff is on the page to show the visual design. --- guis/mobile/PayWithQR.qml | 138 +++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 17 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index f094e63..9dd485e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -21,14 +21,10 @@ import "../Flowee" as Flowee import Flowee.org.pay; Page { - headerText: qsTr("Scan QR") - - columns: 2 - - Item { - id: data - Layout.columnSpan: 2 + id: root + headerText: qsTr("Approve Payment") + Item { // data QRScanner { id: scanner scanType: QRScanner.PaymentDetails @@ -40,22 +36,130 @@ Page { } } - Flowee.Label { - text: "to:" + Flowee.BitcoinAmountLabel { + id: priceBch + value: payment.paymentAmount + fontPixelSize: 38 + anchors.horizontalCenter: parent.horizontalCenter + y: 30 + colorize: false + showFiat: false } Flowee.Label { - text: payment.targetAddress + id: priceFiat + text: Fiat.formattedPrice(payment.paymentAmount, Fiat.price) + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: priceBch.bottom + anchors.topMargin: 12 + color: Qt.darker(palette.text, 1.2) } + + Flowee.Label { - text: "amount:" - } - Flowee.Label { - text: payment.paymentAmount - } - Flowee.Label { - text: "comment:" + id: commentLabel + text: qsTr("Payment description" + ":") + visible: userComment.text !== "" + color: palette.highlight + + anchors.top: priceFiat.bottom + anchors.topMargin: 30 } Flowee.Label { + id: userComment text: payment.userComment + visible: text !== "" + font.italic: true + anchors.top: commentLabel.bottom + anchors.topMargin: 5 } + + Flowee.Label { + id: currentWalletLabel + text: portfolio.current.name + anchors.baseline: parent.width > currentWalletLabel.width + currentWalletValue.width + ? currentWalletValue.baseline : undefined + anchors.bottom: parent.width > currentWalletLabel.width + currentWalletValue.width + ? undefined : currentWalletValue.top + } + Flowee.BitcoinAmountLabel { + id: currentWalletValue + anchors.right: parent.right + anchors.bottom: numericKeyboard.top + anchors.bottomMargin: 20 + value: { + var wallet = portfolio.current; + return wallet.balanceConfirmed + wallet.balanceUnconfirmed; + } + } + + Flow { + id: numericKeyboard + anchors.bottom: slideToApprove.top + width: parent.width + Repeater { + model: 12 + delegate: Item { + width: numericKeyboard.width / 3 + height: text.height + 20 + Flowee.Label { + id: text + anchors.centerIn: parent + font.pixelSize: 28 + text: { + if (index < 9) + return index + 1; + if (index === 9) + return Locale.decimalPoint + if (index === 10) + return "0" + if (index === 11) + return "<-" // TODO use a backspace icon instead. + } + } + } + } + } + Item { + id: slideToApprove + width: parent.width + height: 60 + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + Rectangle { + x: 30 + width: parent.height + height: width + radius: width / 2 + color: root.palette.window + } + Rectangle { + x: parent.width - width - 30 + width: parent.height + height: width + radius: width / 2 + color: root.palette.window + } + Rectangle { + x: 30 + parent.height / 2 + width: parent.width - 30 - 30 - parent.height + height: parent.height + color: root.palette.window + } + Flowee.Label { + text: qsTr("SLIDE TO SEND") + anchors.centerIn: parent + } + + Rectangle { + width: parent.height - 5 + height: width + radius: width / 2 + // opacity: 0.8 + x: 35 + y: 2.5 + color: Pay.useDarkSkin ? mainWindow.floweeGreen : mainWindow.floweeBlue + } + + } + } -- 2.54.0 From f25b4a716ca79b04926fae4da981170668e414b6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 20:15:48 +0100 Subject: [PATCH 0142/1428] Allow the 'BitcoinValue' to easier editable This new allows a value to be set as a number, for instance from the user reading a QR code, and the "typed-value" is created from this in order to allow the user to edit it instead of overwrite it. --- BitcoinValue.cpp | 27 +++++++++++++++++++++++---- testing/value/TestValue.cpp | 21 ++++++++++++++++++++- testing/value/TestValue.h | 3 ++- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/BitcoinValue.cpp b/BitcoinValue.cpp index 1f8edd6..32c723b 100644 --- a/BitcoinValue.cpp +++ b/BitcoinValue.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ BitcoinValue::BitcoinValue(QObject *parent) m_value(0) { connect (FloweePay::instance(), &FloweePay::unitChanged, this, [this]() { + // we follow what the user actually typed. setStringValue(m_typedNumber); }); } @@ -41,6 +42,16 @@ void BitcoinValue::setValue(qint64 value) return; m_value = value; emit valueChanged(); + if (value && m_typedNumber.isEmpty()) { + const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; + m_typedNumber = QString::number(m_value / pow(10, unitConfigDecimals), 'f', unitConfigDecimals); + if (unitConfigDecimals > 0) { // there is a comma separator in the string + // remove trailing zeros + while (m_typedNumber.endsWith('0') || m_typedNumber.endsWith('.')) + m_typedNumber = m_typedNumber.left(m_typedNumber.size() - 1); + } + m_cursorPos = m_typedNumber.size(); + } } void BitcoinValue::moveLeft() @@ -144,12 +155,13 @@ void BitcoinValue::setEnteredString(const QString &value) void BitcoinValue::setStringValue(const QString &value) { int separator = value.indexOf('.'); - QString before, after; + QStringView before; + QString after; if (separator == -1) { before = value; after = "000000000"; } else { - before = value.left(separator); + before = QStringView(value).left(separator); after = value.mid(separator + 1); } qint64 newVal = before.toLong(); @@ -160,7 +172,7 @@ void BitcoinValue::setStringValue(const QString &value) while (after.size() < unitConfigDecimals) after += '0'; - newVal += after.toInt(); + newVal += QStringView(after).left(unitConfigDecimals).toInt(); setValue(newVal); } @@ -186,6 +198,13 @@ void BitcoinValue::setMaxFractionalDigits(int newMaxFractionalDigits) return; m_maxFractionalDigits = newMaxFractionalDigits; emit maxFractionalDigitsChanged(); + + // the behavior here assumes that this is a property most won't set and those that do + // only set it once + if (m_value) { + m_typedNumber = QString::number(m_value / pow(10, m_maxFractionalDigits), 'f', m_maxFractionalDigits); + m_cursorPos = m_typedNumber.size(); + } } void BitcoinValue::resetMaxFractionalDigits() diff --git a/testing/value/TestValue.cpp b/testing/value/TestValue.cpp index 937dc18..c37b40e 100644 --- a/testing/value/TestValue.cpp +++ b/testing/value/TestValue.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -190,4 +190,23 @@ void TestValue::fiatValues() QCOMPARE(testObject.value(), 3457); } +void TestValue::setText() +{ + BitcoinValue testObject; + testObject.setEnteredString("1.23456789"); + QCOMPARE(testObject.value(), 123456789); + FloweePay::instance()->setUnit(FloweePay::BCH); + QCOMPARE(testObject.value(), 123456789); + FloweePay::instance()->setUnit(FloweePay::MilliBCH); + QCOMPARE(testObject.value(), 123456); + FloweePay::instance()->setUnit(FloweePay::MicroBCH); + QCOMPARE(testObject.value(), 123); + FloweePay::instance()->setUnit(FloweePay::Bits); + QCOMPARE(testObject.value(), 123); + FloweePay::instance()->setUnit(FloweePay::Satoshis); + QCOMPARE(testObject.value(), 1); + FloweePay::instance()->setUnit(FloweePay::BCH); + QCOMPARE(testObject.value(), 123456789); +} + QTEST_MAIN(TestValue) diff --git a/testing/value/TestValue.h b/testing/value/TestValue.h index b4f5e61..3cdf8cc 100644 --- a/testing/value/TestValue.h +++ b/testing/value/TestValue.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ private slots: void basics(); void insertAtCursor(); void fiatValues(); + void setText(); private: }; -- 2.54.0 From 6b4b875d44d11065725d42917f92fd648fbeb47b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 20:17:08 +0100 Subject: [PATCH 0143/1428] Add several more simple properties to be used in QML --- PriceDataProvider.cpp | 32 +++++++++++++++++++++++++++++++- PriceDataProvider.h | 30 +++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/PriceDataProvider.cpp b/PriceDataProvider.cpp index c82bfc7..ee480d6 100644 --- a/PriceDataProvider.cpp +++ b/PriceDataProvider.cpp @@ -70,10 +70,15 @@ QString PriceDataProvider::formattedPrice(double amountSats, int price) const { if (price == 0) return QString(); + return formattedPrice(priceFor(amountSats, price)); +} + +int PriceDataProvider::priceFor(double amountSats, int price) const +{ qint64 fiatValue = amountSats * price; fiatValue = (fiatValue + 50000000) / qint64(100000000); assert(fiatValue < INT_MAX); - return formattedPrice(static_cast(fiatValue)); + return static_cast(fiatValue); } QString PriceDataProvider::formattedPrice(int fiatValue) const @@ -122,6 +127,21 @@ QString PriceDataProvider::formattedPrice(int fiatValue) const } } +QString PriceDataProvider::priceToStringSimple(int cents) const +{ + auto value = QString::number(cents); + if (m_displayCents) { + const QChar comma = QLocale::system().decimalPoint().at(0); + if (cents < 10) + value = "0" + value; + if (cents < 100) + value = "0" % comma % value; + else + value = value.left(value.size() - 2) % comma % value.right(2); + } + return value; +} + void PriceDataProvider::fetch() { QString url(CoinGeckoURL); @@ -186,6 +206,16 @@ void PriceDataProvider::finishedDownload() m_timer.start(ReloadTimeout); } +QString PriceDataProvider::currencySymbolPost() const +{ + return m_currencySymbolPost; +} + +QString PriceDataProvider::currencySymbolPrefix() const +{ + return m_currencySymbolPrefix; +} + bool PriceDataProvider::displayCents() const { return m_displayCents; diff --git a/PriceDataProvider.h b/PriceDataProvider.h index 18179b4..0a0c927 100644 --- a/PriceDataProvider.h +++ b/PriceDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,8 @@ class PriceDataProvider : public QObject Q_PROPERTY(int price READ price NOTIFY priceChanged) Q_PROPERTY(QString currencyName READ currencyName NOTIFY currencySymbolChanged) Q_PROPERTY(bool displayCents READ displayCents CONSTANT) + Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix CONSTANT) + Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost CONSTANT) public: explicit PriceDataProvider(QObject *parent = nullptr); @@ -49,12 +51,24 @@ public: * based on \a price. */ Q_INVOKABLE QString formattedPrice(double amountSats, int price) const; + /// Return the price as int (in cents) for the number of sats and the given price. + Q_INVOKABLE int priceFor(double amountSats, int price) const; + /// Return the price as int (in cents) for the number of sats and the current price. + Q_INVOKABLE int priceFor(double amountSats) const { + return priceFor(amountSats, m_currentPrice.price); + } /** * Return a formatted string with the locale-defined price of a fiat price from \a cents. */ Q_INVOKABLE QString formattedPrice(int cents) const; + /// return a string with the given price and needed decimal separator. + /// Please note that the currency indicators are not included, unlike in formattedPrice() + /// \see currencySymbolPrefix() + /// \see currencySymbolPost() + Q_INVOKABLE QString priceToStringSimple(int cents) const; + /** * Returns if the locale defined currency wants to display cents. * @@ -63,6 +77,20 @@ public: */ bool displayCents() const; + /** + * The string to show in front of a fiat price. + * Fiat prices need some unit, or currency-symbol, which we split into the prefix and post + * strings to help display in QML. + */ + QString currencySymbolPrefix() const; + + /** + * The string to show after a fiat price. + * Fiat prices need some unit, or currency-symbol, which we split into the prefix and post + * strings to help display in QML. + */ + QString currencySymbolPost() const; + signals: void priceChanged(int32_t price); void currencySymbolChanged(); -- 2.54.0 From d14f2db7631ae4cfd2068377910cb70a8f5b689a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 20:19:01 +0100 Subject: [PATCH 0144/1428] Rewrite BitcoinValueField to use font-sizing This now makes the editing version look identical to the basic display version with regards to font sizing and coloring. --- guis/Flowee/BitcoinValueField.qml | 153 ++++++++++++++++++++++++------ guis/Flowee/LabelWithCursor.qml | 92 ++++++++++++++++++ guis/widgets.qrc | 1 + 3 files changed, 219 insertions(+), 27 deletions(-) create mode 100644 guis/Flowee/LabelWithCursor.qml diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index d84741b..977ddd6 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -15,39 +15,138 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import Flowee.org.pay 1.0 +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import Flowee.org.pay -/* - * Similar to TextField, this is a wrapper around BitcoinAmountLabel - * in order to provide a background and ediing capabilities. - */ -MoneyValueField { +FocusScope { id: root - implicitHeight: balance.height + 12 - implicitWidth: balance.width + 12 + focus: true + activeFocusOnTab: true - property alias fontPixelSize: balance.fontPixelSize - property double baselineOffset: balance.baselineOffset + balance.y + property alias value: privValue.value + property alias fontPixelSize: beginOfValue.fontPixelSize + property alias contentWidth: row.width + property alias color: beginOfValue.color + baselineOffset: beginOfValue.baselineOffset + signal valueEdited; - BitcoinAmountLabel { - id: balance - x: 6 - y: 6 - value: root.value - colorize: false - visible: !root.activeFocus - color: unit.palette.text - showFiat: false + implicitHeight: beginOfValue.implicitHeight + implicitWidth: row.width + + BitcoinValue { + id: privValue } - Label { - id: unit - text: Pay.unitName - y: 6 - anchors.right: parent.right - anchors.rightMargin: 8 + RowLayout { + id: row + spacing: 4 + height: parent.height + property string amountString: Pay.amountToString(privValue.value) + + LabelWithCursor { + id: beginOfValue + fullString: row.amountString + stringLength: { + /* + Behavior differs based on the unitAllowedDecimals. + Basically this labelWithCursor eats up all the characters + not taken by the other two. But based on the unit they may not + be used at all (since they show smaller fonts). + */ + var allowedDecimals = Pay.unitAllowedDecimals; + var removeChars = 0; + if (allowedDecimals === 8) + removeChars = 5; + else if (allowedDecimals > 0) + removeChars = 2 + 1; + return fullString.length - removeChars + } + cursorPos: root.activeFocus ? privValue.cursorPos : -1 + Layout.alignment: Qt.AlignBaseline + showCursor: root.activeFocus + } + LabelWithCursor { + id: middle + Layout.alignment: Qt.AlignBaseline + color: beginOfValue.color + cursorPos: root.activeFocus ? privValue.cursorPos : -1 + fontPixelSize: beginOfValue.fontPixelSize / 10 * 8 + fullString: row.amountString + textOpacity: text === "000" ? 0.3 : 1 + startPos: beginOfValue.startPos + beginOfValue.stringLength + stringLength: 3 + // visible: Pay.unitAllowedDecimals === 8 + showCursor: root.activeFocus + } + LabelWithCursor { + id: satsLabel + color: beginOfValue.color + cursorPos: root.activeFocus ? privValue.cursorPos : -1 + fontPixelSize: beginOfValue.fontPixelSize / 10 * 8 + fullString: row.amountString + Layout.alignment: Qt.AlignBaseline + textOpacity: text === "00" ? 0.3 : 1 + startPos: middle.startPos + middle.stringLength + stringLength: 2 + visible: Pay.unitAllowedDecimals >= 2 + showCursor: root.activeFocus + } + + Label { + text: Pay.unitName + color: beginOfValue.color + Layout.alignment: Qt.AlignBaseline + } + } + + Rectangle { + anchors.top: row.bottom + anchors.left: row.left + anchors.right: row.right + height: 1.3 + color: mainWindow.palette.highlight visible: root.activeFocus } + + MouseArea { + anchors.fill: parent + onClicked: root.forceActiveFocus(); + } + + Keys.onPressed: (event)=> { + if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { + privValue.insertNumber(event.key); + event.accepted = true; + root.valueEdited(); + } + else if (event.key === 44 || event.key === 46) { + privValue.addSeparator(); + event.accepted = true; + root.valueEdited(); + } + else if (event.key === Qt.Key_Backspace) { + privValue.backspacePressed(); + event.accepted = true; + root.valueEdited(); + } + else if (event.key === Qt.Key_Left) { + privValue.moveLeft(); + cursor.cursorVisible = true; + event.accepted = true; + } + else if (event.key === Qt.Key_Right) { + privValue.moveRight(); + cursor.cursorVisible = true; + event.accepted = true; + } + } + Keys.onReleased: (event)=> { + if (event.matches(StandardKey.Paste)) { + privValue.paste(); + event.accepted = true; + root.valueEdited(); + } + } } diff --git a/guis/Flowee/LabelWithCursor.qml b/guis/Flowee/LabelWithCursor.qml new file mode 100644 index 0000000..eecffd4 --- /dev/null +++ b/guis/Flowee/LabelWithCursor.qml @@ -0,0 +1,92 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Item { + id: root + + property alias fontPixelSize: begin.font.pixelSize + property alias color: begin.color + property alias textOpacity: begin.opacity + property string fullString: "" + property int startPos: 0 + property int stringLength: fullString.length + property int cursorPos: 0 + property bool showCursor: false + implicitWidth: begin.implicitWidth + secondPart.implicitWidth + (cursor.visible ? 1 : 0) + implicitHeight: begin.height + width: implicitWidth + height: implicitHeight + + property string text: { + return fullString.substring(startPos, startPos + stringLength) + } + + baselineOffset: begin.baselineOffset + + Label { + id: begin + color: palette.text + text: { + var fullText = root.text + var cutPoint = parent.cursorPos - parent.startPos; + if (cutPoint >= 0 && cutPoint < parent.stringLength) { + return fullText.substring(0, cutPoint); + } + return fullText; + } + } + Rectangle { + id: cursor + anchors.left: begin.right + width: 1.3 + height: root.height + visible: { + var cutPoint = parent.cursorPos - parent.startPos; + var lastSegment = parent.fullString.length === parent.startPos + parent.stringLength + if (cutPoint < 0) + return false; + if (lastSegment) + return cutPoint <= parent.stringLength + return cutPoint < parent.stringLength + } + color: cursorVisible ? begin.palette.text : "#00000000" + property bool cursorVisible: true + Timer { + id: blinkingCursor + running: root.showCursor + repeat: true + interval: 500 + onTriggered: parent.cursorVisible = !parent.cursorVisible + } + } + Label { + id: secondPart + color: begin.color + anchors.left: begin.right + font.pixelSize: begin.font.pixelSize + opacity: begin.opacity + text: { + var cutPoint = parent.cursorPos - parent.startPos; + if (cutPoint >= 0 && cutPoint < parent.stringLength) { + return root.text.substring(cutPoint); + } + return ""; + } + } +} diff --git a/guis/widgets.qrc b/guis/widgets.qrc index eed664a..88edd38 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -24,5 +24,6 @@ Flowee/WarningLabel.qml Flowee/PasswdDialog.qml Flowee/Label.qml + Flowee/LabelWithCursor.qml -- 2.54.0 From 6e5b330cc63bc206c63d94ac775fe0fb3930cb07 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 22:05:49 +0100 Subject: [PATCH 0145/1428] Also update FiatValueField.qml Same concept as BitcoinValueField, and a small refactor to reuse code between them. --- guis/Flowee/BitcoinValueField.qml | 68 ++------------------------- guis/Flowee/FiatValueField.qml | 53 +++++++++++++-------- guis/Flowee/MoneyValueField.qml | 76 +++++-------------------------- 3 files changed, 51 insertions(+), 146 deletions(-) diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 977ddd6..21264f9 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -17,33 +17,24 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 import Flowee.org.pay -FocusScope { +MoneyValueField { id: root - focus: true - activeFocusOnTab: true - property alias value: privValue.value property alias fontPixelSize: beginOfValue.fontPixelSize property alias contentWidth: row.width property alias color: beginOfValue.color baselineOffset: beginOfValue.baselineOffset - signal valueEdited; implicitHeight: beginOfValue.implicitHeight implicitWidth: row.width - BitcoinValue { - id: privValue - } - RowLayout { id: row spacing: 4 height: parent.height - property string amountString: Pay.amountToString(privValue.value) + property string amountString: Pay.amountToString(root.value) LabelWithCursor { id: beginOfValue @@ -63,7 +54,7 @@ FocusScope { removeChars = 2 + 1; return fullString.length - removeChars } - cursorPos: root.activeFocus ? privValue.cursorPos : -1 + cursorPos: root.activeFocus ? root.cursorPos : -1 Layout.alignment: Qt.AlignBaseline showCursor: root.activeFocus } @@ -71,7 +62,7 @@ FocusScope { id: middle Layout.alignment: Qt.AlignBaseline color: beginOfValue.color - cursorPos: root.activeFocus ? privValue.cursorPos : -1 + cursorPos: root.activeFocus ? root.cursorPos : -1 fontPixelSize: beginOfValue.fontPixelSize / 10 * 8 fullString: row.amountString textOpacity: text === "000" ? 0.3 : 1 @@ -83,7 +74,7 @@ FocusScope { LabelWithCursor { id: satsLabel color: beginOfValue.color - cursorPos: root.activeFocus ? privValue.cursorPos : -1 + cursorPos: root.activeFocus ? root.cursorPos : -1 fontPixelSize: beginOfValue.fontPixelSize / 10 * 8 fullString: row.amountString Layout.alignment: Qt.AlignBaseline @@ -100,53 +91,4 @@ FocusScope { Layout.alignment: Qt.AlignBaseline } } - - Rectangle { - anchors.top: row.bottom - anchors.left: row.left - anchors.right: row.right - height: 1.3 - color: mainWindow.palette.highlight - visible: root.activeFocus - } - - MouseArea { - anchors.fill: parent - onClicked: root.forceActiveFocus(); - } - - Keys.onPressed: (event)=> { - if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { - privValue.insertNumber(event.key); - event.accepted = true; - root.valueEdited(); - } - else if (event.key === 44 || event.key === 46) { - privValue.addSeparator(); - event.accepted = true; - root.valueEdited(); - } - else if (event.key === Qt.Key_Backspace) { - privValue.backspacePressed(); - event.accepted = true; - root.valueEdited(); - } - else if (event.key === Qt.Key_Left) { - privValue.moveLeft(); - cursor.cursorVisible = true; - event.accepted = true; - } - else if (event.key === Qt.Key_Right) { - privValue.moveRight(); - cursor.cursorVisible = true; - event.accepted = true; - } - } - Keys.onReleased: (event)=> { - if (event.matches(StandardKey.Paste)) { - privValue.paste(); - event.accepted = true; - root.valueEdited(); - } - } } diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index 571f453..e56c3a5 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -15,29 +15,44 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import Flowee.org.pay 1.0 +import QtQuick +import QtQuick.Layouts +import Flowee.org.pay -/* - * Similar to TextField, this is a wrapper around BitcoinValue - * in order to provide a background and ediing capabilities. - */ MoneyValueField { id: root - implicitHeight: balance.height + 12 - implicitWidth: Math.max(100, balance.width + 12) - property alias fontPixelSize: balance.font.pixelSize - property double baselineOffset: balance.baselineOffset + balance.y - valueObject.maxFractionalDigits: Fiat.displayCents ? 2 : 0 + property alias fontPixelSize: fiat.fontPixelSize + property alias contentWidth: row.width + property alias color: fiat.color + baselineOffset: fiat.baselineOffset + implicitHeight: fiat.implicitHeight + implicitWidth: row.width + maxFractionalDigits: Fiat.displayCents ? 2 : 0 - Label { - id: balance - x: 6 - y: 6 - text: Fiat.formattedPrice(root.value) - visible: !root.activeFocus - color: root.enabled ? palette.text : palette.dark + RowLayout { + id: row + spacing: 4 + height: parent.height + property string amountString: Fiat.priceToStringSimple(root.value) + + Label { + text: Fiat.currencySymbolPrefix + font.pixelSize: fiat.fontPixelSize + color: fiat.color + } + + LabelWithCursor { + id: fiat + fullString: row.amountString + cursorPos: root.activeFocus ? root.cursorPos : -1 + Layout.alignment: Qt.AlignBaseline + showCursor: root.activeFocus + } + Label { + text: Fiat.currencySymbolPost + font.pixelSize: fiat.fontPixelSize + color: fiat.color + } } } diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index 42723d6..4cf63b7 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -15,81 +15,33 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import Flowee.org.pay 1.0 +import QtQuick +import Flowee.org.pay -/* - * Similar to TextField, this is a wrapper around BitcoinAmountLabel - * in order to provide a background and ediing capabilities. - */ FocusScope { id: root + focus: true activeFocusOnTab: true - property alias value: privValue.value - property alias valueObject: privValue - + property alias cursorPos: privValue.cursorPos + property alias maxFractionalDigits: privValue.maxFractionalDigits signal valueEdited; - onEnabledChanged: { - if (!enabled) - root.focus = false - } - - function reset() { - privValue.reset(); - } - BitcoinValue { id: privValue } MouseArea { - cursorShape: Qt.IBeamCursor anchors.fill: parent - enabled: root.enabled - onClicked: { - root.focus = true - root.forceActiveFocus() - } + onClicked: root.forceActiveFocus(); } - - Rectangle { // focus indicator - anchors.fill: parent - border.color: root.activeFocus ? begin.palette.highlight : begin.palette.mid - border.width: 0.8 - color: root.enabled && !Pay.useDarkSkin ? "white" : "#00000000" // transparant - } - - Item { // edit-label + Rectangle { + anchors.bottom: root.bottom + anchors.left: root.left + anchors.right: root.right + height: 1.3 + color: mainWindow.palette.highlight visible: root.activeFocus - x: 8 - y: 6 - Label { - id: begin - text: privValue.enteredString.substring(0, privValue.cursorPos) - } - Rectangle { - id: cursor - anchors.left: begin.right - width: 1 - height: root.height - 12 - color: cursorVisible ? begin.palette.text : "#00000000" - property bool cursorVisible: true - Timer { - id: blinkingCursor - running: root.activeFocus - repeat: true - interval: 500 - onTriggered: parent.cursorVisible = !parent.cursorVisible - } - } - Label { - anchors.left: begin.right - text: privValue.enteredString.substring(privValue.cursorPos) - } } - Keys.onPressed: (event)=> { if (event.key >= Qt.Key_0 && event.key <= Qt.Key_9) { privValue.insertNumber(event.key); @@ -108,14 +60,10 @@ FocusScope { } else if (event.key === Qt.Key_Left) { privValue.moveLeft(); - blinkingCursor.restart(); - cursor.cursorVisible = true; event.accepted = true; } else if (event.key === Qt.Key_Right) { privValue.moveRight(); - cursor.cursorVisible = true; - blinkingCursor.restart(); event.accepted = true; } } -- 2.54.0 From fbd4c1a8ea90d826abb711799b0853b9d709e627 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 22:07:22 +0100 Subject: [PATCH 0146/1428] Fix UX issue When the user changes unit (BCH / mBCH), the widget would not update until you typed something. Creating a surprise moment. This fixes that, at the expense of a seemingly innocent warning by QML about a binding loop. --- guis/desktop/SendTransactionPane.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 7f7e65f..e6b6cc0 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -581,7 +581,7 @@ Item { Flowee.BitcoinValueField { id: bitcoinValueField value: destinationPane.paymentDetail.paymentAmount - onValueEdited: destinationPane.paymentDetail.paymentAmount = value + onValueChanged: destinationPane.paymentDetail.paymentAmount = value } Flowee.Button { id: sendAll -- 2.54.0 From 03204fad3e79d491f38f973ca3908f6c84f2a5ba Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 Dec 2022 22:39:02 +0100 Subject: [PATCH 0147/1428] Update the PayWithQR.qml to integrate the nicer editors This makes most of the widget functional, I'll leave the "slide to pay" for tomorrow. --- BitcoinValue.cpp | 2 ++ guis/Flowee/MoneyValueField.qml | 1 + guis/mobile/PayWithQR.qml | 58 ++++++++++++++++++++++++--------- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/BitcoinValue.cpp b/BitcoinValue.cpp index 32c723b..093674d 100644 --- a/BitcoinValue.cpp +++ b/BitcoinValue.cpp @@ -68,6 +68,8 @@ void BitcoinValue::moveRight() void BitcoinValue::insertNumber(QChar number) { + if (!number.isNumber()) + throw std::runtime_error("Only numbers can be inserted in insertNumber"); int pos = m_typedNumber.indexOf('.'); const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; if (pos > -1 && m_cursorPos > pos && m_typedNumber.size() - pos - unitConfigDecimals > 0) diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index 4cf63b7..5ad7c11 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -24,6 +24,7 @@ FocusScope { activeFocusOnTab: true property alias value: privValue.value property alias cursorPos: privValue.cursorPos + property alias moneyEditor: privValue property alias maxFractionalDigits: privValue.maxFractionalDigits signal valueEdited; diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 9dd485e..5e40aae 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -36,25 +36,35 @@ Page { } } - Flowee.BitcoinAmountLabel { + // if true, the bitcoin value is provided by the user (or QR), and the fiat follows + // if false, the user edits the fiat price and the bitcoin value is calculated. + property bool fiatFollowsSats: true + + Flowee.BitcoinValueField { id: priceBch - value: payment.paymentAmount - fontPixelSize: 38 - anchors.horizontalCenter: parent.horizontalCenter y: 30 - colorize: false - showFiat: false + value: payment.paymentAmount + focus: true + anchors.horizontalCenter: parent.horizontalCenter + onValueEdited: payment.paymentAmount = value + fontPixelSize: size + property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize + Behavior on size { NumberAnimation { } } + onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true } - Flowee.Label { + Flowee.FiatValueField { id: priceFiat - text: Fiat.formattedPrice(payment.paymentAmount, Fiat.price) + value: Fiat.priceFor(payment.paymentAmount) anchors.horizontalCenter: parent.horizontalCenter anchors.top: priceBch.bottom - anchors.topMargin: 12 - color: Qt.darker(palette.text, 1.2) + anchors.topMargin: 18 + focus: true + fontPixelSize: size + property double size: !fiatFollowsSats ? 38 : commentLabel.font.pixelSize + Behavior on size { NumberAnimation { } } + onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false } - Flowee.Label { id: commentLabel text: qsTr("Payment description" + ":") @@ -100,22 +110,40 @@ Page { model: 12 delegate: Item { width: numericKeyboard.width / 3 - height: text.height + 20 + height: textLabel.height + 20 Flowee.Label { - id: text + id: textLabel anchors.centerIn: parent font.pixelSize: 28 text: { if (index < 9) return index + 1; if (index === 9) - return Locale.decimalPoint + return Qt.locale().decimalPoint if (index === 10) return "0" - if (index === 11) + // if (index === 11) return "<-" // TODO use a backspace icon instead. } } + MouseArea { + anchors.fill: parent + onClicked: { + var editor = undefined; + if (priceBch.activeFocus) + editor = priceBch.moneyEditor; + else if (priceFiat.activeFocus) + editor = priceFiat.moneyEditor; + if (index < 9) // these are digits + editor.insertNumber("" + (index + 1)); + else if (index === 9) + editor.addSeparator(); + else if (index === 10) + editor.insertNumber("0"); + else + editor.backspacePressed(); + } + } } } } -- 2.54.0 From eb924dd5a233d78e24799453aa43fa4c47bb4a6d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:16:13 +0100 Subject: [PATCH 0148/1428] Clean up no longer used patch. --- android/docker/scripts/buildQt.sh | 4 ---- android/docker/scripts/qtbase-cmake-macros.patch | 10 ---------- 2 files changed, 14 deletions(-) delete mode 100644 android/docker/scripts/qtbase-cmake-macros.patch diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 40bad25..97098df 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -68,7 +68,3 @@ do rm -rf build/* done -cd /opt/android-qt6/ -patch -p0 < /usr/local/bin/qtbase-cmake-macros.patch - - diff --git a/android/docker/scripts/qtbase-cmake-macros.patch b/android/docker/scripts/qtbase-cmake-macros.patch deleted file mode 100644 index 9ea8bda..0000000 --- a/android/docker/scripts/qtbase-cmake-macros.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- lib/cmake/Qt6Core/Qt6CoreMacros.cmake 2022-11-10 16:08:46.845156002 +0100 -+++ lib/cmake/Qt6Core/Qt6CoreMacros.cmake 2022-11-10 16:09:12.735517356 +0100 -@@ -548,7 +548,6 @@ - cmake_parse_arguments(PARSE_ARGV 1 arg "MANUAL_FINALIZATION" "" "") - - _qt_internal_create_executable("${target}" ${arg_UNPARSED_ARGUMENTS}) -- target_link_libraries("${target}" PRIVATE Qt6::Core) - - if(arg_MANUAL_FINALIZATION) - # Caller says they will call qt6_finalize_target() themselves later -- 2.54.0 From 1aa58cf62cf685f4a1bc5540ba0120a519df5900 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:16:58 +0100 Subject: [PATCH 0149/1428] Make BitcoinvalueField hide trailing zeros --- guis/Flowee/BitcoinValueField.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 21264f9..bbe632f 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -34,7 +34,20 @@ MoneyValueField { id: row spacing: 4 height: parent.height - property string amountString: Pay.amountToString(root.value) + property string amountString: { + var fullString = Pay.amountToString(root.value); + // for editing we should remove the trailing zeros + // but leave the separator if that is what the user typed. So a valid string is "6." + var userTypedString = moneyEditor.enteredString; + let length = userTypedString.length; + for (let i = fullString.length - 1; i > length; i = i - 1) { + if (fullString.charAt(i) !== '0') { + length = i; + break; + } + } + return fullString.substring(0, length); + } LabelWithCursor { id: beginOfValue -- 2.54.0 From d37a24d984d83777c16c6aa9a4c2c9c3743dd262 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:18:44 +0100 Subject: [PATCH 0150/1428] Add ObjectShaker to give feedback on bad input When the user uses the keyboard and that doesn't get accepted, we now shake the widget we are editing. --- BitcoinValue.cpp | 41 ++++++++++++--------- BitcoinValue.h | 6 +-- guis/Flowee/ObjectShaker.qml | 71 ++++++++++++++++++++++++++++++++++++ guis/mobile/PayWithQR.qml | 25 ++++++------- guis/widgets.qrc | 1 + 5 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 guis/Flowee/ObjectShaker.qml diff --git a/BitcoinValue.cpp b/BitcoinValue.cpp index 093674d..a0bb3bb 100644 --- a/BitcoinValue.cpp +++ b/BitcoinValue.cpp @@ -66,14 +66,14 @@ void BitcoinValue::moveRight() setCursorPos(m_cursorPos + 1); } -void BitcoinValue::insertNumber(QChar number) +bool BitcoinValue::insertNumber(QChar number) { if (!number.isNumber()) throw std::runtime_error("Only numbers can be inserted in insertNumber"); int pos = m_typedNumber.indexOf('.'); const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; if (pos > -1 && m_cursorPos > pos && m_typedNumber.size() - pos - unitConfigDecimals > 0) - return; + return false; int cursorPos = m_cursorPos; m_typedNumber.insert(cursorPos, number); while (((pos < 0 && m_typedNumber.size() > 1) || pos > 1) && m_typedNumber.startsWith('0')) { @@ -84,21 +84,23 @@ void BitcoinValue::insertNumber(QChar number) setStringValue(m_typedNumber); setCursorPos(cursorPos + 1); emit enteredStringChanged(); + return true; } -void BitcoinValue::addSeparator() +bool BitcoinValue::addSeparator() { - if (m_typedNumber.indexOf('.') == -1) { - m_typedNumber.insert(m_cursorPos, '.'); - int movedPlaces = 1; - if (m_typedNumber.size() == 1) { - ++movedPlaces; - m_typedNumber = "0."; - } - setCursorPos(m_cursorPos + movedPlaces); - setStringValue(m_typedNumber); - emit enteredStringChanged(); + if (m_typedNumber.indexOf('.') != -1) + return false; + m_typedNumber.insert(m_cursorPos, '.'); + int movedPlaces = 1; + if (m_typedNumber.size() == 1) { + ++movedPlaces; + m_typedNumber = "0."; } + setCursorPos(m_cursorPos + movedPlaces); + setStringValue(m_typedNumber); + emit enteredStringChanged(); + return true; } void BitcoinValue::paste() @@ -108,18 +110,21 @@ void BitcoinValue::paste() setEnteredString(clipboard->text().trimmed()); } -void BitcoinValue::backspacePressed() +bool BitcoinValue::backspacePressed() { - int cursorPos = m_cursorPos; + int cursorPosition = m_cursorPos; + if (m_typedNumber.isEmpty()) + return false; if (m_typedNumber.size() <= 1) { m_typedNumber.clear(); - cursorPos = 0; + cursorPosition = 0; } else { - m_typedNumber.remove(--cursorPos, 1); + m_typedNumber.remove(--cursorPosition, 1); } setStringValue(m_typedNumber); emit enteredStringChanged(); - setCursorPos(cursorPos); + setCursorPos(cursorPosition); + return true; } void BitcoinValue::reset() diff --git a/BitcoinValue.h b/BitcoinValue.h index d9147dd..a8a3987 100644 --- a/BitcoinValue.h +++ b/BitcoinValue.h @@ -43,10 +43,10 @@ public: Q_INVOKABLE void moveLeft(); Q_INVOKABLE void moveRight(); - Q_INVOKABLE void insertNumber(QChar number); - Q_INVOKABLE void addSeparator(); + Q_INVOKABLE bool insertNumber(QChar number); + Q_INVOKABLE bool addSeparator(); Q_INVOKABLE void paste(); - Q_INVOKABLE void backspacePressed(); + Q_INVOKABLE bool backspacePressed(); Q_INVOKABLE void reset(); QString enteredString() const; diff --git a/guis/Flowee/ObjectShaker.qml b/guis/Flowee/ObjectShaker.qml new file mode 100644 index 0000000..2443178 --- /dev/null +++ b/guis/Flowee/ObjectShaker.qml @@ -0,0 +1,71 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Item { + id: objectShaker + property Item target: parent + + // center target in its parent. + Component.onCompleted: target.x = (target.parent.width - target.width) / 2; + + function start() { + if (anim.running) + return; + anim.loop = 0; + anim.from = target.x; + anim.to = target.x - 5; + anim.start() + } + // keep target centerd in its parent + Connections { + target: objectShaker.target + function onWidthChanged() { + // we assume it will be centered in its parent + target.x = (target.parent.width - target.width) / 2 + } + } + + NumberAnimation { + id: anim + property int loop: 0 + target: objectShaker.target + property: "x" + duration: 30 + easing.type: Easing.InOutQuad + onFinished: { + if (loop === 0) { + from = to + to = to + 10 + } + else if (loop === 1) { + from = to + to = to - 10 + } + else if (loop === 2) { + from = to + to = to + 5 + } + + if (loop < 3) { + loop = loop + 1 + start(); + } + } + } +} diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 5e40aae..3e78b1f 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -45,24 +45,24 @@ Page { y: 30 value: payment.paymentAmount focus: true - anchors.horizontalCenter: parent.horizontalCenter onValueEdited: payment.paymentAmount = value fontPixelSize: size property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize - Behavior on size { NumberAnimation { } } onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true + Behavior on size { NumberAnimation { } } + Flowee.ObjectShaker { id: bchShaker } } Flowee.FiatValueField { id: priceFiat value: Fiat.priceFor(payment.paymentAmount) - anchors.horizontalCenter: parent.horizontalCenter anchors.top: priceBch.bottom anchors.topMargin: 18 focus: true fontPixelSize: size property double size: !fiatFollowsSats ? 38 : commentLabel.font.pixelSize - Behavior on size { NumberAnimation { } } onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false + Behavior on size { NumberAnimation { } } + Flowee.ObjectShaker { id: fiatShaker } } Flowee.Label { @@ -126,22 +126,21 @@ Page { return "<-" // TODO use a backspace icon instead. } } + MouseArea { anchors.fill: parent onClicked: { - var editor = undefined; - if (priceBch.activeFocus) - editor = priceBch.moneyEditor; - else if (priceFiat.activeFocus) - editor = priceFiat.moneyEditor; + let editor = fiatFollowsSats ? priceBch.moneyEditor : priceFiat.moneyEditor; if (index < 9) // these are digits - editor.insertNumber("" + (index + 1)); + var shake = !editor.insertNumber("" + (index + 1)); else if (index === 9) - editor.addSeparator(); + shake = !editor.addSeparator() else if (index === 10) - editor.insertNumber("0"); + shake = !editor.insertNumber("0"); else - editor.backspacePressed(); + shake = !editor.backspacePressed(); + if (shake) + fiatFollowsSats ? bchShaker.start() : fiatShaker.start(); } } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 88edd38..469bb67 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -25,5 +25,6 @@ Flowee/PasswdDialog.qml Flowee/Label.qml Flowee/LabelWithCursor.qml + Flowee/ObjectShaker.qml -- 2.54.0 From a8c7a6c3aeddfb0f42443da193f4a87ab2a8f2f7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:21:21 +0100 Subject: [PATCH 0151/1428] Minor bugfixes Actually hide the cursor when we are no longer editing. --- guis/Flowee/BitcoinValueField.qml | 3 +-- guis/Flowee/LabelWithCursor.qml | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index bbe632f..8253119 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -67,7 +67,7 @@ MoneyValueField { removeChars = 2 + 1; return fullString.length - removeChars } - cursorPos: root.activeFocus ? root.cursorPos : -1 + cursorPos: root.cursorPos Layout.alignment: Qt.AlignBaseline showCursor: root.activeFocus } @@ -81,7 +81,6 @@ MoneyValueField { textOpacity: text === "000" ? 0.3 : 1 startPos: beginOfValue.startPos + beginOfValue.stringLength stringLength: 3 - // visible: Pay.unitAllowedDecimals === 8 showCursor: root.activeFocus } LabelWithCursor { diff --git a/guis/Flowee/LabelWithCursor.qml b/guis/Flowee/LabelWithCursor.qml index eecffd4..6302fa2 100644 --- a/guis/Flowee/LabelWithCursor.qml +++ b/guis/Flowee/LabelWithCursor.qml @@ -57,6 +57,8 @@ Item { width: 1.3 height: root.height visible: { + if (!showCursor) + return false; var cutPoint = parent.cursorPos - parent.startPos; var lastSegment = parent.fullString.length === parent.startPos + parent.stringLength if (cutPoint < 0) -- 2.54.0 From 094d7e0c2fed9f253a216af976290dd50640f8a4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:23:56 +0100 Subject: [PATCH 0152/1428] Don't expect users to provide one --- android/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 211d321..88c3175 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,7 +8,7 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -copy mirrorlist /etc/pacman.d/ +#copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Suy --noconfirm --noprogressbar \ && pacman -Sy --noconfirm --noprogressbar --needed base-devel \ -- 2.54.0 From b89276a688ce8fbb854104967bce570f510ea628 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 21:30:05 +0100 Subject: [PATCH 0153/1428] Fix language name --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 748417c..4d98477 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -75,7 +75,7 @@ You? Nederland
s
Tom Zander
-
Polska
+
Polski
yantri
" -- 2.54.0 From 4d66fc6cec33f4c6fa46851064ccb57820e5b3a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 11:51:20 +0100 Subject: [PATCH 0154/1428] Fix buildZXing.sh --- android/docker/scripts/buildZXing.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/android/docker/scripts/buildZXing.sh b/android/docker/scripts/buildZXing.sh index 3a33738..cbe9269 100755 --- a/android/docker/scripts/buildZXing.sh +++ b/android/docker/scripts/buildZXing.sh @@ -7,7 +7,7 @@ source /etc/profile cd /usr/local/cache if ! test -f zxing-cpp-v${VERSION}.tar.gz; then - wget https://github.com/zxing-cpp/zxing-cpp/archive/refs/tags/v${VERSION}.tar.gz + wget --quiet -O zxing-cpp-v${VERSION}.tar.gz https://github.com/zxing-cpp/zxing-cpp/archive/refs/tags/v${VERSION}.tar.gz fi cd ~builduser tar xf /usr/local/cache/zxing-cpp-v${VERSION}.tar.gz @@ -24,8 +24,5 @@ cmake -DCMAKE_TOOLCHAIN_FILE=/opt/android-qt6/lib/cmake/Qt6/qt.toolchain.cmake \ -G Ninja \ .. -cd .. ninja install - - -- 2.54.0 From cf54bc2936573146914e26c469a07b3c2c4b7443 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 13:37:18 +0100 Subject: [PATCH 0155/1428] Use more supported tag name. Payment Requests (QR) now are generate with 'label' for the user-text. --- PaymentRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PaymentRequest.cpp b/PaymentRequest.cpp index 3f2cf6e..114aec7 100644 --- a/PaymentRequest.cpp +++ b/PaymentRequest.cpp @@ -307,7 +307,7 @@ QString PaymentRequest::qrCodeString() const rc += "&"; else rc += "?"; - rc += QString("message=%1").arg(m_message); + rc += QString("label=%1").arg(m_message); QUrl url(rc); rc = QString::fromLatin1(url.toEncoded()); } -- 2.54.0 From 406327becbad180130cadedff4ed612a93b1c02a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 14:22:11 +0100 Subject: [PATCH 0156/1428] Finish up basic payment page Next up; broadcast and feedback. --- BitcoinValue.cpp | 13 +++-- Payment.cpp | 6 ++- guis/Flowee/BitcoinValueField.qml | 7 +-- guis/Flowee/MoneyValueField.qml | 25 +++++++++- guis/mobile.qrc | 1 + guis/mobile/PayWithQR.qml | 83 ++++++++++++++++--------------- guis/mobile/SlideToApprove.qml | 76 ++++++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 49 deletions(-) create mode 100644 guis/mobile/SlideToApprove.qml diff --git a/BitcoinValue.cpp b/BitcoinValue.cpp index a0bb3bb..e52ec4c 100644 --- a/BitcoinValue.cpp +++ b/BitcoinValue.cpp @@ -47,10 +47,17 @@ void BitcoinValue::setValue(qint64 value) m_typedNumber = QString::number(m_value / pow(10, unitConfigDecimals), 'f', unitConfigDecimals); if (unitConfigDecimals > 0) { // there is a comma separator in the string // remove trailing zeros - while (m_typedNumber.endsWith('0') || m_typedNumber.endsWith('.')) - m_typedNumber = m_typedNumber.left(m_typedNumber.size() - 1); + bool done = false; + while (!done) { + if (m_typedNumber.endsWith('.')) + done = true; + if (done || m_typedNumber.endsWith('0')) + m_typedNumber = m_typedNumber.left(m_typedNumber.size() - 1); + else + done = true; + } } - m_cursorPos = m_typedNumber.size(); + setCursorPos(m_typedNumber.size()); } } diff --git a/Payment.cpp b/Payment.cpp index 542ca1f..f1a563f 100644 --- a/Payment.cpp +++ b/Payment.cpp @@ -98,7 +98,7 @@ void Payment::setTargetAddress(const QString &address_) for (const auto &item : query.queryItems()) { if (item.first == "amount") { bool ok; - auto amount = item.second.toLongLong(&ok); + auto amount = item.second.toDouble(&ok); if (ok) { out->setPaymentAmount(amount * 1E8); emit amountChanged(); @@ -234,6 +234,8 @@ void Payment::prepare() // This can only be due to fees as we called 'verify' above. m_error = tr("Not enough funds selected for fees"); emit errorChanged(); + m_txPrepared = false; + emit txPreparedChanged(); return; } } @@ -242,6 +244,8 @@ void Payment::prepare() if (funding.outputs.empty()) { // not enough funds. m_error = tr("Not enough funds in wallet to make payment!"); emit errorChanged(); + m_txPrepared = false; + emit txPreparedChanged(); return; } } diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 8253119..0279b70 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -38,11 +38,12 @@ MoneyValueField { var fullString = Pay.amountToString(root.value); // for editing we should remove the trailing zeros // but leave the separator if that is what the user typed. So a valid string is "6." - var userTypedString = moneyEditor.enteredString; + var userTypedString = money.enteredString; let length = userTypedString.length; for (let i = fullString.length - 1; i > length; i = i - 1) { - if (fullString.charAt(i) !== '0') { - length = i; + let k = fullString.charAt(i); + if (k !== '0' && k !== '.' && k !== ',') { + length = i + 1; break; } } diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index 5ad7c11..5ffe489 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -24,10 +24,33 @@ FocusScope { activeFocusOnTab: true property alias value: privValue.value property alias cursorPos: privValue.cursorPos - property alias moneyEditor: privValue property alias maxFractionalDigits: privValue.maxFractionalDigits signal valueEdited; + // provide nested access to the BitcoinValue + // this allows us to wrap methods + property Item money: Item { + property alias enteredString: privValue.enteredString + function insertNumber(character) { + let rc = privValue.insertNumber(character); + if (rc) + root.valueEdited(); + return rc; + } + function addSeparator() { + let rc = privValue.addSeparator(); + if (rc) + root.valueEdited(); + return rc; + } + function backspacePressed() { + let rc = privValue.backspacePressed(); + if (rc) + root.valueEdited(); + return rc; + } + } + BitcoinValue { id: privValue } diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 89e6bea..5d0e46c 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -29,5 +29,6 @@ mobile/SendTransactionsTab.qml mobile/QRScannerOverlay.qml mobile/PayWithQR.qml + mobile/SlideToApprove.qml diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 3e78b1f..5755915 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -29,10 +29,20 @@ Page { id: scanner scanType: QRScanner.PaymentDetails Component.onCompleted: scanner.start(); - onFinished: payment.targetAddress = scanResult + onFinished: { + payment.targetAddress = scanResult + if (payment.isValid) + payment.prepare(); + } } Payment { id: payment + account: portfolio.current + onIsValidChanged: if (isValid) prepare() + + // easier testing values (for when you don't have a camera) + // paymentAmount: 1000000 + // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" } } @@ -45,12 +55,13 @@ Page { y: 30 value: payment.paymentAmount focus: true - onValueEdited: payment.paymentAmount = value fontPixelSize: size property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true Behavior on size { NumberAnimation { } } Flowee.ObjectShaker { id: bchShaker } + + onValueEdited: payment.paymentAmount = value } Flowee.FiatValueField { id: priceFiat @@ -69,7 +80,6 @@ Page { id: commentLabel text: qsTr("Payment description" + ":") visible: userComment.text !== "" - color: palette.highlight anchors.top: priceFiat.bottom anchors.topMargin: 30 @@ -78,10 +88,21 @@ Page { id: userComment text: payment.userComment visible: text !== "" + color: palette.highlight font.italic: true anchors.top: commentLabel.bottom anchors.topMargin: 5 } + Flowee.Label { + id: errorLabel + text: payment.error + visible: payment.isValid + color: Pay.useDarkSkin ? "#cc1818" : "#6a0c0c" + anchors.top: userComment.bottom + wrapMode: Text.Wrap + anchors.topMargin: 20 + width: parent.width + } Flowee.Label { id: currentWalletLabel @@ -105,6 +126,7 @@ Page { Flow { id: numericKeyboard anchors.bottom: slideToApprove.top + anchors.bottomMargin: 15 width: parent.width Repeater { model: 12 @@ -130,7 +152,7 @@ Page { MouseArea { anchors.fill: parent onClicked: { - let editor = fiatFollowsSats ? priceBch.moneyEditor : priceFiat.moneyEditor; + let editor = fiatFollowsSats ? priceBch.money : priceFiat.money; if (index < 9) // these are digits var shake = !editor.insertNumber("" + (index + 1)); else if (index === 9) @@ -146,47 +168,26 @@ Page { } } } - Item { + SlideToApprove { id: slideToApprove - width: parent.width - height: 60 anchors.bottom: parent.bottom anchors.bottomMargin: 10 - Rectangle { - x: 30 - width: parent.height - height: width - radius: width / 2 - color: root.palette.window + width: parent.width + enabled: payment.isValid && payment.txPrepared + onActivated: { + payment.prepare() + broadcastPage.x = 0 + root.headerText = qsTr("Sending out Payment") } - Rectangle { - x: parent.width - width - 30 - width: parent.height - height: width - radius: width / 2 - color: root.palette.window - } - Rectangle { - x: 30 + parent.height / 2 - width: parent.width - 30 - 30 - parent.height - height: parent.height - color: root.palette.window - } - Flowee.Label { - text: qsTr("SLIDE TO SEND") - anchors.centerIn: parent - } - - Rectangle { - width: parent.height - 5 - height: width - radius: width / 2 - // opacity: 0.8 - x: 35 - y: 2.5 - color: Pay.useDarkSkin ? mainWindow.floweeGreen : mainWindow.floweeBlue - } - } + Rectangle { + id: broadcastPage + width: parent.width + height: parent.height + x: 0 - width - 10 + color: root.palette.base + + Behavior on x { NumberAnimation {} } + } } diff --git a/guis/mobile/SlideToApprove.qml b/guis/mobile/SlideToApprove.qml new file mode 100644 index 0000000..24b3a1f --- /dev/null +++ b/guis/mobile/SlideToApprove.qml @@ -0,0 +1,76 @@ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: root + signal activated; + + height: 60 + Rectangle { + x: 30 + width: parent.height + height: width + radius: width / 2 + color: root.palette.window + } + Rectangle { + x: parent.width - width - 30 + width: parent.height + height: width + radius: width / 2 + color: root.palette.window + } + Rectangle { + x: 30 + parent.height / 2 + width: parent.width - 30 - 30 - parent.height + height: parent.height + color: root.palette.window + } + Flowee.Label { + id: textLabel + text: qsTr("SLIDE TO SEND") + anchors.centerIn: parent + } + + Rectangle { + id: dragCircle + width: parent.height - 5 + height: width + radius: width / 2 + x: 35 + y: 2.5 + color: { + if (root.enabled) + return Pay.useDarkSkin ? mainWindow.floweeGreen : mainWindow.floweeBlue; + return Qt.darker(textLabel.palette.button, 1.2); + } + property bool finished: false + onXChanged: { + if (x >= root.width - 70) { + finished = true; + root.activated(); + } + } + + DragHandler { + id: dragHandler + yAxis.enabled: false + xAxis.enabled: !dragCircle.finished + xAxis.minimum: 35 + xAxis.maximum: root.width - 70 + acceptedButtons: Qt.LeftButton + cursorShape: Qt.DragMoveCursor + onActiveChanged: { + // should the user abort the swipe, move it back + if (!active && !dragCircle.finished) + parent.x = 35 + } + } + Behavior on x { + NumberAnimation { + id: revertAnim + easing.type: Easing.OutElastic + } + } + } +} -- 2.54.0 From 41d332b6974250f6bc07a5232937d777f1c5e85d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 14:36:51 +0100 Subject: [PATCH 0157/1428] Make camera screen acceptable and abortable The camera scanning screen now isn't looking like a total disaster anymore and I even put a close button on it to allow closing it without a scan. --- CameraController.cpp | 5 +++ CameraController.h | 2 + guis/mobile/PayWithQR.qml | 12 ++++-- guis/mobile/QRScannerOverlay.qml | 63 ++++++++++++++++++++++++++++---- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/CameraController.cpp b/CameraController.cpp index 8a85fe8..77cc65d 100644 --- a/CameraController.cpp +++ b/CameraController.cpp @@ -457,6 +457,11 @@ void CameraController::abortRequest(QRScanner *request) } } +void CameraController::abort() +{ + abortRequest(d->scanRequest); +} + void CameraController::setCamera(QObject *object) { if (object == d->camera) diff --git a/CameraController.h b/CameraController.h index 1eacdab..c0ec96d 100644 --- a/CameraController.h +++ b/CameraController.h @@ -52,6 +52,8 @@ public: void startRequest(QRScanner *request); void abortRequest(QRScanner *request); + Q_INVOKABLE void abort(); + void setCamera(QObject *object); QObject *camera() const; diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 5755915..47489db 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -30,9 +30,15 @@ Page { scanType: QRScanner.PaymentDetails Component.onCompleted: scanner.start(); onFinished: { - payment.targetAddress = scanResult - if (payment.isValid) - payment.prepare(); + var rc = scanResult + if (rc === "") { // scanning failed + thePile.pop(); + } + else { + payment.targetAddress = rc + if (payment.isValid) + payment.prepare(); + } } } Payment { diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 6cfd468..49976c1 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -1,6 +1,23 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import QtQuick import QtQuick.Controls as QQC2 -import QtQuick.Layouts +// import QtQuick.Layouts import QtMultimedia import "../Flowee" as Flowee @@ -18,7 +35,7 @@ Item { Component { id: videoFeedPanel Rectangle { - color: "red" + color: mainWindow.palette.window anchors.left: parent.left anchors.right: parent.right height: 300 @@ -30,18 +47,16 @@ Item { Connections { target: CameraController function onCameraActiveChanged() { - console.log("CameraActive now: " + CameraController.cameraActive) if (CameraController.cameraActive) { camera.start(); } else { - // camera.stop(); + // camera.stop(); // stopping the camera on Qt641 tends to not allow us to restart it } } } - Camera { + Camera { id: camera - // active: CameraController.cameraActive } CaptureSession { camera: camera @@ -51,9 +66,41 @@ Item { id: videoOutput visible: CameraController.cameraActive fillMode: VideoOutput.Stretch - // TODO use width/height from CameraController width: parent.width - height: 400 + height: parent.height + } + Rectangle { + opacity: 0.3 + color: "black" + width: parent.width; + height: parent.height / 4 + } + Rectangle { + opacity: 0.3 + color: "black" + width: parent.width / 5 + height: parent.height + } + Rectangle { + opacity: 0.3 + color: "black" + width: parent.width; + height: parent.height / 4 + anchors.bottom: parent.bottom + } + Rectangle { + opacity: 0.3 + color: "black" + width: parent.width / 5 + height: parent.height + anchors.right: parent.right + } + + Flowee.CloseIcon { + anchors.right: parent.right + anchors.rightMargin: 10 + y: 10 + onClicked: CameraController.abort(); } } } -- 2.54.0 From 91ae779f9181c4f109afd1aace2e3c6d2a182115 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 Dec 2022 18:30:02 +0100 Subject: [PATCH 0158/1428] Reuse BroadcastFeedback on Mobile This moves the broadcastfeedback part of the desktop to its own (now shared) widget, and with minor changes reuses it on the mobile side. --- guis/Flowee/BitcoinValueField.qml | 2 +- guis/Flowee/BroadcastFeedback.qml | 212 +++++++++++++++++++++++++++ guis/Flowee/ImageButton.qml | 76 ++++++++++ guis/Flowee/TextField.qml | 27 ++-- guis/desktop.qrc | 1 - guis/desktop/SendTransactionPane.qml | 195 +----------------------- guis/desktop/images/edit-copy.svg | 13 -- guis/images/edit-copy.svg | 21 +++ guis/images/internet.svg | 75 ++++++++++ guis/mobile/PayWithQR.qml | 23 ++- guis/mobile/SlideToApprove.qml | 2 +- guis/widgets.qrc | 4 + 12 files changed, 416 insertions(+), 235 deletions(-) create mode 100644 guis/Flowee/BroadcastFeedback.qml create mode 100644 guis/Flowee/ImageButton.qml delete mode 100644 guis/desktop/images/edit-copy.svg create mode 100644 guis/images/edit-copy.svg create mode 100644 guis/images/internet.svg diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 0279b70..51944ee 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -47,7 +47,7 @@ MoneyValueField { break; } } - return fullString.substring(0, length); + return fullString.substring(0, Math.max(1, length)); } LabelWithCursor { diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml new file mode 100644 index 0000000..749b0f7 --- /dev/null +++ b/guis/Flowee/BroadcastFeedback.qml @@ -0,0 +1,212 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import QtQuick.Shapes +import "../Flowee" as Flowee +import "../ControlColors.js" as ControlColors +import Flowee.org.pay + + +// screen to show the result of a Payment broadcast. +// Please notice you need to have a 'payment' object +// in the parents namespace. +// Also, this screen won't do anything until you actually +// call broadcast on the payment. +QQC2.Control { + id: root + anchors.fill: parent + + signal closeButtonPressed; + property string status: "" + + states: [ + State { + name: "notStarted" + when: payment.broadcastStatus === Payment.NotStarted + }, + State { + name: "preparing" + when: payment.broadcastStatus === Payment.TxOffered + PropertyChanges { target: background; + opacity: 1 + y: 0 + } + PropertyChanges { target: progressCircle; sweepAngle: 90 } + StateChangeScript { script: ControlColors.applyLightSkin(root) } + }, + State { + name: "sent1" // sent to only one peer + extend: "preparing" + when: payment.broadcastStatus === Payment.TxSent1 + PropertyChanges { target: progressCircle; sweepAngle: 150 } + PropertyChanges { target: root; status: qsTr("Sending Payment") } + }, + State { + name: "waiting" // waiting for possible rejection. + when: payment.broadcastStatus === Payment.TxWaiting + extend: "preparing" + PropertyChanges { target: progressCircle; sweepAngle: 320 } + }, + State { + name: "success" // no reject, great success + when: payment.broadcastStatus === Payment.TxBroadcastSuccess + extend: "preparing" + PropertyChanges { target: progressCircle + sweepAngle: 320 + startAngle: -20 + } + PropertyChanges { target: checkShape; opacity: 1 } + PropertyChanges { target: root; status: qsTr("Payment Sent") } + }, + State { + name: "rejected" // a peer didn't like our tx + when: payment.broadcastStatus === Payment.TxRejected + extend: "preparing" + StateChangeScript { script: ControlColors.applyDarkSkin(root) } + PropertyChanges { target: background; color: "#7f0000" } + PropertyChanges { target: circleShape; opacity: 0 } + PropertyChanges { target: root; status: qsTr("Transaction rejected by network") } + } + ] + Rectangle { + id: background + width: parent.width + height: parent.height + opacity: 0 + visible: opacity > 0 + color: mainWindow.floweeGreen + y: height + 2 + + // The 'progress' icon. + Shape { + id: circleShape + anchors.horizontalCenter: parent.horizontalCenter + y: 25 + anchors.top: title.bottom + anchors.topMargin: 10 + width: 160 + height: width + smooth: true + ShapePath { + strokeWidth: 20 + strokeColor: "#dedede" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 100; startY: 10 + + PathAngleArc { + id: progressCircle + centerX: 80 + centerY: 80 + radiusX: 70; radiusY: 70 + startAngle: -80 + sweepAngle: 0 + Behavior on sweepAngle { NumberAnimation { duration: 2500 } } + Behavior on startAngle { NumberAnimation { } } + } + } + Behavior on opacity { NumberAnimation { } } + } + Shape { + id: checkShape + anchors.fill: circleShape + smooth: true + opacity: 0 + ShapePath { + id: checkPath + strokeWidth: 16 + strokeColor: "green" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 52; startY: 80 + PathLine { x: 76; y: 110 } + PathLine { x: 125; y: 47 } + } + } + + Label { + id: fiatAmount + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: circleShape.bottom + anchors.topMargin: 10 + color: "black" + font.pixelSize: 38 + text: Fiat.formattedPrice(payment.effectiveFiatAmount) + visible: Fiat.price !== 0 + } + BitcoinAmountLabel { + id: cryptoAmount + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: fiatAmount.bottom + anchors.topMargin: 20 + fontPixelSize: 28 + value: payment.effectiveBchAmount + colorize: false + showFiat: false + } + + RowLayout { + id: txIdFeedback + spacing: 30 + anchors.top: cryptoAmount.bottom + visible: root.state === "waiting" || root.state === "success" + anchors.topMargin: 10 + anchors.horizontalCenter: parent.horizontalCenter + ImageButton { + source: "qrc:/edit-copy.svg" + onClicked: Pay.copyToClipboard(payment.txid); + responseText: qsTr("Copied TXID to clipboard") + } + ImageButton { + source: "qrc:/internet.svg" + onClicked: Pay.openInExplorer(payment.txid); + responseText: qsTr("Opening Website") + } + } + TextField { + id: transactionComment + anchors.top: txIdFeedback.bottom + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(400, parent.width - 40); + onTextChanged: payment.userComment = text + placeholderText: qsTr("Add a personal note") + placeholderTextColor: "#3e3e3e" + } + + Button { + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.right: parent.right + anchors.rightMargin: 20 + text: qsTr("Close") + onClicked: { + payment.reset() + transactionComment.text = "" + root.closeButtonPressed(); + } + } + + Behavior on opacity { NumberAnimation { } } + Behavior on y { NumberAnimation { } } + Behavior on color { ColorAnimation { } } + } +} diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml new file mode 100644 index 0000000..2791223 --- /dev/null +++ b/guis/Flowee/ImageButton.qml @@ -0,0 +1,76 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Controls as QQC2 + +QQC2.Control { + width: 42 + height: 42 + + signal clicked; + property alias source: imageIcon.source; + property alias responseText: comment.text; + + Rectangle { + id: highlight + anchors.fill: parent + color: palette.button + visible: mouseArea.containsMouse + radius: 6 + } + Image { + id: imageIcon + anchors.fill: parent + anchors.margins: 4 + smooth: true + } + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + parent.clicked() + if (comment.text !== "") + feedbackPopup.visible = true + } + } + + Rectangle { + id: feedbackPopup + radius: 6 + color: palette.window + border.width: 2 + border.color: palette.highlight + visible: false + width: comment.width + 12 + height: comment.height + 12 + anchors.bottom: parent.top + anchors.horizontalCenter: parent.horizontalCenter + + Timer { + interval: 3000 + running: parent.visible + onTriggered: parent.visible = false + } + Label { + id: comment + anchors.centerIn: parent + } + } +} diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 82ec9a4..b7889b4 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -29,18 +29,19 @@ QQC2.TextField { color: palette.text background: Rectangle { - implicitHeight: root.contentHeight + 2 - implicitWidth: 140 - color: { - if (root.enabled) - return root.palette.base; - return "#00000000"; - } - border.color: { - if (root.enabled) - return root.activeFocus ? root.palette.highlight : root.palette.button - return "transparant"; - } - border.width: 0.8 + implicitHeight: root.contentHeight + 2 + implicitWidth: 140 + radius: 3 + color: { + if (root.enabled) + return root.palette.base; + return "#00000000"; + } + border.color: { + if (root.enabled) + return root.activeFocus ? root.palette.highlight : root.palette.button + return "transparant"; + } + border.width: 0.8 } } diff --git a/guis/desktop.qrc b/guis/desktop.qrc index a314f3c..cb4327a 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -17,7 +17,6 @@ desktop/images/eye-open-light.png desktop/images/eye-open.png desktop/images/edit-delete.svg - desktop/images/edit-copy.svg desktop/images/lock-light.svg desktop/images/lock-dark.svg desktop/defaults.ini diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index e6b6cc0..e3458d8 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -246,201 +246,8 @@ Item { id: listViewKeyHandler } - Control { - id: broadcastFeedback + Flowee.BroadcastFeedback { anchors.fill: parent - states: [ - State { - name: "notStarted" - when: payment.broadcastStatus === Payment.NotStarted - }, - State { - name: "preparing" - when: payment.broadcastStatus === Payment.TxOffered - PropertyChanges { target: background; - opacity: 1 - y: 0 - } - PropertyChanges { target: progressCircle; sweepAngle: 90 } - StateChangeScript { script: ControlColors.applyLightSkin(broadcastFeedback) } - }, - State { - name: "sent1" // sent to only one peer - extend: "preparing" - when: payment.broadcastStatus === Payment.TxSent1 - PropertyChanges { target: progressCircle; sweepAngle: 150 } - }, - State { - name: "waiting" // waiting for possible rejection. - when: payment.broadcastStatus === Payment.TxWaiting - extend: "preparing" - PropertyChanges { target: progressCircle; sweepAngle: 320 } - }, - State { - name: "success" // no reject, great success - when: payment.broadcastStatus === Payment.TxBroadcastSuccess - extend: "preparing" - PropertyChanges { target: progressCircle - sweepAngle: 320 - startAngle: -20 - } - PropertyChanges { target: checkShape; opacity: 1 } - }, - State { - name: "rejected" // a peer didn't like our tx - when: payment.broadcastStatus === Payment.TxRejected - extend: "preparing" - StateChangeScript { script: ControlColors.applyDarkSkin(broadcastFeedback) } - PropertyChanges { target: background; color: "#7f0000" } - PropertyChanges { target: circleShape; opacity: 0 } - PropertyChanges { - target: txidFeedbackLabel - text: qsTr("Transaction rejected by network") - } - } - ] - Rectangle { - id: background - width: parent.width - height: parent.height - opacity: 0 - visible: opacity != 0 - color: mainWindow.floweeGreen - y: height + 2 - - Label { - id: title - anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 15 - y: 25 - text: qsTr("Payment Sent") - } - - // The 'progress' icon. - Shape { - id: circleShape - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: title.bottom - anchors.topMargin: 10 - width: 160 - height: width - smooth: true - ShapePath { - strokeWidth: 20 - strokeColor: "#dedede" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 100; startY: 10 - - PathAngleArc { - id: progressCircle - centerX: 80 - centerY: 80 - radiusX: 70; radiusY: 70 - startAngle: -80 - sweepAngle: 0 - Behavior on sweepAngle { NumberAnimation { duration: 2500 } } - Behavior on startAngle { NumberAnimation { } } - } - } - Behavior on opacity { NumberAnimation { } } - } - Shape { - id: checkShape - anchors.fill: circleShape - smooth: true - opacity: 0 - ShapePath { - id: checkPath - strokeWidth: 16 - strokeColor: "green" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 52; startY: 80 - PathLine { x: 76; y: 110 } - PathLine { x: 125; y: 47 } - } - } - - Label { - id: fiatAmount - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: circleShape.bottom - anchors.topMargin: 10 - font.pixelSize: 24 - text: Fiat.formattedPrice(payment.effectiveFiatAmount) - visible: Fiat.price !== 0 - } - Flowee.BitcoinAmountLabel { - id: cryptoAmount - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: fiatAmount.bottom - anchors.topMargin: 20 - fontPixelSize: 28 - value: payment.effectiveBchAmount - colorize: false - showFiat: false - } - Flowee.LabelWithClipboard { - id: txidFeedbackLabel - anchors.top: cryptoAmount.bottom - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - menuText: qsTr("Copy transaction-ID") - clipboardText: payment.txid - width: parent.width - horizontalAlignment: Qt.AlignHCenter - text: qsTr("Your payment can be found by its identifyer: %1").arg(payment.txid) - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - } - - RowLayout { - id: txIdFeedback - anchors.top: txidFeedbackLabel.bottom - anchors.topMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - ToolButton { - icon.source: "qrc:/edit-copy.svg" - onClicked: Pay.copyToClipboard(payment.txid); - text: qsTr("Copy") - } - ToolButton { - onClicked: Pay.openInExplorer(payment.txid); - text: qsTr("Internet") - } - } - - Label { - anchors.verticalCenter: transactionComment.verticalCenter - anchors.right: transactionComment.left - anchors.rightMargin: 10 - text: qsTr("Comment") + ":" - } - Flowee.TextField { - id: transactionComment - anchors.top: txIdFeedback.bottom - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - width: 400 - onTextChanged: payment.userComment = text - } - - Flowee.Button { - anchors.top: transactionComment.bottom - anchors.topMargin: 10 - anchors.right: parent.right - anchors.rightMargin: 20 - text: qsTr("Close") - onClicked: { - payment.reset() - transactionComment.text = "" - } - } - - Behavior on opacity { NumberAnimation { } } - Behavior on y { NumberAnimation { } } - Behavior on color { ColorAnimation { } } - } } // ============= Payment components =============== diff --git a/guis/desktop/images/edit-copy.svg b/guis/desktop/images/edit-copy.svg deleted file mode 100644 index 47aa337..0000000 --- a/guis/desktop/images/edit-copy.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/guis/images/edit-copy.svg b/guis/images/edit-copy.svg new file mode 100644 index 0000000..df9b085 --- /dev/null +++ b/guis/images/edit-copy.svg @@ -0,0 +1,21 @@ + + + + + + + diff --git a/guis/images/internet.svg b/guis/images/internet.svg new file mode 100644 index 0000000..97dcbbf --- /dev/null +++ b/guis/images/internet.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 47489db..901cab8 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -44,6 +44,7 @@ Page { Payment { id: payment account: portfolio.current + fiatPrice: Fiat.price onIsValidChanged: if (isValid) prepare() // easier testing values (for when you don't have a camera) @@ -103,7 +104,7 @@ Page { id: errorLabel text: payment.error visible: payment.isValid - color: Pay.useDarkSkin ? "#cc1818" : "#6a0c0c" + color: Pay.useDarkSkin ? "#cc5454" : "#6a0c0c" anchors.top: userComment.bottom wrapMode: Text.Wrap anchors.topMargin: 20 @@ -180,20 +181,18 @@ Page { anchors.bottomMargin: 10 width: parent.width enabled: payment.isValid && payment.txPrepared - onActivated: { - payment.prepare() - broadcastPage.x = 0 - root.headerText = qsTr("Sending out Payment") - } + onActivated: payment.broadcast() } - Rectangle { + Flowee.BroadcastFeedback { id: broadcastPage - width: parent.width - height: parent.height - x: 0 - width - 10 - color: root.palette.base + anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. + anchors.rightMargin: -10 + onStatusChanged: { + if (status !== "") + root.headerText = status; + } - Behavior on x { NumberAnimation {} } + onCloseButtonPressed: thePile.pop(); } } diff --git a/guis/mobile/SlideToApprove.qml b/guis/mobile/SlideToApprove.qml index 24b3a1f..72512e3 100644 --- a/guis/mobile/SlideToApprove.qml +++ b/guis/mobile/SlideToApprove.qml @@ -46,7 +46,7 @@ Item { } property bool finished: false onXChanged: { - if (x >= root.width - 70) { + if (x >= root.width - 80) { finished = true; root.activated(); } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 469bb67..ab38b4a 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -1,5 +1,7 @@ + images/edit-copy.svg + images/internet.svg Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml Flowee/Button.qml @@ -26,5 +28,7 @@ Flowee/Label.qml Flowee/LabelWithCursor.qml Flowee/ObjectShaker.qml + Flowee/BroadcastFeedback.qml + Flowee/ImageButton.qml -- 2.54.0 From 73a44e31c5ff4718b901682488abd1fa780d93ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 11:34:10 +0100 Subject: [PATCH 0159/1428] Add new icons For the send and receive tabs. Half an hour with Inkscape, I'm a little proud of these two icons. Created them myself. --- guis/mobile.qrc | 3 + guis/mobile/MainView.qml | 5 +- guis/mobile/MainViewBase.qml | 1 + guis/mobile/ReceiveScreen.qml | 25 +++++ guis/mobile/SendTransactionsTab.qml | 2 +- guis/mobile/images/receive.svg | 132 +++++++++++++++++++++++++++ guis/mobile/images/sending-light.svg | 13 +++ guis/mobile/images/sending.svg | 13 +++ 8 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 guis/mobile/ReceiveScreen.qml create mode 100644 guis/mobile/images/receive.svg create mode 100644 guis/mobile/images/sending-light.svg create mode 100644 guis/mobile/images/sending.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 5d0e46c..6ded026 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -11,6 +11,9 @@ mobile/images/smallArrow.svg mobile/images/external.svg mobile/images/external-light.svg + mobile/images/sending-light.svg + mobile/images/sending.svg + mobile/images/receive.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 9854c83..fc08f4c 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -25,10 +25,7 @@ MainViewBase { SendTransactionsTab { anchors.fill: parent } - Item { - id: receiveScreen - property string icon: Pay.useDarkSkin ? "qrc:/external-light.svg" : "qrc:/external.svg" - property string title: qsTr("Bla") + ReceiveScreen { anchors.fill: parent } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4195a5e..a4f1d97 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -123,6 +123,7 @@ QQC2.Control { source: stack.children[modelData].icon width: 35 height: 35 + smooth: true y: 12 anchors.horizontalCenter: parent.horizontalCenter } diff --git a/guis/mobile/ReceiveScreen.qml b/guis/mobile/ReceiveScreen.qml new file mode 100644 index 0000000..d10d8e1 --- /dev/null +++ b/guis/mobile/ReceiveScreen.qml @@ -0,0 +1,25 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: receiveScreen + property string icon: "qrc:/receive.svg" + property string title: qsTr("Receive") +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 78cc793..180f209 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -20,7 +20,7 @@ import QtQuick.Layouts FocusScope { id: root - property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string icon: "qrc:/sending" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); property string title: qsTr("Send") ColumnLayout { diff --git a/guis/mobile/images/receive.svg b/guis/mobile/images/receive.svg new file mode 100644 index 0000000..889b646 --- /dev/null +++ b/guis/mobile/images/receive.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guis/mobile/images/sending-light.svg b/guis/mobile/images/sending-light.svg new file mode 100644 index 0000000..07611d0 --- /dev/null +++ b/guis/mobile/images/sending-light.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/guis/mobile/images/sending.svg b/guis/mobile/images/sending.svg new file mode 100644 index 0000000..0e16e82 --- /dev/null +++ b/guis/mobile/images/sending.svg @@ -0,0 +1,13 @@ + + + + + + -- 2.54.0 From 3295910e943acd48607d1507455d3082809990c6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 11:35:00 +0100 Subject: [PATCH 0160/1428] Remove not existing feature from menu. --- guis/mobile/SendTransactionsTab.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 180f209..226581b 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -32,11 +32,12 @@ FocusScope { showPageIcon: true onClicked: thePile.push("PayWithQR.qml") } + /* TextButton { text: qsTr("My Wallets") subtext: qsTr("Move between wallets") showPageIcon: true onClicked: thePile.push("MoveBetweeWallets.qml") - } + } */ } } -- 2.54.0 From 8cf573c9ede5f08b78c1e73408db07bb4e18eabc Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 12:57:45 +0100 Subject: [PATCH 0161/1428] Improve camera controller When no camera is found, the abort() method now properly closes. The startup of some difficult cameras now avoids the completely black screen. Seems to be a timing issue. --- CameraController.cpp | 26 +++++++++++++++++++++----- CameraController.h | 1 + 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CameraController.cpp b/CameraController.cpp index 77cc65d..8fde1d3 100644 --- a/CameraController.cpp +++ b/CameraController.cpp @@ -174,7 +174,7 @@ void CameraControllerPrivate::initCamera() logCritical().nospace() << "Changing camera resolution to " << preferred.resolution().width() << "x" << preferred.resolution().height(); cam->setCameraFormat(preferred); cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. - cam->setWhiteBalanceMode(QCamera::WhiteBalanceShade); // avoid flash + cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash } void CameraControllerPrivate::checkState() @@ -431,7 +431,8 @@ void CameraController::startRequest(QRScanner *request) logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); if (res == QtAndroidPrivate::Authorized) { d->state = Authorized; - d->checkState(); + // move the actual turning on of the camera to the next event. + QTimer::singleShot(10, this, SLOT(checkState())); } else { d->state = Denied; } @@ -454,6 +455,11 @@ void CameraController::abortRequest(QRScanner *request) d->cameraStarted = false; d->lock.unlock(); emit cameraActiveChanged(); + + if (d->m_scanningThread == nullptr) { + // then the above would have no effect; + qrScanFinished(); + } } } @@ -509,9 +515,11 @@ bool CameraController::visible() const void CameraController::qrScanFinished() { - assert(d->m_scanningThread); - logFatal() << " -> " << d->m_scanningThread->text; - auto resultText = d->m_scanningThread->text; + QString resultText; + if (d->m_scanningThread) { + resultText = d->m_scanningThread->text; + logFatal() << " -> " << d->m_scanningThread->text; + } delete d->m_scanningThread; d->m_scanningThread = nullptr; @@ -526,4 +534,12 @@ void CameraController::qrScanFinished() d->scanRequest->finishedScan(resultText); d->scanRequest = nullptr; } + QCamera *cam = qobject_cast(d->camera); + if (cam) + cam->setTorchMode(QCamera::TorchOff); +} + +void CameraController::checkState() +{ + d->checkState(); } diff --git a/CameraController.h b/CameraController.h index c0ec96d..d11171a 100644 --- a/CameraController.h +++ b/CameraController.h @@ -74,6 +74,7 @@ signals: private slots: void qrScanFinished(); + void checkState(); private: CameraControllerPrivate * d; -- 2.54.0 From 3c30934963af4a6881d040cce4ef596667ff5afc Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 13:00:19 +0100 Subject: [PATCH 0162/1428] Provide silly icons and do the receive screen. The 3 icons at the top of the account screen now have 2 with actual icons. They don't look very good, but at least its better than no icons. Also I stole most of the receive screen from desktop and ported it here. Quick and dirty as that screen has been long overdue for a refresh. --- guis/Flowee/ImageButton.qml | 17 +- guis/mobile.qrc | 4 + guis/mobile/AccountHistory.qml | 14 +- guis/mobile/IconButton.qml | 2 +- guis/mobile/MainView.qml | 4 +- guis/mobile/MainViewBase.qml | 3 + guis/mobile/ReceiveScreen.qml | 25 --- guis/mobile/ReceiveTab.qml | 286 +++++++++++++++++++++++++++ guis/mobile/images/qr-code-light.svg | 11 ++ guis/mobile/images/qr-code.svg | 11 ++ 10 files changed, 341 insertions(+), 36 deletions(-) delete mode 100644 guis/mobile/ReceiveScreen.qml create mode 100644 guis/mobile/ReceiveTab.qml create mode 100644 guis/mobile/images/qr-code-light.svg create mode 100644 guis/mobile/images/qr-code.svg diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index 2791223..3fbbf40 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -21,11 +21,12 @@ import QtQuick.Controls as QQC2 QQC2.Control { width: 42 - height: 42 + height: width signal clicked; property alias source: imageIcon.source; property alias responseText: comment.text; + property int iconSize: width Rectangle { id: highlight @@ -36,8 +37,9 @@ QQC2.Control { } Image { id: imageIcon - anchors.fill: parent - anchors.margins: 4 + width: Math.min(parent.width - 8, iconSize) + height: Math.min(parent.height - 8, iconSize) + anchors.centerIn: parent smooth: true } MouseArea { @@ -54,9 +56,9 @@ QQC2.Control { Rectangle { id: feedbackPopup radius: 6 - color: palette.window + color: mainWindow.palette.window border.width: 2 - border.color: palette.highlight + border.color: mainWindow.palette.highlight visible: false width: comment.width + 12 height: comment.height + 12 @@ -73,4 +75,9 @@ QQC2.Control { anchors.centerIn: parent } } + + Label { + id: textArea + + } } diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6ded026..49ab50c 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -14,6 +14,8 @@ mobile/images/sending-light.svg mobile/images/sending.svg mobile/images/receive.svg + mobile/images/qr-code.svg + mobile/images/qr-code-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js @@ -33,5 +35,7 @@ mobile/QRScannerOverlay.qml mobile/PayWithQR.qml mobile/SlideToApprove.qml + mobile/ReceiveTab.qml + mobile/SmallAccountSelector.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index fc7d41b..052cdf7 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -72,18 +72,24 @@ ListView { Row { width: parent.width height: 60 - IconButton { + Flowee.ImageButton { + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); width: parent.width / 3 - text: qsTr("Pay") onClicked: thePile.push("PayWithQR.qml") + iconSize: 40 + height: dummyButton.height } IconButton { + id: dummyButton width: parent.width / 3 text: qsTr("Scheduled") } - IconButton { + Flowee.ImageButton { width: parent.width / 3 - text: qsTr("Receive") + height: dummyButton.height + iconSize: 50 + source: "qrc:/receive.svg" + onClicked: switchToTab(2) // receive tab } } diff --git a/guis/mobile/IconButton.qml b/guis/mobile/IconButton.qml index 42bdccd..7cfe107 100644 --- a/guis/mobile/IconButton.qml +++ b/guis/mobile/IconButton.qml @@ -20,7 +20,7 @@ import "../Flowee" as Flowee Item { id: root - height: 60 + label.height + 10 + height: 60 + label.height width: height signal clicked; property alias text: label.text diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index fc08f4c..a9ada54 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -25,7 +25,9 @@ MainViewBase { SendTransactionsTab { anchors.fill: parent } - ReceiveScreen { + ReceiveTab { anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index a4f1d97..546de40 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -39,6 +39,9 @@ QQC2.Control { } takeFocus(); } + function switchToTab(index) { + currentIndex = index + } // called from main when this page becomes active, as well as when we change tabs function takeFocus() { diff --git a/guis/mobile/ReceiveScreen.qml b/guis/mobile/ReceiveScreen.qml deleted file mode 100644 index d10d8e1..0000000 --- a/guis/mobile/ReceiveScreen.qml +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import QtQuick -import "../Flowee" as Flowee - -Item { - id: receiveScreen - property string icon: "qrc:/receive.svg" - property string title: qsTr("Receive") -} diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml new file mode 100644 index 0000000..a9023bc --- /dev/null +++ b/guis/mobile/ReceiveTab.qml @@ -0,0 +1,286 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Shapes +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay + +FocusScope { + id: receiveTab + property string icon: "qrc:/receive.svg" + property string title: qsTr("Receive") + + focus: true + property QtObject account: portfolio.current + property QtObject request: null + + /* + TZ: I apologize for the design, I made it when I just had learned QML / cpp mixing. + This is a quick port from the desktop one. In general this component needs + a redesign so we avoid lots of null pointer checks and similar logic that really + should be hidden from the UI layer. + */ + + onAccountChanged: { + if (request != null) { + if (request.state === PaymentRequest.Unpaid) + request.switchAccount(portfolio.current); + else + request = null; + } + } + onActiveFocusChanged: { + if (activeFocus && request == null) { + request = account.createPaymentRequest(receiveTab) + } + } + + function reset() { + if (request.saveState !== PaymentRequest.Stored) + request.destroy(); + request = account.createPaymentRequest(receiveTab) + description.text = ""; + // bitcoinValueField.reset(); + } + + Flowee.Label { + id: instructions + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 20 + text: qsTr("Share this QR to receive") + opacity: 0.5 + } + + Image { + id: qrCode + width: height + height: { + var h = parent.height - 220; + return Math.min(h, 256) + } + source: receiveTab.request == null ? "" : "image://qr/" + receiveTab.request.qr + smooth: false + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: instructions.bottom + anchors.topMargin: 20 + opacity: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? 1: 0 + + MouseArea { + anchors.fill: parent + onClicked: { + Pay.copyToClipboard(receiveTab.request.qr) + clipboardFeedback.opacity = 1 + } + } + + Rectangle { + id: clipboardFeedback + opacity: 0 + width: feedbackText.width + 20 + height: feedbackText.height + 20 + radius: 10 + color: Pay.useDarkSkin ? "#333" : "#ddd" + anchors.top: parent.bottom + anchors.topMargin: -13 + anchors.horizontalCenter: parent.horizontalCenter + + Flowee.Label { + id: feedbackText + x: 10 + y: 10 + text: qsTr("Copied to clipboard") + wrapMode: Text.WordWrap + } + + Behavior on opacity { OpacityAnimator {} } + + /// after 10 seconds, remove feedback. + Timer { + interval: 10000 + running: clipboardFeedback.opacity === 1 + onTriggered: clipboardFeedback.opacity = 0 + } + } + + Behavior on opacity { OpacityAnimator {} } + } + + // the "payment received" screen. + Rectangle { + anchors.top: parent.top + anchors.topMargin: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: qrCode.bottom + radius: 10 + gradient: Gradient { + GradientStop { + position: 0.6 + color: { + if (receiveTab.request == null) + return "red" + var state = receiveTab.request.state; + if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) + return receiveTab.palette.base + if (state === PaymentRequest.DoubleSpentSeen) + return "#640e0f" // red + return "#3e8b4e" // in all other cases: green + } + Behavior on color { ColorAnimation {} } + } + GradientStop { + position: 0.1 + color: receiveTab.palette.base + } + } + opacity: receiveTab.request == null ? 0 : (receiveTab.request.state === PaymentRequest.Unpaid ? 0: 1) + + // animating timer to indicate our checking the security of the transaction. + // (i.e. waiting for the double spent proof) + Item { + id: feedback + width: 160 + height: 160 + y: (parent.height - height) / 3 * 2 + visible: receiveTab.request == null || receiveTab.request.state !== PaymentRequest.DoubleSpentSeen + Shape { + id: circleShape + anchors.fill: parent + opacity: progressCircle.sweepAngle === 340 ? 0 : 1 + x: 40 + ShapePath { + strokeWidth: 20 + strokeColor: "#9ea0b0" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 100; startY: 10 + + PathAngleArc { + id: progressCircle + centerX: 80 + centerY: 80 + radiusX: 70; radiusY: 70 + startAngle: -80 + sweepAngle: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? 0: 340 + + Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } + } + } + + Flowee.Label { + anchors.centerIn: parent + text: qsTr("Checking") // checking security + } + Behavior on opacity { OpacityAnimator {} } + } + + Flowee.Label { + color: "green" + text: "✔" + opacity: 1 - circleShape.opacity + font.pixelSize: 130 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + } + } + + Flowee.Label { + id: feedbackLabel + text: { + if (receiveTab.request == null) + return ""; + var s = receiveTab.request.state; + if (s === PaymentRequest.DoubleSpentSeen) + // double-spent-proof received + return qsTr("Transaction high risk") + if (s === PaymentRequest.PaymentSeen) + return qsTr("Payment Seen") + if (s === PaymentRequest.PaymentSeenOk) + return qsTr("Payment Accepted") + if (s === PaymentRequest.Confirmed) + return qsTr("Payment Settled") + return "INTERNAL ERROR"; + } + width: parent.width - 40 + anchors.verticalCenter: feedback.verticalCenter + anchors.left: feedback.visible ? feedback.right : parent.left + anchors.leftMargin: 20 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 20 + } + Flowee.Label { + visible: receiveTab.request == null || receiveTab.request.state === PaymentRequest.DoubleSpentSeen + anchors.top: feedbackLabel.bottom + anchors.right: parent.right + anchors.rightMargin: 10 + width: parent.width - 20 + horizontalAlignment: Qt.AlignRight + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: qsTr("Instant payment failed. Wait for confirmation. (double spent proof received)") + } + + Behavior on opacity { OpacityAnimator {} } + } + + // entry-fields + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: qrCode.bottom + anchors.topMargin: 30 + Flowee.Label { + text: qsTr("Description") + ":" + } + Flowee.TextField { + id: description + Layout.fillWidth: true + enabled: receiveTab.request != null && receiveTab.request.state === PaymentRequest.Unpaid + onTextChanged: receiveTab.request.message = text + focus: true + } + + /* + Flowee.Label { + id: payAmount + text: qsTr("Amount") + ":" + } + RowLayout { + spacing: 10 + Flowee.BitcoinValueField { + id: bitcoinValueField + enabled: receiveTab.request != null && receiveTab.request.state === PaymentRequest.Unpaid + onValueChanged: receiveTab.request.amount = value + } + Flowee.Label { + Layout.alignment: Qt.AlignBaseline + anchors.baselineOffset: bitcoinValueField.baselineOffset + text: Fiat.formattedPrice(bitcoinValueField.value, Fiat.price) + } + } + */ + + Flowee.Button { + Layout.alignment: Qt.AlignRight + text: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") + onClicked: reset(); + } + } + +} diff --git a/guis/mobile/images/qr-code-light.svg b/guis/mobile/images/qr-code-light.svg new file mode 100644 index 0000000..4445f99 --- /dev/null +++ b/guis/mobile/images/qr-code-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/guis/mobile/images/qr-code.svg b/guis/mobile/images/qr-code.svg new file mode 100644 index 0000000..01b7dc7 --- /dev/null +++ b/guis/mobile/images/qr-code.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + -- 2.54.0 From f3a9493e8454f1b999e9675435432e62119d1673 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 13:04:35 +0100 Subject: [PATCH 0163/1428] Remove lib which is no longer present in the docker On rebuilding the library as part of the docker builds, this static lib no longer is created making it easier to use this library. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 308e5fa..51be6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ if (ANDROID) set_target_properties(ZXing::ZXing PROPERTIES IMPORTED_LOCATION "/opt/android-zxing/lib/libZXing.a") set_property(TARGET ZXing::ZXing PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES - "/opt/android-zxing/lib/libZXing.a;/opt/android-zxing/lib/libzueci-static.a") + "/opt/android-zxing/lib/libZXing.a") set (ZXing_FOUND TRUE) else () find_package(ZXing REQUIRED) -- 2.54.0 From 5fba582bbe73d999d5b1112d93373ff687b23ca8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 13:06:21 +0100 Subject: [PATCH 0164/1428] Make SmallAccountSelector reuable. --- guis/mobile/AccountHistory.qml | 149 +------------------------ guis/mobile/SmallAccountSelector.qml | 155 +++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 148 deletions(-) create mode 100644 guis/mobile/SmallAccountSelector.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 052cdf7..8843298 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -93,157 +93,10 @@ ListView { } } - Item { + SmallAccountSelector { id: smallAccountSelector width: parent.width - height: walletSelector.height z: 10 - - property bool open: false - - visible: { - // if there is only an initial, not user-owned wallet, there is nothing to show here. - if (!portfolio.current.isUserOwned) - return false; - if (portfolio.accounts.length === 1) - return false; - return true; - } - - Rectangle { - id: walletSelector - height: currentWalletName.height + 14 - width: parent.width + 5 - color: "#00000000" - border.color: mainWindow.palette.button - border.width: hasMultipleWallets ? 0.7 : 0 - x: -5 - - property bool hasMultipleWallets: portfolio.accounts.length > 1 - - Flowee.Label { - x: 5 - id: currentWalletName - text: portfolio.current.name - anchors.verticalCenter: parent.verticalCenter - } - Flowee.ArrowPoint { - id: arrowPoint - visible: parent.hasMultipleWallets - color: mainWindow.palette.text - anchors.right: parent.right - anchors.rightMargin: 10 - rotation: 90 - anchors.verticalCenter: parent.verticalCenter - } - MouseArea { - anchors.fill: parent - enabled: parent.hasMultipleWallets - onClicked: { - smallAccountSelector.open = !smallAccountSelector.open - // move focus so the 'back' button will close it. - extraWallets.forceActiveFocus(); - } - } - } - - Rectangle { - id: extraWallets - width: parent.width - y: walletSelector.height - height: parent.open ? columnLayout.height + 20 : 0 - color: mainWindow.palette.base - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - clip: true - visible: height > 0 - focus: true - ColumnLayout { - y: 10 - id: columnLayout - width: parent.width - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - - Repeater { // portfolio holds all our accounts - width: parent.width - model: portfolio.accounts - delegate: Item { - width: extraWallets.width - height: accountName.height * 2 + 12 - Rectangle { - color: mainWindow.palette.button - anchors.fill: parent - visible: modelData === portfolio.current - } - Flowee.Label { - id: accountName - y: 6 - text: modelData.name - } - Flowee.Label { - id: lastActive - anchors.top: accountName.bottom - text: qsTr("last active") + ": " - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.Label { - anchors.top: lastActive.top - anchors.left: lastActive.right - text: Pay.formatDate(modelData.lastMinedTransaction) - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.BitcoinAmountLabel { - anchors.right: parent.right - anchors.top: accountName.top - showFiat: true - value: modelData.balanceConfirmed + modelData.balanceUnconfirmed - colorize: false - } - MouseArea { - anchors.fill: parent - onClicked: { - portfolio.current = modelData - smallAccountSelector.open = false - } - } - } - } - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - } - - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { - event.accepted = true; - smallAccountSelector.open = false - } - } - } } Flowee.BitcoinAmountLabel { opacity: Pay.hideBalance ? 0.2 : 1 diff --git a/guis/mobile/SmallAccountSelector.qml b/guis/mobile/SmallAccountSelector.qml new file mode 100644 index 0000000..edc4276 --- /dev/null +++ b/guis/mobile/SmallAccountSelector.qml @@ -0,0 +1,155 @@ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +Item { + id: smallAccountSelector + height: walletSelector.height + property bool open: false + + visible: { + // if there is only an initial, not user-owned wallet, there is nothing to show here. + if (!portfolio.current.isUserOwned) + return false; + if (portfolio.accounts.length === 1) + return false; + return true; + } + + Rectangle { + id: walletSelector + height: currentWalletName.height + 14 + width: parent.width + 5 + color: "#00000000" + border.color: mainWindow.palette.button + border.width: hasMultipleWallets ? 0.7 : 0 + x: -5 + + property bool hasMultipleWallets: portfolio.accounts.length > 1 + + Flowee.Label { + x: 5 + id: currentWalletName + text: portfolio.current.name + anchors.verticalCenter: parent.verticalCenter + } + Flowee.ArrowPoint { + id: arrowPoint + visible: parent.hasMultipleWallets + color: mainWindow.palette.text + anchors.right: parent.right + anchors.rightMargin: 10 + rotation: 90 + anchors.verticalCenter: parent.verticalCenter + } + MouseArea { + anchors.fill: parent + enabled: parent.hasMultipleWallets + onClicked: { + smallAccountSelector.open = !smallAccountSelector.open + // move focus so the 'back' button will close it. + extraWallets.forceActiveFocus(); + } + } + } + + Rectangle { + id: extraWallets + width: parent.width + y: walletSelector.height + height: parent.open ? columnLayout.height + 20 : 0 + color: mainWindow.palette.base + Behavior on height { + NumberAnimation { + duration: 200 + } + } + + clip: true + visible: height > 0 + focus: true + ColumnLayout { + y: 10 + id: columnLayout + width: parent.width + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight + } + } + + Repeater { // portfolio holds all our accounts + width: parent.width + model: portfolio.accounts + delegate: Item { + width: extraWallets.width + height: accountName.height * 2 + 12 + Rectangle { + color: mainWindow.palette.button + anchors.fill: parent + visible: modelData === portfolio.current + } + Flowee.Label { + id: accountName + y: 6 + text: modelData.name + } + Flowee.Label { + id: lastActive + anchors.top: accountName.bottom + text: qsTr("last active") + ": " + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.Label { + anchors.top: lastActive.top + anchors.left: lastActive.right + text: Pay.formatDate(modelData.lastMinedTransaction) + font.pointSize: mainWindow.font.pointSize * 0.8 + font.bold: false + } + Flowee.BitcoinAmountLabel { + anchors.right: parent.right + anchors.top: accountName.top + showFiat: true + value: modelData.balanceConfirmed + modelData.balanceUnconfirmed + colorize: false + } + MouseArea { + anchors.fill: parent + onClicked: { + portfolio.current = modelData + smallAccountSelector.open = false + } + } + } + } + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: mainWindow.palette.highlight + } + } + } + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + event.accepted = true; + smallAccountSelector.open = false + } + } + } +} -- 2.54.0 From 94c74de75e29caa9084b9eddb219960cfb1ed35b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 13:06:48 +0100 Subject: [PATCH 0165/1428] Hide wallet name when its the default never used one. --- guis/mobile/PayWithQR.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 901cab8..646289d 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -114,6 +114,7 @@ Page { Flowee.Label { id: currentWalletLabel text: portfolio.current.name + visible: portfolio.current.isUserOwned anchors.baseline: parent.width > currentWalletLabel.width + currentWalletValue.width ? currentWalletValue.baseline : undefined anchors.bottom: parent.width > currentWalletLabel.width + currentWalletValue.width -- 2.54.0 From 21bc7343ce2f7d6a67bcee5ee71dd47e1414bd05 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 13:53:15 +0100 Subject: [PATCH 0166/1428] Swap order to fix screen layout Moving the text line up would allow the virtual keyboard to have more space and likely that the top-level button to get out of the screen stays visible. I.e. users don't have to manually hide the keyboard to continue. --- guis/Flowee/BroadcastFeedback.qml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 749b0f7..cfb1aea 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -163,6 +163,17 @@ QQC2.Control { showFiat: false } + TextField { + id: transactionComment + anchors.top: txIdFeedback.bottom + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(400, parent.width - 40); + onTextChanged: payment.userComment = text + placeholderText: qsTr("Add a personal note") + placeholderTextColor: "#3e3e3e" + } + RowLayout { id: txIdFeedback spacing: 30 @@ -181,17 +192,6 @@ QQC2.Control { responseText: qsTr("Opening Website") } } - TextField { - id: transactionComment - anchors.top: txIdFeedback.bottom - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(400, parent.width - 40); - onTextChanged: payment.userComment = text - placeholderText: qsTr("Add a personal note") - placeholderTextColor: "#3e3e3e" - } - Button { anchors.bottom: parent.bottom anchors.bottomMargin: 30 -- 2.54.0 From e65469c979c3c0a1cfca87ad947ac2ada328f9fd Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Dec 2022 14:23:01 +0100 Subject: [PATCH 0167/1428] Update version Also replace tabs with spaces in the XML --- CMakeLists.txt | 2 +- android/AndroidManifest.xml | 38 ++++++++++++++++++------------------- main.cpp | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51be6eb..3dd17f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ # along with this program. If not, see . cmake_minimum_required(VERSION 3.19) -project(flowee_pay VERSION 0.1 LANGUAGES CXX) +project(flowee_pay VERSION 0.2 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index babf84c..bd8d39a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,32 +1,32 @@ - + + android:anyDensity="true" + android:largeScreens="true" + android:normalScreens="true" + android:smallScreens="true"/> + android:name="org.qtproject.qt.android.bindings.QtApplication" + android:hardwareAccelerated="true" + android:label="Flowee Pay" + android:requestLegacyExternalStorage="true" + android:allowNativeHeapPointerTagging="false" + android:allowBackup="true" + android:fullBackupOnly="false" + android:icon="@drawable/icon"> + android:name="org.qtproject.qt.android.bindings.QtActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:label="Flowee Pay" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true" > diff --git a/main.cpp b/main.cpp index c847d81..10054f5 100644 --- a/main.cpp +++ b/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2022.09.0"); + qapp.setApplicationVersion("2022.12.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From c391b8961145530e1ab30233a32c7fa635b3f7f1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Dec 2022 23:37:25 +0100 Subject: [PATCH 0168/1428] Revert debug --- guis/desktop/AccountDetails.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index e5f0658..a6dd2df 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -184,7 +184,7 @@ Item { Flowee.Button { id: decryptButton anchors.right: parent.right - text: decryptWarning.implicitHeight// qsTr("Open") + text: qsTr("Open") enabled: passwordField.text.length > 3 onClicked: { var rc = root.account.decrypt(passwordField.text); -- 2.54.0 From 163e6156134432d37b3baf3688150592463c6f8b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Dec 2022 00:03:27 +0100 Subject: [PATCH 0169/1428] Move sources into the src subdir Slowly the amount of cpp sources has been growing to the point where its just too much to store in the root of the project. I think they are more happy in a subdir as well, getting an elevated position for themselves. --- CMakeLists.txt | 80 +++---------------- AccountInfo.cpp => src/AccountInfo.cpp | 0 AccountInfo.h => src/AccountInfo.h | 0 AddressInfo.cpp => src/AddressInfo.cpp | 0 AddressInfo.h => src/AddressInfo.h | 0 BitcoinValue.cpp => src/BitcoinValue.cpp | 0 BitcoinValue.h => src/BitcoinValue.h | 0 src/CMakeLists.txt | 74 +++++++++++++++++ .../CameraController.cpp | 0 CameraController.h => src/CameraController.h | 0 FloweePay.cpp => src/FloweePay.cpp | 0 FloweePay.h => src/FloweePay.h | 0 MenuModel.cpp => src/MenuModel.cpp | 0 MenuModel.h => src/MenuModel.h | 0 MetaExtractor.cpp => src/MetaExtractor.cpp | 0 .../NetDataProvider.cpp | 0 NetDataProvider.h => src/NetDataProvider.h | 0 NetPeer.cpp => src/NetPeer.cpp | 0 NetPeer.h => src/NetPeer.h | 0 .../NetworkLogClient.cpp | 0 NetworkLogClient.h => src/NetworkLogClient.h | 0 .../NewWalletConfig.cpp | 0 NewWalletConfig.h => src/NewWalletConfig.h | 0 .../NotificationManager.cpp | 0 .../NotificationManager.h | 0 Payment.cpp => src/Payment.cpp | 0 Payment.h => src/Payment.h | 0 .../PaymentDetailInputs.cpp | 0 .../PaymentDetailInputs_p.h | 0 .../PaymentDetailOutput.cpp | 0 .../PaymentDetailOutput_p.h | 0 PaymentRequest.cpp => src/PaymentRequest.cpp | 0 PaymentRequest.h => src/PaymentRequest.h | 0 Payment_p.h => src/Payment_p.h | 0 .../PortfolioDataProvider.cpp | 0 .../PortfolioDataProvider.h | 0 .../PriceDataProvider.cpp | 0 .../PriceDataProvider.h | 0 .../PriceHistoryDataProvider.cpp | 0 .../PriceHistoryDataProvider.h | 0 QRCreator.cpp => src/QRCreator.cpp | 0 QRCreator.h => src/QRCreator.h | 0 QRScanner.cpp => src/QRScanner.cpp | 0 QRScanner.h => src/QRScanner.h | 0 .../TransactionInfo.cpp | 0 TransactionInfo.h => src/TransactionInfo.h | 0 Wallet.cpp => src/Wallet.cpp | 0 Wallet.h => src/Wallet.h | 0 .../WalletCoinsModel.cpp | 0 WalletCoinsModel.h => src/WalletCoinsModel.h | 0 WalletEnums.cpp => src/WalletEnums.cpp | 0 WalletEnums.h => src/WalletEnums.h | 0 .../WalletHistoryModel.cpp | 0 .../WalletHistoryModel.h | 0 .../WalletSecretsModel.cpp | 0 .../WalletSecretsModel.h | 0 .../Wallet_encryption.cpp | 0 Wallet_p.h => src/Wallet_p.h | 0 .../Wallet_spending.cpp | 0 Wallet_support.cpp => src/Wallet_support.cpp | 0 Wallet_test.cpp => src/Wallet_test.cpp | 0 main.cpp => src/main.cpp | 0 main_utils.cpp => src/main_utils.cpp | 0 .../main_utils_android.cpp | 0 .../qml_path_helper.cpp.in | 0 testing/priceHistory/CMakeLists.txt | 2 +- testing/value/CMakeLists.txt | 5 +- testing/wallet/CMakeLists.txt | 4 +- 68 files changed, 91 insertions(+), 74 deletions(-) rename AccountInfo.cpp => src/AccountInfo.cpp (100%) rename AccountInfo.h => src/AccountInfo.h (100%) rename AddressInfo.cpp => src/AddressInfo.cpp (100%) rename AddressInfo.h => src/AddressInfo.h (100%) rename BitcoinValue.cpp => src/BitcoinValue.cpp (100%) rename BitcoinValue.h => src/BitcoinValue.h (100%) create mode 100644 src/CMakeLists.txt rename CameraController.cpp => src/CameraController.cpp (100%) rename CameraController.h => src/CameraController.h (100%) rename FloweePay.cpp => src/FloweePay.cpp (100%) rename FloweePay.h => src/FloweePay.h (100%) rename MenuModel.cpp => src/MenuModel.cpp (100%) rename MenuModel.h => src/MenuModel.h (100%) rename MetaExtractor.cpp => src/MetaExtractor.cpp (100%) rename NetDataProvider.cpp => src/NetDataProvider.cpp (100%) rename NetDataProvider.h => src/NetDataProvider.h (100%) rename NetPeer.cpp => src/NetPeer.cpp (100%) rename NetPeer.h => src/NetPeer.h (100%) rename NetworkLogClient.cpp => src/NetworkLogClient.cpp (100%) rename NetworkLogClient.h => src/NetworkLogClient.h (100%) rename NewWalletConfig.cpp => src/NewWalletConfig.cpp (100%) rename NewWalletConfig.h => src/NewWalletConfig.h (100%) rename NotificationManager.cpp => src/NotificationManager.cpp (100%) rename NotificationManager.h => src/NotificationManager.h (100%) rename Payment.cpp => src/Payment.cpp (100%) rename Payment.h => src/Payment.h (100%) rename PaymentDetailInputs.cpp => src/PaymentDetailInputs.cpp (100%) rename PaymentDetailInputs_p.h => src/PaymentDetailInputs_p.h (100%) rename PaymentDetailOutput.cpp => src/PaymentDetailOutput.cpp (100%) rename PaymentDetailOutput_p.h => src/PaymentDetailOutput_p.h (100%) rename PaymentRequest.cpp => src/PaymentRequest.cpp (100%) rename PaymentRequest.h => src/PaymentRequest.h (100%) rename Payment_p.h => src/Payment_p.h (100%) rename PortfolioDataProvider.cpp => src/PortfolioDataProvider.cpp (100%) rename PortfolioDataProvider.h => src/PortfolioDataProvider.h (100%) rename PriceDataProvider.cpp => src/PriceDataProvider.cpp (100%) rename PriceDataProvider.h => src/PriceDataProvider.h (100%) rename PriceHistoryDataProvider.cpp => src/PriceHistoryDataProvider.cpp (100%) rename PriceHistoryDataProvider.h => src/PriceHistoryDataProvider.h (100%) rename QRCreator.cpp => src/QRCreator.cpp (100%) rename QRCreator.h => src/QRCreator.h (100%) rename QRScanner.cpp => src/QRScanner.cpp (100%) rename QRScanner.h => src/QRScanner.h (100%) rename TransactionInfo.cpp => src/TransactionInfo.cpp (100%) rename TransactionInfo.h => src/TransactionInfo.h (100%) rename Wallet.cpp => src/Wallet.cpp (100%) rename Wallet.h => src/Wallet.h (100%) rename WalletCoinsModel.cpp => src/WalletCoinsModel.cpp (100%) rename WalletCoinsModel.h => src/WalletCoinsModel.h (100%) rename WalletEnums.cpp => src/WalletEnums.cpp (100%) rename WalletEnums.h => src/WalletEnums.h (100%) rename WalletHistoryModel.cpp => src/WalletHistoryModel.cpp (100%) rename WalletHistoryModel.h => src/WalletHistoryModel.h (100%) rename WalletSecretsModel.cpp => src/WalletSecretsModel.cpp (100%) rename WalletSecretsModel.h => src/WalletSecretsModel.h (100%) rename Wallet_encryption.cpp => src/Wallet_encryption.cpp (100%) rename Wallet_p.h => src/Wallet_p.h (100%) rename Wallet_spending.cpp => src/Wallet_spending.cpp (100%) rename Wallet_support.cpp => src/Wallet_support.cpp (100%) rename Wallet_test.cpp => src/Wallet_test.cpp (100%) rename main.cpp => src/main.cpp (100%) rename main_utils.cpp => src/main_utils.cpp (100%) rename main_utils_android.cpp => src/main_utils_android.cpp (100%) rename qml_path_helper.cpp.in => src/qml_path_helper.cpp.in (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dd17f0..774a7f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,6 @@ cmake_minimum_required(VERSION 3.19) project(flowee_pay VERSION 0.2 LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) @@ -53,12 +52,6 @@ endif() find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) find_package(QREncode REQUIRED) -option(local_qml "Allow local QML loading" OFF) -option(networkLog "Include network-logging client" OFF) - -add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) -# The cmake system name will hold values like Android, Linux or others. - function(download_file url path) if (NOT EXISTS "${path}") get_filename_component(file "${path}" NAME) @@ -75,55 +68,6 @@ function(download_file url path) endif() endfunction() -set (PAY_SOURCES - AccountInfo.cpp - AddressInfo.cpp - BitcoinValue.cpp - FloweePay.cpp - MenuModel.cpp - NetDataProvider.cpp - NetPeer.cpp - NewWalletConfig.cpp - NotificationManager.cpp - Payment.cpp - PaymentRequest.cpp - PaymentDetailOutput.cpp - PaymentDetailInputs.cpp - PortfolioDataProvider.cpp - PriceDataProvider.cpp - PriceHistoryDataProvider.cpp - QRCreator.cpp - TransactionInfo.cpp - Wallet.cpp - WalletCoinsModel.cpp - WalletHistoryModel.cpp - WalletSecretsModel.cpp - Wallet_encryption.cpp - Wallet_support.cpp - Wallet_spending.cpp - WalletEnums.cpp -) - -if (NetworkLogClient) - list(APPEND PAY_SOURCES NetworkLogClient.cpp) -endif() - -if (ANDROID) - list(APPEND PAY_SOURCES main_utils_android.cpp) -else () - list(APPEND PAY_SOURCES main_utils.cpp) -endif () - -add_library(pay_lib STATIC ${PAY_SOURCES}) - -target_link_libraries(pay_lib - flowee_apputils - flowee_utils - flowee_p2p - ${OPENSSL_LIBRARIES} - ${Boost_LIBRARIES} - Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ${QREncode_LIBRARIES}) - ###### Translations if(NOT ANDROID) @@ -156,6 +100,7 @@ endif() ###### Pay executable +include_directories(${CMAKE_SOURCE_DIR}/src) if (NOT ANDROID) set (NOT_ANDROID TRUE) @@ -174,9 +119,9 @@ if (BUILD_DESKTOP_PAY) if (local_qml) set (QML_PATH ${CMAKE_SOURCE_DIR}/guis/) endif() - configure_file(qml_path_helper.cpp.in guis/desktop/qml_path_helper.cpp) + configure_file(src/qml_path_helper.cpp.in guis/desktop/qml_path_helper.cpp) set (SOURCES_PAY - main.cpp + src/main.cpp guis/desktop/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY @@ -216,11 +161,11 @@ if (ANDROID AND BUILD_MOBILE_PAY) ${CMAKE_SOURCE_DIR}/data/bip39-spanish DESTINATION ${ASSETS_DIR}) - configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) + configure_file(src/qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) set (SOURCES_PAY_MOBILE - main.cpp - CameraController.cpp - QRScanner.cpp + src/main.cpp + src/CameraController.cpp + src/QRScanner.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -253,13 +198,13 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) if(local_qml) set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) endif() - configure_file(qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) + configure_file(src/qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) file(COPY translations/mobile-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) set (SOURCES_PAY_MOBILE - main.cpp - CameraController.cpp - QRScanner.cpp + src/main.cpp + src/CameraController.cpp + src/QRScanner.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -284,7 +229,7 @@ option (BUILD_PAY_TOOLS if (BUILD_PAY_TOOLS) add_executable(blockheaders-meta-extractor - MetaExtractor.cpp + src/MetaExtractor.cpp ) target_link_libraries(blockheaders-meta-extractor flowee_p2p @@ -309,6 +254,7 @@ if (EXISTS ${CMAKE_SOURCE_DIR}/data/blockheaders) install(FILES ${CMAKE_SOURCE_DIR}/data/blockheaders DESTINATION share/floweepay) endif() +add_subdirectory(src) if (NOT ANDROID) add_subdirectory(testing) endif() diff --git a/AccountInfo.cpp b/src/AccountInfo.cpp similarity index 100% rename from AccountInfo.cpp rename to src/AccountInfo.cpp diff --git a/AccountInfo.h b/src/AccountInfo.h similarity index 100% rename from AccountInfo.h rename to src/AccountInfo.h diff --git a/AddressInfo.cpp b/src/AddressInfo.cpp similarity index 100% rename from AddressInfo.cpp rename to src/AddressInfo.cpp diff --git a/AddressInfo.h b/src/AddressInfo.h similarity index 100% rename from AddressInfo.h rename to src/AddressInfo.h diff --git a/BitcoinValue.cpp b/src/BitcoinValue.cpp similarity index 100% rename from BitcoinValue.cpp rename to src/BitcoinValue.cpp diff --git a/BitcoinValue.h b/src/BitcoinValue.h similarity index 100% rename from BitcoinValue.h rename to src/BitcoinValue.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e7b7972 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,74 @@ +# This file is part of the Flowee project +# Copyright (C) 2020-2022 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(flowee_lib) + +option(local_qml "Allow local QML loading" OFF) +option(networkLog "Include network-logging client" OFF) +option(NetworkLogClient "Include the network based logging in the executables" OFF) + +add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) +# The cmake system name will hold values like Android, Linux or others. + +set (PAY_SOURCES + AccountInfo.cpp + AddressInfo.cpp + BitcoinValue.cpp + FloweePay.cpp + MenuModel.cpp + NetDataProvider.cpp + NetPeer.cpp + NewWalletConfig.cpp + NotificationManager.cpp + Payment.cpp + PaymentRequest.cpp + PaymentDetailOutput.cpp + PaymentDetailInputs.cpp + PortfolioDataProvider.cpp + PriceDataProvider.cpp + PriceHistoryDataProvider.cpp + QRCreator.cpp + TransactionInfo.cpp + Wallet.cpp + WalletCoinsModel.cpp + WalletHistoryModel.cpp + WalletSecretsModel.cpp + Wallet_encryption.cpp + Wallet_support.cpp + Wallet_spending.cpp + WalletEnums.cpp +) + +if (NetworkLogClient) + list(APPEND PAY_SOURCES NetworkLogClient.cpp) +endif() + +if (ANDROID) + list(APPEND PAY_SOURCES main_utils_android.cpp) +else () + list(APPEND PAY_SOURCES main_utils.cpp) +endif () + +add_library(pay_lib STATIC ${PAY_SOURCES}) + +target_link_libraries(pay_lib + flowee_apputils + flowee_utils + flowee_p2p + ${OPENSSL_LIBRARIES} + ${Boost_LIBRARIES} + Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ${QREncode_LIBRARIES}) + diff --git a/CameraController.cpp b/src/CameraController.cpp similarity index 100% rename from CameraController.cpp rename to src/CameraController.cpp diff --git a/CameraController.h b/src/CameraController.h similarity index 100% rename from CameraController.h rename to src/CameraController.h diff --git a/FloweePay.cpp b/src/FloweePay.cpp similarity index 100% rename from FloweePay.cpp rename to src/FloweePay.cpp diff --git a/FloweePay.h b/src/FloweePay.h similarity index 100% rename from FloweePay.h rename to src/FloweePay.h diff --git a/MenuModel.cpp b/src/MenuModel.cpp similarity index 100% rename from MenuModel.cpp rename to src/MenuModel.cpp diff --git a/MenuModel.h b/src/MenuModel.h similarity index 100% rename from MenuModel.h rename to src/MenuModel.h diff --git a/MetaExtractor.cpp b/src/MetaExtractor.cpp similarity index 100% rename from MetaExtractor.cpp rename to src/MetaExtractor.cpp diff --git a/NetDataProvider.cpp b/src/NetDataProvider.cpp similarity index 100% rename from NetDataProvider.cpp rename to src/NetDataProvider.cpp diff --git a/NetDataProvider.h b/src/NetDataProvider.h similarity index 100% rename from NetDataProvider.h rename to src/NetDataProvider.h diff --git a/NetPeer.cpp b/src/NetPeer.cpp similarity index 100% rename from NetPeer.cpp rename to src/NetPeer.cpp diff --git a/NetPeer.h b/src/NetPeer.h similarity index 100% rename from NetPeer.h rename to src/NetPeer.h diff --git a/NetworkLogClient.cpp b/src/NetworkLogClient.cpp similarity index 100% rename from NetworkLogClient.cpp rename to src/NetworkLogClient.cpp diff --git a/NetworkLogClient.h b/src/NetworkLogClient.h similarity index 100% rename from NetworkLogClient.h rename to src/NetworkLogClient.h diff --git a/NewWalletConfig.cpp b/src/NewWalletConfig.cpp similarity index 100% rename from NewWalletConfig.cpp rename to src/NewWalletConfig.cpp diff --git a/NewWalletConfig.h b/src/NewWalletConfig.h similarity index 100% rename from NewWalletConfig.h rename to src/NewWalletConfig.h diff --git a/NotificationManager.cpp b/src/NotificationManager.cpp similarity index 100% rename from NotificationManager.cpp rename to src/NotificationManager.cpp diff --git a/NotificationManager.h b/src/NotificationManager.h similarity index 100% rename from NotificationManager.h rename to src/NotificationManager.h diff --git a/Payment.cpp b/src/Payment.cpp similarity index 100% rename from Payment.cpp rename to src/Payment.cpp diff --git a/Payment.h b/src/Payment.h similarity index 100% rename from Payment.h rename to src/Payment.h diff --git a/PaymentDetailInputs.cpp b/src/PaymentDetailInputs.cpp similarity index 100% rename from PaymentDetailInputs.cpp rename to src/PaymentDetailInputs.cpp diff --git a/PaymentDetailInputs_p.h b/src/PaymentDetailInputs_p.h similarity index 100% rename from PaymentDetailInputs_p.h rename to src/PaymentDetailInputs_p.h diff --git a/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp similarity index 100% rename from PaymentDetailOutput.cpp rename to src/PaymentDetailOutput.cpp diff --git a/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h similarity index 100% rename from PaymentDetailOutput_p.h rename to src/PaymentDetailOutput_p.h diff --git a/PaymentRequest.cpp b/src/PaymentRequest.cpp similarity index 100% rename from PaymentRequest.cpp rename to src/PaymentRequest.cpp diff --git a/PaymentRequest.h b/src/PaymentRequest.h similarity index 100% rename from PaymentRequest.h rename to src/PaymentRequest.h diff --git a/Payment_p.h b/src/Payment_p.h similarity index 100% rename from Payment_p.h rename to src/Payment_p.h diff --git a/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp similarity index 100% rename from PortfolioDataProvider.cpp rename to src/PortfolioDataProvider.cpp diff --git a/PortfolioDataProvider.h b/src/PortfolioDataProvider.h similarity index 100% rename from PortfolioDataProvider.h rename to src/PortfolioDataProvider.h diff --git a/PriceDataProvider.cpp b/src/PriceDataProvider.cpp similarity index 100% rename from PriceDataProvider.cpp rename to src/PriceDataProvider.cpp diff --git a/PriceDataProvider.h b/src/PriceDataProvider.h similarity index 100% rename from PriceDataProvider.h rename to src/PriceDataProvider.h diff --git a/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp similarity index 100% rename from PriceHistoryDataProvider.cpp rename to src/PriceHistoryDataProvider.cpp diff --git a/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h similarity index 100% rename from PriceHistoryDataProvider.h rename to src/PriceHistoryDataProvider.h diff --git a/QRCreator.cpp b/src/QRCreator.cpp similarity index 100% rename from QRCreator.cpp rename to src/QRCreator.cpp diff --git a/QRCreator.h b/src/QRCreator.h similarity index 100% rename from QRCreator.h rename to src/QRCreator.h diff --git a/QRScanner.cpp b/src/QRScanner.cpp similarity index 100% rename from QRScanner.cpp rename to src/QRScanner.cpp diff --git a/QRScanner.h b/src/QRScanner.h similarity index 100% rename from QRScanner.h rename to src/QRScanner.h diff --git a/TransactionInfo.cpp b/src/TransactionInfo.cpp similarity index 100% rename from TransactionInfo.cpp rename to src/TransactionInfo.cpp diff --git a/TransactionInfo.h b/src/TransactionInfo.h similarity index 100% rename from TransactionInfo.h rename to src/TransactionInfo.h diff --git a/Wallet.cpp b/src/Wallet.cpp similarity index 100% rename from Wallet.cpp rename to src/Wallet.cpp diff --git a/Wallet.h b/src/Wallet.h similarity index 100% rename from Wallet.h rename to src/Wallet.h diff --git a/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp similarity index 100% rename from WalletCoinsModel.cpp rename to src/WalletCoinsModel.cpp diff --git a/WalletCoinsModel.h b/src/WalletCoinsModel.h similarity index 100% rename from WalletCoinsModel.h rename to src/WalletCoinsModel.h diff --git a/WalletEnums.cpp b/src/WalletEnums.cpp similarity index 100% rename from WalletEnums.cpp rename to src/WalletEnums.cpp diff --git a/WalletEnums.h b/src/WalletEnums.h similarity index 100% rename from WalletEnums.h rename to src/WalletEnums.h diff --git a/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp similarity index 100% rename from WalletHistoryModel.cpp rename to src/WalletHistoryModel.cpp diff --git a/WalletHistoryModel.h b/src/WalletHistoryModel.h similarity index 100% rename from WalletHistoryModel.h rename to src/WalletHistoryModel.h diff --git a/WalletSecretsModel.cpp b/src/WalletSecretsModel.cpp similarity index 100% rename from WalletSecretsModel.cpp rename to src/WalletSecretsModel.cpp diff --git a/WalletSecretsModel.h b/src/WalletSecretsModel.h similarity index 100% rename from WalletSecretsModel.h rename to src/WalletSecretsModel.h diff --git a/Wallet_encryption.cpp b/src/Wallet_encryption.cpp similarity index 100% rename from Wallet_encryption.cpp rename to src/Wallet_encryption.cpp diff --git a/Wallet_p.h b/src/Wallet_p.h similarity index 100% rename from Wallet_p.h rename to src/Wallet_p.h diff --git a/Wallet_spending.cpp b/src/Wallet_spending.cpp similarity index 100% rename from Wallet_spending.cpp rename to src/Wallet_spending.cpp diff --git a/Wallet_support.cpp b/src/Wallet_support.cpp similarity index 100% rename from Wallet_support.cpp rename to src/Wallet_support.cpp diff --git a/Wallet_test.cpp b/src/Wallet_test.cpp similarity index 100% rename from Wallet_test.cpp rename to src/Wallet_test.cpp diff --git a/main.cpp b/src/main.cpp similarity index 100% rename from main.cpp rename to src/main.cpp diff --git a/main_utils.cpp b/src/main_utils.cpp similarity index 100% rename from main_utils.cpp rename to src/main_utils.cpp diff --git a/main_utils_android.cpp b/src/main_utils_android.cpp similarity index 100% rename from main_utils_android.cpp rename to src/main_utils_android.cpp diff --git a/qml_path_helper.cpp.in b/src/qml_path_helper.cpp.in similarity index 100% rename from qml_path_helper.cpp.in rename to src/qml_path_helper.cpp.in diff --git a/testing/priceHistory/CMakeLists.txt b/testing/priceHistory/CMakeLists.txt index 940f208..8b27f2a 100644 --- a/testing/priceHistory/CMakeLists.txt +++ b/testing/priceHistory/CMakeLists.txt @@ -16,7 +16,7 @@ set(CMAKE_AUTOMOC ON) -include_directories(${CMAKE_SOURCE_DIR} ${Qt6Test_INCLUDE_DIRS}) +include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_price_history TestPriceHistory.cpp diff --git a/testing/value/CMakeLists.txt b/testing/value/CMakeLists.txt index 16e6046..580820e 100644 --- a/testing/value/CMakeLists.txt +++ b/testing/value/CMakeLists.txt @@ -16,10 +16,7 @@ set(CMAKE_AUTOMOC ON) -include_directories( - ${CMAKE_SOURCE_DIR} - ${Qt6Test_INCLUDE_DIRS} -) +include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_value TestValue.cpp) target_link_libraries(test_value diff --git a/testing/wallet/CMakeLists.txt b/testing/wallet/CMakeLists.txt index e91a561..c462b93 100644 --- a/testing/wallet/CMakeLists.txt +++ b/testing/wallet/CMakeLists.txt @@ -16,11 +16,11 @@ set(CMAKE_AUTOMOC ON) -include_directories(${CMAKE_SOURCE_DIR} ${Qt6Test_INCLUDE_DIRS}) +include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_wallet TestWallet.cpp - ${CMAKE_SOURCE_DIR}/Wallet_test.cpp + ${CMAKE_SOURCE_DIR}/src/Wallet_test.cpp ) target_link_libraries(test_wallet pay_lib Qt6::Test) add_test(NAME Pay_test_wallet COMMAND test_wallet) -- 2.54.0 From cf30287b0a2eafc3d2e16aad3a8463bcc35e522e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Dec 2022 11:53:40 +0100 Subject: [PATCH 0170/1428] Improve detection of android build --- android/build-pay.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index a0a7438..b99797b 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -33,12 +33,11 @@ if test -z "$_pay_native_name_"; then exit fi - # check if the provided dir is really an android HUB-libs dir _thehub_dir_=`realpath $_thehub_dir_` _ok=0 if test -f $_thehub_dir_/lib/libflowee_p2p.a; then - if grep -q OS_ANDROID $_thehub_dir_/build.ninja; then + if grep -q -- -DANDROID $_thehub_dir_/build.ninja; then _ok=1 fi fi -- 2.54.0 From dbccb36077161162b86ad96a94e3b87da15bdb6d Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Sep 2022 08:34:48 +0200 Subject: [PATCH 0171/1428] cleanup the desktop file a bit --- guis/desktop/org.flowee.pay.desktop | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/guis/desktop/org.flowee.pay.desktop b/guis/desktop/org.flowee.pay.desktop index 3da2ce3..9e25f9e 100644 --- a/guis/desktop/org.flowee.pay.desktop +++ b/guis/desktop/org.flowee.pay.desktop @@ -1,16 +1,12 @@ -# SPDX-License-Identifier: GPL-2.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-later [Desktop Entry] -Version=1.0 Name=Flowee Pay GenericName=Bitcoin Cash Payment Solution -X-GNOME-FullName=Bitcoin Cash Payment Solution Comment=Transact on the Bitcoin Cash network, payments and more Keywords=wallet;bitcoin;bitcoincash;financial; Type=Application -Categories=Qt;Office;Finance -MimeType=; +Categories=Office;Finance; Exec=pay Terminal=false -StartupNotify=true Icon=org.flowee.pay -X-Ayatana-Desktop-Shortcuts=Drawing +MimeType=x-scheme-handler/bitcoincash; -- 2.54.0 From 702735e17a7f82bd0bdd74c84e4c84f3744a675a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Dec 2022 20:07:04 +0100 Subject: [PATCH 0172/1428] Fix the grouping of transactions. This adds a unit test as well, which is a bit tricky as this is date specific. --- src/WalletHistoryModel.cpp | 37 +++-- src/WalletHistoryModel.h | 7 +- testing/CMakeLists.txt | 2 + testing/walletHistoryModel/CMakeLists.txt | 25 ++++ .../TestWalletHistoryModel.cpp | 135 ++++++++++++++++++ .../TestWalletHistoryModel.h | 43 ++++++ 6 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 testing/walletHistoryModel/CMakeLists.txt create mode 100644 testing/walletHistoryModel/TestWalletHistoryModel.cpp create mode 100644 testing/walletHistoryModel/TestWalletHistoryModel.h diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 900bf6d..041abee 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -43,27 +43,33 @@ bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) } else if (date == today.addDays(-1)) { period = WalletEnums::Yesterday; - date = date.addDays(-1); days = 1; } - else if (date > today.addDays(-1 * today.dayOfWeek() + 1)) { + else if (date >= today.addDays(-1 * today.dayOfWeek() + 1)) { // this week period = WalletEnums::EarlierThisWeek; date = date.addDays(-1 * date.dayOfWeek()); - days = 7; + const auto yesterday = today.addDays(-1); + days = yesterday.day() - date.day(); } - else if (date > today.addDays(-1 * today.day() + 1)) { + else if (date >= today.addDays(-1 * today.day() + 1)) { // this month period = WalletEnums::EarlierThisMonth; date = date.addDays(-1 * date.day() + 1); - days = date.daysInMonth(); + const auto weekStart = today.addDays(-1 * today.dayOfWeek() + 1); + days = weekStart.day() - 1; } else { // any (other) month period = WalletEnums::Month; date = date.addDays(-1 * date.day() + 1); days = date.daysInMonth(); + + const auto yesterday = today.addDays(-1); + if (yesterday.year() == date.year() && yesterday.month() == date.month()) { + // don't eat the events that happend yesterday. + days -= 1; + } } - assert(days > 0); const QDateTime dt(date, QTime()); endTime = dt.addDays(days).toSecsSinceEpoch() - 1; } @@ -124,8 +130,9 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const case MinedDate: { if (item.minedBlockHeight <= 0) return QVariant(); - auto header = FloweePay::instance()->p2pNet()->blockchain().block(item.minedBlockHeight); - return QVariant(QDateTime::fromSecsSinceEpoch(header.nTime)); + + auto timestamp = secsSinceEpochFor(item.minedBlockHeight); + return QVariant(QDateTime::fromSecsSinceEpoch(timestamp)); } case FundsIn: { qint64 value = 0; @@ -252,10 +259,10 @@ QString WalletHistoryModel::dateForItem(qreal offset) const auto item = m_wallet->m_walletTransactions.at(m_rowsProxy.at(row)); if (item.minedBlockHeight <= 0) return QString(); - auto header = FloweePay::instance()->p2pNet()->blockchain().block(item.minedBlockHeight); - if (header.nTime == 0) + auto timestamp = secsSinceEpochFor(item.minedBlockHeight); + if (timestamp == 0) return QString(); - return QDateTime::fromSecsSinceEpoch(header.nTime).toString("MMMM yyyy"); + return QDateTime::fromSecsSinceEpoch(timestamp).toString("MMMM yyyy"); } void WalletHistoryModel::appendTransactions(int firstNew, int count) @@ -335,8 +342,7 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) if (blockheight <= 0) { timestamp = time(nullptr); } else { - const auto &bc = FloweePay::instance()->p2pNet()->blockchain(); - timestamp = bc.block(blockheight).nTime; + timestamp = secsSinceEpochFor(blockheight); } assert(timestamp > 0); @@ -368,6 +374,11 @@ void WalletHistoryModel::setIncludeFlags(const QFlags &fla QTimer::singleShot(0, this, SLOT(createMap())); } +uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const +{ + return FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime; +} + int WalletHistoryModel::lastSyncIndicator() const { return m_lastSyncIndicator; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 2e52a2e..7b27407 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -84,7 +84,12 @@ signals: void lastSyncIndicatorChanged(); void includeFlagsChanged(); -private slots: +protected: + // virtual to allow the unit test to not use p2pNet for this + /// return the timestamp of a block (aka nTime) as defined by the block-header + virtual uint32_t secsSinceEpochFor(int blockHeight) const; + +protected slots: void appendTransactions(int firstNew, int count); void transactionChanged(int txIndex); void createMap(); diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 8c7a166..35bb839 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -19,11 +19,13 @@ if (${Qt6Test_FOUND}) enable_testing() add_definitions(-DIN_TESTS) add_subdirectory(wallet) + add_subdirectory(walletHistoryModel) add_subdirectory(value) add_subdirectory(priceHistory) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS test_wallet + test_wallet_history_model test_value test_price_history ) diff --git a/testing/walletHistoryModel/CMakeLists.txt b/testing/walletHistoryModel/CMakeLists.txt new file mode 100644 index 0000000..2362770 --- /dev/null +++ b/testing/walletHistoryModel/CMakeLists.txt @@ -0,0 +1,25 @@ +# This file is part of the Flowee project +# Copyright (C) 2022 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(CMAKE_AUTOMOC ON) + +include_directories(${Qt6Test_INCLUDE_DIRS}) + +add_executable(test_wallethistorymodel + TestWalletHistoryModel.cpp +) +target_link_libraries(test_wallethistorymodel pay_lib Qt6::Test) +add_test(NAME Pay_test_wallet_history_model COMMAND test_wallethistorymodel) diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.cpp b/testing/walletHistoryModel/TestWalletHistoryModel.cpp new file mode 100644 index 0000000..eaa0fb3 --- /dev/null +++ b/testing/walletHistoryModel/TestWalletHistoryModel.cpp @@ -0,0 +1,135 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TestWalletHistoryModel.h" +#include +#include + +#include + +class MockWalletHistoryModel : public WalletHistoryModel +{ +public: + explicit MockWalletHistoryModel(Wallet *wallet) + : WalletHistoryModel(wallet) {} + + void notifyNewTransactions(int firstNew, int count) { + appendTransactions(firstNew, count); + } + +protected: + uint32_t secsSinceEpochFor(int blockHeight) const override { + assert(blockHeight > 0); + assert(blockHeight <= 6000); + // lets make our blocks range from 1000 hours ago (a little over 40 days) to now. + // we define one block every 600 seconds exect, making the 1000 hours take 6000 blocks. + static auto now = QDateTime::currentSecsSinceEpoch(); + return static_cast(now - 1000 * 3600) + blockHeight * 600; + } +}; + +class MockWallet : public Wallet +{ +public: + void createTransactions1() { + WalletTransaction wtx; + for (int i = m_nextWalletTransactionId; i <= 6000; ++i) { + wtx.minedBlockHeight = i; + m_walletTransactions.insert(std::make_pair(m_nextWalletTransactionId++, wtx)); + } + } +}; + +TestWalletHistoryModel::TestWalletHistoryModel() +{ + QString basedir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + m_dir = basedir + QString("/floweepay-%1/").arg(QCoreApplication::instance()->applicationPid()); +} + +void TestWalletHistoryModel::cleanup() +{ + if (m_dir.isEmpty()) + return; + QDir dir(m_dir); + dir.removeRecursively(); + m_dir.clear(); +} + + +void TestWalletHistoryModel::basic() +{ + auto wallet = createWallet(); + std::unique_ptr model(new MockWalletHistoryModel(wallet.get())); + QCOMPARE(model->rowCount(), 0); + wallet->createTransactions1(); + model->notifyNewTransactions(1, 6000); // wallet starts counting at 1 + QCOMPARE(model->rowCount(), 6000); + + auto first = model->data(model->index(0, 0), WalletHistoryModel::MinedHeight); + QVERIFY(first.isValid()); + QCOMPARE(first.toInt(), 6000); + + first = model->data(model->index(0, 0), WalletHistoryModel::GroupId); + QVERIFY(first.isValid()); + /* + * As the grouping is done on the current date, I need to also verify relative to the + * current date. + * The first transaction will always be in "today", but the amount of transactions after + * that are in that same group is relative to what time it is... + * + * This leads the groups to be numbered like this: + * + * 0: prev month [may be twice] + * 1: earlier this month [optional] + * 2: earlier this week [optional] + * 3: yesterday [optional] + * 4: today + * + * Since we occupy 40 days, the spread may be any combination of those. + * i.e. 'today' may be just group-id 2 in case today is the first of this month. + */ + QVERIFY(first.toInt() >= 2); + QCOMPARE(model->groupingPeriod(first.toInt()), "Today"); + + auto groupId = model->data(model->index(24 * 6, 0), WalletHistoryModel::GroupId); + QVERIFY(groupId.isValid()); + QVERIFY(first.toInt() > groupId.toInt()); + QCOMPARE(model->groupingPeriod(groupId.toInt()), "Yesterday"); + + const auto now = QDateTime::currentDateTime(); + auto today = now.date(); + logFatal() << "dayOfWeek" << today.dayOfWeek(); + if (today.dayOfWeek() > 2) { + // that means we should have a 'EarlierThisWeek' group. + + groupId = model->data(model->index(24 * 6 * (today.dayOfWeek() - 1), 0), WalletHistoryModel::GroupId); + QVERIFY(groupId.isValid()); + QCOMPARE(model->groupingPeriod(groupId.toInt()), "Earlier this week"); + } + if (today.day() > 2) { + // that means we should have a 'EarlierThisMonth' group. + + groupId = model->data(model->index(24 * 6 * (today.day() - 1), 0), WalletHistoryModel::GroupId); + QVERIFY(groupId.isValid()); + QCOMPARE(model->groupingPeriod(groupId.toInt()), "Earlier this month"); + } + +} + + + +QTEST_MAIN(TestWalletHistoryModel) diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.h b/testing/walletHistoryModel/TestWalletHistoryModel.h new file mode 100644 index 0000000..1660421 --- /dev/null +++ b/testing/walletHistoryModel/TestWalletHistoryModel.h @@ -0,0 +1,43 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TEST_WALLET_HISTORY_MODEL_H +#define TEST_WALLET_HISTORY_MODEL_H + +#include +#include + +class TestWalletHistoryModel : public QObject +{ + Q_OBJECT +public: + TestWalletHistoryModel(); + +private slots: + void cleanup(); // called after each testcase. + void basic(); + +private: + template + std::unique_ptr createWallet() { + std::unique_ptr wallet(reinterpret_cast(Wallet::createWallet(m_dir.toStdString(), 1111, "test"))); + return wallet; + } + QString m_dir; +}; + +#endif -- 2.54.0 From bacbfb28495b8eae6630af3e3d962e09a499de19 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Dec 2022 20:17:18 +0100 Subject: [PATCH 0173/1428] Be more lenient with hiding of the progress indicator When there has not been a block for some time we might be up to date already. Also remove unused variable. --- guis/mobile/AccountHistory.qml | 1 - guis/mobile/AccountSyncState.qml | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 8843298..f176cdb 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -111,7 +111,6 @@ ListView { AccountSyncState { account: portfolio.current - hideWhenDone: true width: parent.width } diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index bbdc1df..1fac425 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -24,7 +24,6 @@ Item { id: root height: indicator.height + 3 + (done ? 0 : circleShape.height) property QtObject account: null - property bool hideWhenDone: false property bool done: false property int startPos: account.initialBlockHeight onAccountChanged: { @@ -38,7 +37,7 @@ Item { let currentPos = account.lastBlockSynched; // only show the progress-circle when its more than 2 days behind // and we are not synched - done = end - startPos < 300 || end - currentPos < 2; + done = end - startPos < 300 || end - currentPos <= 6; } // The 'progress' circle. -- 2.54.0 From bad7419886ae6a061197e3b157c0edd01f5b028d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Dec 2022 20:47:08 +0100 Subject: [PATCH 0174/1428] Make camera usage on desktop not a CPU hog This now waits the QR parsing thread should we have time left over before the next frame. --- src/CameraController.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 8fde1d3..f38b4f7 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -198,7 +198,7 @@ void CameraControllerPrivate::checkState() logFatal() << "CameraController found cam error:" << cam->errorString(); auto sink = qobject_cast(videoSink); - assert(sink); // likely bug in your QML + assert(sink); // here to detect bug in QML QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { currentFrame = frame; @@ -223,7 +223,15 @@ QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) void QRScanningThread::run() { + auto lastFrameScanned = time(nullptr); while (true) { + const auto now = time(nullptr); + auto sleep = 34 - (now - lastFrameScanned); // assume 30 - FPS + // Sleep if we are too fast and (assuming 33.3 ms per frame) we would end up + // parsing the same frame twice. + if (sleep > 0) + QThread::msleep(sleep); + m_parent->lock.lock(); bool exit = !m_parent->cameraStarted; QVideoFrame frame = m_parent->currentFrame; @@ -231,6 +239,7 @@ void QRScanningThread::run() if (exit) return; + lastFrameScanned = time(nullptr); auto results = readBarcodes(frame); for (const auto &result : results) { const auto &bytes = result.bytes(); -- 2.54.0 From 60341e4f8aed19bd9fcfd07581333eba1811bad5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:17:04 +0100 Subject: [PATCH 0175/1428] Fix regression; move define to the root level Yesterdays refactor causes the OS defines to only be specified for the shared library. This makes the app-specific compiles receive the define again too. --- CMakeLists.txt | 3 +++ src/CMakeLists.txt | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 774a7f5..a7332f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,9 @@ function(download_file url path) endif() endfunction() +# The cmake system name will hold values like Android, Linux or others. +add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) + ###### Translations if(NOT ANDROID) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e7b7972..09eb0ab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,9 +20,6 @@ option(local_qml "Allow local QML loading" OFF) option(networkLog "Include network-logging client" OFF) option(NetworkLogClient "Include the network based logging in the executables" OFF) -add_compile_definitions(TARGET_OS_${CMAKE_SYSTEM_NAME}) -# The cmake system name will hold values like Android, Linux or others. - set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp -- 2.54.0 From 4f35c21a4f80f7e0da8c443d905a2da179fe0d80 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:18:07 +0100 Subject: [PATCH 0176/1428] Cleanup The shapes code moved out of this file, remove the import. --- guis/desktop/SendTransactionPane.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index e3458d8..b003675 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -19,7 +19,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window -import QtQuick.Shapes import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors import Flowee.org.pay -- 2.54.0 From a0c4f9be7a7d3e32fe6324f273bc6625521f44c8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:21:56 +0100 Subject: [PATCH 0177/1428] Much improvement with the camera section. This adds a prettty cutout screen and better feedback on the camera view popping up. We also make visible the scan overlay instantly, hiding out any other UI that is hidden beneath it. We correctly handle various odd issues with Qt / Android. - the permissions request (QFuture) returned in a different thread, we now move back to the main thread before doing any calls on the multimedia objects. - The popping up of the android permissions requestor actually makes the app think its being made inactive. Now we no longer cancel the QR scan request in such cases. --- guis/mobile/QRScannerOverlay.qml | 140 +++++++++++++++++++++---------- guis/mobile/main.qml | 1 - src/CameraController.cpp | 93 ++++++++++++++------ src/CameraController.h | 8 +- 4 files changed, 170 insertions(+), 72 deletions(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 49976c1..4a47fce 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -17,12 +17,34 @@ */ import QtQuick import QtQuick.Controls as QQC2 -// import QtQuick.Layouts +import QtQuick.Shapes import QtMultimedia import "../Flowee" as Flowee -Item { +FocusScope { id: root + focus: true + visible: CameraController.visible + + Connections { + target: CameraController + function onVisibleChanged() { + if (CameraController.visible) + root.forceActiveFocus(); + } + } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + event.accepted = true; + CameraController.abort(); + } + } + Rectangle { + id: background + anchors.fill: parent + color: mainWindow.palette.window + } + // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the // feature is actually requisted by the user. @@ -32,14 +54,77 @@ Item { anchors.fill: parent } + // The 'progress' icon. + Shape { + id: cutout + anchors.fill: parent.fill + smooth: true + opacity: 0.5 + + property int x1: root.width / 2 - 100 + property int y1: root.height / 2 - 100 + property int y2: y1 + 200 + property int x2: x1 + 200 + property int radius: 30 + + ShapePath { + fillColor: "black" + strokeWidth: 0 + strokeColor: "transparent" + startX: 0; startY: 0 + + PathLine { x: width; y: 0 } + PathLine { x: width; y: height } + PathLine { x: 0; y: height } + PathLine { x: 0; y: height / 2 } + + // move to the center part. + PathLine { x: cutout.x1; y: height / 2 } + + PathLine { x: cutout.x1; y: cutout.y1 + cutout.radius } + PathArc { + x: cutout.x1 + cutout.radius + y: cutout.y1 + radiusX: cutout.radius + radiusY: cutout.radius + } + + PathLine { x: cutout.x2 - cutout.radius; y: cutout.y1 } + PathArc { + x: cutout.x2 + y: cutout.y1 + cutout.radius + radiusX: cutout.radius + radiusY: cutout.radius + } + PathLine { x: cutout.x2; y: cutout.y2 - cutout.radius} + PathArc { + x: cutout.x2 - cutout.radius + y: cutout.y2 + radiusX: cutout.radius + radiusY: cutout.radius + } + PathLine { x: cutout.x1 + cutout.radius; y: cutout.y2 } + PathArc { + x: cutout.x1 + y: cutout.y2 - cutout.radius + radiusX: cutout.radius + radiusY: cutout.radius + } + PathLine { x: cutout.x1; y: height / 2} + PathLine { x: 0; y: height / 2 } + PathLine { x: 0; y: 0 } + } + } + Flowee.CloseIcon { + anchors.right: parent.right + anchors.rightMargin: 10 + y: 10 + onClicked: CameraController.abort(); + } + Component { id: videoFeedPanel - Rectangle { - color: mainWindow.palette.window - anchors.left: parent.left - anchors.right: parent.right - height: 300 - + Item { Component.onCompleted: { CameraController.camera = camera CameraController.videoSink = videoOutput.videoSink @@ -49,8 +134,10 @@ Item { function onCameraActiveChanged() { if (CameraController.cameraActive) { camera.start(); - } else { - // camera.stop(); // stopping the camera on Qt641 tends to not allow us to restart it + } else if (Qt.platform.os !== "linux") { + // at least on Linux stopping a camera and turning it on again fails with + // "Camera is in use" + camera.stop(); } } } @@ -69,39 +156,6 @@ Item { width: parent.width height: parent.height } - Rectangle { - opacity: 0.3 - color: "black" - width: parent.width; - height: parent.height / 4 - } - Rectangle { - opacity: 0.3 - color: "black" - width: parent.width / 5 - height: parent.height - } - Rectangle { - opacity: 0.3 - color: "black" - width: parent.width; - height: parent.height / 4 - anchors.bottom: parent.bottom - } - Rectangle { - opacity: 0.3 - color: "black" - width: parent.width / 5 - height: parent.height - anchors.right: parent.right - } - - Flowee.CloseIcon { - anchors.right: parent.right - anchors.rightMargin: 10 - y: 10 - onClicked: CameraController.abort(); - } } } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index fe827bf..2d489f2 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -63,6 +63,5 @@ ApplicationWindow { QRScannerOverlay { anchors.fill: parent - visible: CameraController.visible } } diff --git a/src/CameraController.cpp b/src/CameraController.cpp index f38b4f7..d55f00b 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -63,6 +63,7 @@ public: mutable QMutex lock; QVideoFrame currentFrame; + QCameraFormat preferredFormat; bool cameraLoaded = false; bool cameraStarted = false; @@ -104,7 +105,13 @@ CameraControllerPrivate::CameraControllerPrivate(CameraController *qq) auto guiApp = qobject_cast(QCoreApplication::instance()); assert(guiApp); QObject::connect(guiApp, &QGuiApplication::applicationStateChanged, qq, [=](Qt::ApplicationState appState) { - if (appState == Qt::ApplicationInactive) { + // the application state will turn to "Inactive" when the Android dialog + // asking permissions is put on top, and when the user enters data, we are + // turned into active again. + // So check if we are already authorized as that avoid us turning off + // the camera just after turning it on the first time. + if (state == Authorized && appState == Qt::ApplicationInactive) { + logInfo() << "App state inactive, turning off camera"; // when the user leaves the app screen, the permissions granted to us // may have changed, so we need to re-ask. state = NotAsked; @@ -161,7 +168,7 @@ void CameraControllerPrivate::initCamera() if (preferredIsCheap && !formatIsCheap) continue; // avoid going for the biggest feed, but not too small either. - if (oldSize.width() < 210 || (size.width() < oldSize.width() && size.width() >= 210)) { + if (oldSize.width() < 600 || (size.width() < oldSize.width() && size.width() >= 600)) { preferred = format; logInfo() << "picked"; } @@ -171,34 +178,39 @@ void CameraControllerPrivate::initCamera() } } } - logCritical().nospace() << "Changing camera resolution to " << preferred.resolution().width() << "x" << preferred.resolution().height(); - cam->setCameraFormat(preferred); - cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. - cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash + logCritical().nospace() << "Selected camera resolution: " << preferred.resolution().width() << "x" << preferred.resolution().height(); + preferredFormat = preferred; } void CameraControllerPrivate::checkState() { if (state != Authorized) return; - if (!cameraLoaded) { + if (!cameraLoaded || !visible) { cameraLoaded = true; - emit q->loadCameraChanged(); - } - if (!visible) { visible = true; emit q->visibleChanged(); + emit q->loadCameraChanged(); + + // then wait 300ms before turning on the actual camera + QTimer::singleShot(300, q, SLOT(checkState())); + return; } if (camera && videoSink && !cameraStarted && scanRequest.get()) { - logFatal() << "Camera active is now true"; - cameraStarted = true; - emit q->cameraActiveChanged(); QCamera *cam = qobject_cast(camera); - if (cam && cam->error() != QCamera::NoError) + auto sink = qobject_cast(videoSink); + if (!cam || !sink) { // here to detect bug in QML + logFatal() << "invalid or no camera or sink object set"; + return; + } + if (cam->error() != QCamera::NoError) logFatal() << "CameraController found cam error:" << cam->errorString(); - auto sink = qobject_cast(videoSink); - assert(sink); // here to detect bug in QML + cam->setCameraFormat(preferredFormat); + cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. + cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash + cameraStarted = true; + QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { currentFrame = frame; @@ -208,6 +220,8 @@ void CameraControllerPrivate::checkState() m_scanningThread->start(); } }); + logDebug() << "Camera active is now true"; + emit q->cameraActiveChanged(); // this emit makes QML activate the camera } } @@ -290,9 +304,6 @@ void QRScanningThread::run() return; } } - - // No matches. We go for another round. - // we assume at least 33ms has passed (at 30 frames that is the frame-rate) and we'll be parsing the next frame on loop } } @@ -411,43 +422,73 @@ CameraController::CameraController(QObject *parent) : QObject(parent), d(new CameraControllerPrivate(this)) { + // pre-load the camera for stability sake + QTimer::singleShot(3000, this, SLOT(initialize())); + + // The Android permissions requesting stuff returns results in a different thread, + // allow an easy way to move back to the main thread using a connection. + QObject::connect(this, SIGNAL(startCheckState()), this, SLOT(checkState()), Qt::QueuedConnection); } +void CameraController::initialize() +{ +#ifdef TARGET_OS_Android + QMutexLocker locker(&d->lock); + if (d->state == NotAsked) { +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); + future.then([=](QtAndroidPrivate::PermissionResult res) { + if (res == QtAndroidPrivate::PermissionResult::Authorized) { + // the camera had been authorized before, load the camera components in the UI. + d->cameraLoaded = true; + emit loadCameraChanged(); + } + }); +#endif + } +#endif +} + + void CameraController::startRequest(QRScanner *request) { assert(request); d->scanRequest = request; + + if (!d->visible) { + d->visible = true; + emit visibleChanged(); + } + if (d->state == NotAsked) { #ifdef TARGET_OS_Android #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) // we expect that in Qt65 this API will be changed or removed as the QCoreApplication::requestPermission will then become available. // for now, use the private APIs (since 6.5 is still 4 months from release) - d->state = Asking; - logCritical() << "Starting to ask for permission for camera usage"; auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); future.then([=](QtAndroidPrivate::PermissionResult res) { - logCritical() << "Check permission returned: " << res; if (res == QtAndroidPrivate::PermissionResult::Authorized) { d->state = Authorized; - d->checkState(); + emit startCheckState(); } else { - logCritical() << "We are starting to request permissions"; // then ask auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); future.then([=](QtAndroidPrivate::PermissionResult res) { - logCritical() << "Permission request result: " << res << (res == QtAndroidPrivate::Authorized); if (res == QtAndroidPrivate::Authorized) { d->state = Authorized; // move the actual turning on of the camera to the next event. - QTimer::singleShot(10, this, SLOT(checkState())); + // please notice that this reply came in a different thread than the main one. + // We must move back to the main one before doing anything. + emit startCheckState(); } else { d->state = Denied; } }); } }); + return; #endif // version check #else d->state = Authorized; diff --git a/src/CameraController.h b/src/CameraController.h index d11171a..8ec7faa 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -30,8 +30,8 @@ class QRScanner; * * a. user action causes a QRScanner QML object to be populated and activated, which is backed by a cpp class of the same name. - * b. we get a 'startRequest' call, we set visible to true. - * c. we check for authorization, if granted we set loadCamera to true. + * b. we get a 'startRequest' call, we set loadCamera to true. + * c. we check for authorization, if granted we set visible to true. * d. the QML populates our qmlCamera and videoSink properties * e. we check the camera setup, setting the best resolution and stuff. * f. we 'start' the camera by setting the cameraActive bool to true, which the QML uses. @@ -72,7 +72,11 @@ signals: void cameraActiveChanged(); void visibleChanged(); + // \internal (used to move thread) + void startCheckState(); + private slots: + void initialize(); void qrScanFinished(); void checkState(); -- 2.54.0 From 039a435d5f3975ce5aa288e3b4bdf942d87aeca4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:23:43 +0100 Subject: [PATCH 0178/1428] Be more accepting in what we receive in a QR payment We fetch the message from either the 'label' or the 'message' url variable now. --- src/Payment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index f1a563f..a984dca 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -104,7 +104,7 @@ void Payment::setTargetAddress(const QString &address_) emit amountChanged(); } } - else if (item.first == "label") { + else if (item.first == "label" || item.first == "message") { setUserComment(item.second); } } -- 2.54.0 From 6eb3df5248de75c87880907bf8c1d004fe00b71d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:38:33 +0100 Subject: [PATCH 0179/1428] Close accounts section on menu close. --- guis/mobile/MenuOverlay.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index ed8229c..5f3a57d 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -25,6 +25,8 @@ Item { id: root property bool open: false + onOpenChanged: if (!open) baseArea.openAccounts = false; // close the accounts when the menu is closed + Rectangle { anchors.fill: parent opacity: { -- 2.54.0 From 12c514e024960dcb8e4e02b2060f38e6bca4b232 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 14:41:55 +0100 Subject: [PATCH 0180/1428] Avoid confusion, use palette.windowText The 'text' and 'windowText' were both used like they were the same thing, which they currently are but don't have to be. Now migrate the various cases where to we use 'text' to 'windowText'. This choice is made because the Label object uses windowText by default. In future we might want to make the 'text' one have a slightly different color. Its still used in Qt components like the popup menu. --- guis/Flowee/BitcoinAmountLabel.qml | 2 +- guis/Flowee/CheckBox.qml | 6 +++--- guis/Flowee/GroupBox.qml | 4 ++-- guis/Flowee/Label.qml | 2 +- guis/Flowee/LabelWithCursor.qml | 4 ++-- guis/Flowee/TextField.qml | 2 +- guis/desktop/SendTransactionPane.qml | 6 +++--- guis/desktop/WalletTransaction.qml | 2 +- guis/desktop/main.qml | 4 ++-- guis/mobile/TextButton.qml | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 099beb2..b5d93a0 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -35,7 +35,7 @@ QQC2.Control { * This is only used if you fill it with a date object. */ property var fiatTimestamp: null - property color color: palette.text + property color color: palette.windowText property alias fontPixelSize: main.font.pixelSize implicitHeight: row.implicitHeight diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index cd6279b..4543114 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -56,7 +56,7 @@ Item { if (!root.enabled) return "darkgray" if (root.checked && Pay.useDarkSkin) - return mainWindow.palette.text; + return mainWindow.palette.windowText; return mainWindow.palette.highlight } Behavior on x { NumberAnimation {}} @@ -87,7 +87,7 @@ Item { anchors.left: slider.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 6 - color: enabled ? palette.text : "darkgray" + color: enabled ? palette.windowText : "darkgray" } Rectangle { @@ -99,7 +99,7 @@ Item { anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 16 radius: width - color: q.palette.text + color: q.palette.windowText Label { id: q text: "?" diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index a8ef270..9850666 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -70,7 +70,7 @@ QQC2.Control { Text { id: titleLabel x: 3 - color: root.palette.text + color: root.palette.windowText // focus indicator Rectangle { @@ -90,7 +90,7 @@ QQC2.Control { anchors.leftMargin: 20 visible: root.effectiveCollapsed && text !== "" font.italic: true - color: root.palette.text + color: root.palette.windowText } } diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml index 1bf1e4f..fb33067 100644 --- a/guis/Flowee/Label.qml +++ b/guis/Flowee/Label.qml @@ -20,5 +20,5 @@ import QtQuick.Controls as QQC2 QQC2.Label { // With Qt6.4 on Android, this extra line is needed to // get the label to follow the app-color-style - color: mainWindow.palette.text + color: mainWindow.palette.windowText } diff --git a/guis/Flowee/LabelWithCursor.qml b/guis/Flowee/LabelWithCursor.qml index 6302fa2..f28f227 100644 --- a/guis/Flowee/LabelWithCursor.qml +++ b/guis/Flowee/LabelWithCursor.qml @@ -41,7 +41,7 @@ Item { Label { id: begin - color: palette.text + color: palette.windowText text: { var fullText = root.text var cutPoint = parent.cursorPos - parent.startPos; @@ -67,7 +67,7 @@ Item { return cutPoint <= parent.stringLength return cutPoint < parent.stringLength } - color: cursorVisible ? begin.palette.text : "#00000000" + color: cursorVisible ? begin.palette.windowText : "#00000000" property bool cursorVisible: true Timer { id: blinkingCursor diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index b7889b4..aab529d 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -26,7 +26,7 @@ QQC2.TextField { selectByMouse: true // In Qt6.3 this is just always black, so adjust to color placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#3e3e3e" - color: palette.text + color: palette.windowText background: Rectangle { implicitHeight: root.contentHeight + 2 diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index b003675..5c95da3 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -171,8 +171,8 @@ Item { Layout.fillWidth: true // Change the color when the portfolio changed since 'prepare' was clicked. color: prepareButton.portfolioUsed === portfolio.current - ? palette.text - : Qt.darker(palette.text, (Pay.useDarkSkin ? 1.6 : 0.4)) + ? palette.windowText + : Qt.darker(palette.windowText, (Pay.useDarkSkin ? 1.6 : 0.4)) menuText: qsTr("Copy transaction-ID") } Label { @@ -317,7 +317,7 @@ Item { if (!activeFocus && text !== "" && !addressOk) color = Pay.useDarkSkin ? "#ff6568" : "red" else - color = mainWindow.palette.text + color = mainWindow.palette.windowText } } Label { diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index 6ad830c..c5bae44 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -84,7 +84,7 @@ Item { } opacity: txRoot.isRejected ? 1 : 0.5 font.pointSize: mainLabel.font.pointSize * 0.8 - color: txRoot.isRejected ? (Pay.useDarkSkin ? "#ec2327" : "#b41214") : palette.text + color: txRoot.isRejected ? (Pay.useDarkSkin ? "#ec2327" : "#b41214") : palette.windowText Component.onCompleted: updateText() Timer { diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 05d6842..aef7d37 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -465,7 +465,7 @@ ApplicationWindow { } colorize: false showFiat: false - color: mainWindow.palette.text + color: mainWindow.palette.windowText fontPixelSize: { if (leftColumn.width < 240) // max width is 252 return leftColumn.width / 7 @@ -567,7 +567,7 @@ ApplicationWindow { Timer { id: animTimer interval: 305 - onTriggered: fiatValue.color = fiatValue.palette.text + onTriggered: fiatValue.color = fiatValue.palette.windowText } } diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index d1d13bd..70011b2 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -44,7 +44,7 @@ Item { anchors.topMargin: 6 font.pointSize: mainWindow.font.poinSize * 0.7 font.bold: false - color: Qt.darker(palette.text, 1.5) + color: Qt.darker(palette.windowText, 1.5) } MouseArea { anchors.fill: parent -- 2.54.0 From e06cfc04f4f062dcbab61a46f9080c7cbf91e10f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 15:02:01 +0100 Subject: [PATCH 0181/1428] Change to use a persistent docker instance To speed up builds, don't create and destroy dockers every run, instead start one and keep calling into that one to do more builds. --- android/build-pay.sh | 65 ++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index b99797b..941e00d 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -67,6 +67,7 @@ fi if test ! -f .config; then cat << HERE > .config +#!/bin/bash cd /home/builds/build if ! test -f build.ninja; then @@ -112,23 +113,6 @@ if test "\$1" = "distclean"; then perl -e 'use File::Path qw(remove_tree); opendir DIR, "."; while (\$entry = readdir DIR) { if (\$entry=~/^\./) { next; } if (\$entry=~/smartBuild.sh$/ || \$entry=~/^imports$/) { next; } unlink "\$entry"; remove_tree "\$entry"; }' fi -if test ! -f build.ninja; then - cp -n imports/*qm . - docker run --rm -ti\ - --volume=`pwd`:/home/builds/build \ - --volume=$floweePaySrcDir:/home/builds/src \ - --volume=$_thehub_dir_:/home/builds/floweelibs \ - $_docker_name_ \ - build/.config -fi - -if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockheaders.info; then - imports/blockheaders-meta-extractor android-build/assets -fi -if test -f $floweePaySrcDir/android/netlog.conf; then - cp $floweePaySrcDir/android/netlog.conf android-build/assets/ -fi - if test "\$1" = "sign" -o "\$2" = "sign" then MAKE_SIGNED_APK=1 @@ -138,21 +122,44 @@ else fi fi -docker run --rm -ti\ - --volume=`pwd`:/home/builds/build \ - --volume=$floweePaySrcDir:/home/builds/src \ - --volume=$_thehub_dir_:/home/builds/floweelibs \ - $_docker_name_ \ - "/usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir \$MAKE_UNSIGNED_APK" +if test -f .docker; then + DOCKERID=\`cat .docker\` + if test -n "\$DOCKERID"; then + if test -z "\`docker container inspect \$DOCKERID | grep '"Status": "running"'\`"; then + echo "docker image died, removing" + docker container rm \$DOCKERID + DOCKERID="" + fi + fi +fi +if test -z "\$DOCKERID"; then + echo "starting docker container" + DOCKERID=\`docker run -d -ti \\ + --volume=`pwd`:/home/builds/build \\ + --volume=$floweePaySrcDir:/home/builds/src \\ + --volume=$_thehub_dir_:/home/builds/floweelibs \\ + flowee/buildenv-android:v6.4.1 /bin/bash\` + echo "\$DOCKERID" > .docker +fi +execInDocker="docker container exec --workdir /home/builds --user \`id -u\` \$DOCKERID" + +if test ! -f build.ninja; then + cp -n imports/*qm . + \$execInDocker build/.config +fi + +if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockheaders.info; then + imports/blockheaders-meta-extractor android-build/assets +fi +if test -f $floweePaySrcDir/android/netlog.conf; then + cp $floweePaySrcDir/android/netlog.conf android-build/assets/ +fi + +\$execInDocker /usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir \$MAKE_UNSIGNED_APK if test -n "\$MAKE_SIGNED_APK" then - docker run --rm -ti\ - --volume=`pwd`:/home/builds/build \ - --volume=$floweePaySrcDir:/home/builds/src \ - --volume=$_thehub_dir_:/home/builds/floweelibs \ - $_docker_name_ \ - build/.sign + \$execInDocker build/.sign echo -n "-- COPYING: " cp -v android-build//build/outputs/apk/release/android-build-release-signed.apk floweepay.apk fi -- 2.54.0 From 3f32f759e72a60e8c0685884db8ac1372808c01b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 16:23:51 +0100 Subject: [PATCH 0182/1428] Improve camera stability. This adds some timers and similar hacks which seem to have a very positive result on getting an actual picture consistently from the camera. Tested this on 3 actual phones, of different make and versions. Fixes: #11 --- src/CameraController.cpp | 33 ++++++++++++++++++++------------- src/CameraController.h | 1 + 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index d55f00b..9ca7516 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -133,6 +133,7 @@ void CameraControllerPrivate::initCamera() QCamera *cam = qobject_cast(camera); if (!cam) return; + cam->stop(); // workaround for why some phones don't scan the first time. QCameraFormat preferred; bool preferredIsCheap = false; for (const auto &format : cam->cameraDevice().videoFormats()) { @@ -206,25 +207,31 @@ void CameraControllerPrivate::checkState() if (cam->error() != QCamera::NoError) logFatal() << "CameraController found cam error:" << cam->errorString(); + cam->stop(); // workaround for why some phones don't scan the first time. cam->setCameraFormat(preferredFormat); cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash - cameraStarted = true; - - QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { - currentFrame = frame; - - if (!m_scanningThread) { - m_scanningThread = new QRScanningThread(this); - QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); - m_scanningThread->start(); - } - }); - logDebug() << "Camera active is now true"; - emit q->cameraActiveChanged(); // this emit makes QML activate the camera + QTimer::singleShot(300, q, SLOT(checkState2())); } } +void CameraController::checkState2() +{ + d->cameraStarted = true; + auto sink = qobject_cast(d->videoSink); + QObject::connect(sink, &QVideoSink::videoFrameChanged, this, [=](const QVideoFrame &frame) { + d->currentFrame = frame; + + if (!d->m_scanningThread) { + d->m_scanningThread = new QRScanningThread(d); + QObject::connect (d->m_scanningThread, SIGNAL(finished()), this, SLOT(qrScanFinished()), Qt::QueuedConnection); + d->m_scanningThread->start(); + } + }); + logDebug() << "Camera active is now true"; + emit cameraActiveChanged(); // this emit makes QML activate the camera +} + // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) diff --git a/src/CameraController.h b/src/CameraController.h index 8ec7faa..908d173 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -79,6 +79,7 @@ private slots: void initialize(); void qrScanFinished(); void checkState(); + void checkState2(); private: CameraControllerPrivate * d; -- 2.54.0 From fdaed901ebbc2965c5d2860087c1bee6c25e4895 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Dec 2022 16:29:38 +0100 Subject: [PATCH 0183/1428] Make more robust --- android/docker/scripts/buildQt.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 97098df..81a00d0 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -9,12 +9,13 @@ echo "Based on Qt version $TAG" >> /etc/versions source /etc/profile function checkout ( + repo=$1 (cd /usr/local/cache - if ! test -d $1.git; then - git clone --bare https://code.qt.io/qt/$1.git + if ! test -d $repo.git; then + git clone --bare https://code.qt.io/qt/$repo.git fi cd ~builduser - git clone -l /usr/local/cache/$1.git -b $TAG) + git clone -l /usr/local/cache/$repo.git -b $TAG) ) # The QtBase builds are different. -- 2.54.0 From c6df261335b13aa8aae5dd3e968f8067ace585d0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 10:50:29 +0100 Subject: [PATCH 0184/1428] State time in the recent transactions. --- guis/ControlColors.js | 2 +- guis/mobile/AccountHistory.qml | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index f14d84f..577d032 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -21,7 +21,7 @@ function applyDarkSkin(item) { item.palette.mid = "#6b6b6b" item.palette.midlight = "#41464c" item.palette.shadow = "#111111" - item.palette.text = "#fcfcfc" + item.palette.text = "#b2b2b2" item.palette.toolTipBase = "#31363b" item.palette.toolTipText = "#eff0f1" item.palette.window = "#232629" diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index f176cdb..4a51dea 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -211,10 +211,24 @@ ListView { return qsTr("Sent"); } } - Flowee.Label { + QQC2.Label { anchors.top: ruler.bottom anchors.left: commentLabel.left - text: Pay.formatDate(model.date); + color: palette.text + text: { + var date = model.date; + var today = new Date(); + if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) { + let day = today.getDate(); + if (date.getDate() === day || date.getDate() === day - 1) { + // Then this is an item in the 'today' or the 'yesterday' group. + // specify more specific date/time + return date.getHours() + ":" + date.getMinutes() + } + } + + return Pay.formatDate(model.date); + } } Rectangle { -- 2.54.0 From b6b8bb407a4ac607ce07e5efd248c12d5eb81b4a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 11:59:07 +0100 Subject: [PATCH 0185/1428] Add a plus icon in front of the 'add wallet' button. --- guis/mobile/MenuOverlay.qml | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 5f3a57d..5cf0a24 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -24,6 +24,7 @@ import "../Flowee" as Flowee Item { id: root property bool open: false + clip: true onOpenChanged: if (!open) baseArea.openAccounts = false; // close the accounts when the menu is closed @@ -140,14 +141,38 @@ Item { onClicked: portfolio.current = modelData } } - TextButton { - id: textButton - text: qsTr("Add Wallet") - showPageIcon: true - onClicked: { - thePile.push("./NewAccount.qml") - root.open = false - baseArea.openAccounts = false + Item { + id: addWalletRow + width: parent.width + height: addWalletButton.height + Rectangle { + id: horizontalBar + width: 10 + height: 2 + x: 2 + color: mainWindow.palette.mid + anchors.verticalCenter:verticalBar.verticalCenter + } + Rectangle { + id: verticalBar + y: 18 // base on our height and the TextButton 10px spacing at the top + width: 2 + height: 10 + anchors.horizontalCenter: horizontalBar.horizontalCenter + color: mainWindow.palette.mid + } + TextButton { + id: addWalletButton + text: qsTr("Add Wallet") + showPageIcon: true + anchors.left: horizontalBar.right + anchors.leftMargin: 6 + anchors.right: parent.right + onClicked: { + thePile.push("./NewAccount.qml") + root.open = false + baseArea.openAccounts = false + } } } } -- 2.54.0 From 0386ae01548b4b4dae281ad53cb3f079604eeab0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 12:00:01 +0100 Subject: [PATCH 0186/1428] Export baseline. --- guis/mobile/TextButton.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 70011b2..073a07b 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -22,6 +22,7 @@ Item { id: root width: parent.width height: label.height + (smallLabel.text === "" ? 0 : smallLabel.height + 6) + 20 + baselineOffset: label.baselineOffset + 10 signal clicked property alias text: label.text -- 2.54.0 From ebae4b6bcd77d0f8d0c7c8dede7375d823d61e30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 12:37:21 +0100 Subject: [PATCH 0187/1428] Add gui-font-scaling config option. Allow the user to change the font sizing of the GUI. --- guis/mobile.qrc | 1 + guis/mobile/GuiSettings.qml | 64 +++++++++++++++++++++++++++++++++++++ guis/mobile/MenuOverlay.qml | 2 +- guis/mobile/main.qml | 14 ++++---- src/FloweePay.cpp | 5 +++ src/MenuModel.cpp | 1 + 6 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 guis/mobile/GuiSettings.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 49ab50c..d0b37c8 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -37,5 +37,6 @@ mobile/SlideToApprove.qml mobile/ReceiveTab.qml mobile/SmallAccountSelector.qml + mobile/GuiSettings.qml diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml new file mode 100644 index 0000000..69447d0 --- /dev/null +++ b/guis/mobile/GuiSettings.qml @@ -0,0 +1,64 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + headerText: qsTr("Display Settings") + id: root + + ColumnLayout { + width: parent.width + Flowee.Label { + text: qsTr("Font sizing") + } + + Row { + width: parent.width + id: fontSizing + property double buttonWidth: width / 6 + Repeater { + model: 6 + delegate: MouseArea { + width: fontSizing.buttonWidth + height: 30 + property int target: index * 25 + 75 + onClicked: Pay.fontScaling = target + + Rectangle { + width: parent.width - 5 + x: 2.5 + height: 3 + color: Pay.fontScaling == target ? root.palette.highlight : root.palette.button + } + + Flowee.Label { + font.pixelSize: 15 + text: "" + target + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + } + } + + } + + } +} diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 5cf0a24..bc45bad 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -24,7 +24,6 @@ import "../Flowee" as Flowee Item { id: root property bool open: false - clip: true onOpenChanged: if (!open) baseArea.openAccounts = false; // close the accounts when the menu is closed @@ -45,6 +44,7 @@ Item { width: 300 height: parent.height x: root.open ? 0 : 0 - width -3 + clip: true Rectangle { id: baseArea diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 2d489f2..26f6444 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -37,12 +37,14 @@ ApplicationWindow { if (!isLoading) thePile.replace("./MainView.qml"); } - Component.onCompleted: { - var scale = Pay.fontScaling; - mainWindow.font.bold = scale > 90; - mainWindow.font.fontPtSize = scale > 90; - var baseFromOS = mainWindow.font.pointSize; - mainWindow.font.pointSize = baseFromOS + 2 * (scale / 100) + Component.onCompleted: updateFontSize(); + function updateFontSize() { + // 75% = > 14.25, 100% => 19, 200% => 28 + mainWindow.font.pixelSize = 17 + (11 * (Pay.fontScaling-100) / 100) + } + Connections { + target: Pay + function onFontScalingChanged() { updateFontSize(); } } property color floweeSalmon: "#ff9d94" diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d1efeb0..d0c7b3d 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -48,6 +48,7 @@ constexpr const char *UNIT_TYPE = "unit"; constexpr const char *CREATE_START_WALLET = "create-start-wallet"; constexpr const char *WINDOW_WIDTH = "window/width"; constexpr const char *WINDOW_HEIGHT = "window/height"; +constexpr const char *FONTSCALING = "window/font-scaling"; constexpr const char *DARKSKIN = "darkSkin"; constexpr const char *HIDEBALANCE = "hideBalance"; constexpr const char *USERAGENT = "net/useragent"; @@ -110,6 +111,7 @@ FloweePay::FloweePay() m_windowWidth = defaultConfig.value(WINDOW_WIDTH, -1).toInt(); m_darkSkin = defaultConfig.value(DARKSKIN, true).toBool(); m_dspTimeout = defaultConfig.value(DSPTIMEOUT, 3000).toInt(); + m_fontScaling = defaultConfig.value(FONTSCALING, 100).toInt(); m_createStartWallet = defaultConfig.value(CREATE_START_WALLET, false).toBool(); QSettings appConfig; @@ -117,6 +119,7 @@ FloweePay::FloweePay() m_windowHeight = appConfig.value(WINDOW_HEIGHT, m_windowHeight).toInt(); m_windowWidth = appConfig.value(WINDOW_WIDTH, m_windowWidth).toInt(); m_darkSkin = appConfig.value(DARKSKIN, m_darkSkin).toBool(); + m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); @@ -697,6 +700,8 @@ void FloweePay::setFontScaling(int newFontScaling) return; m_fontScaling = newFontScaling; emit fontScalingChanged(); + QSettings appConfig; + appConfig.setValue(FONTSCALING, m_fontScaling); } PriceDataProvider *FloweePay::prices() const diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 54a7ed8..7d47ab8 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -24,6 +24,7 @@ MenuModel::MenuModel(QObject *parent) // m_root.children.append({tr("Accounts"), "AccountsList.qml", {}}); m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); m_root.children.append({tr("About"), "./About.qml", {}}); + m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); /* m_root.children.append({tr("Settings"), "", { { tr("Security"), "", {} }, -- 2.54.0 From 7af28e9d223374af75857d1976e107f8bed52d02 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 12:39:44 +0100 Subject: [PATCH 0188/1428] Use cheaper malloc on save. --- src/FloweePay.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d0c7b3d..e8251df 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -299,7 +299,7 @@ void FloweePay::loadingCompleted() void FloweePay::saveData() { - Streaming::BufferPool data; + auto &data = Streaming::pool(m_wallets.size() * 100); Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { if (wallet->encryptionSeed() != 0) @@ -314,6 +314,7 @@ void FloweePay::saveData() auto buf = builder.buffer(); + // hash the new file and check if its different lest we can skip saving const QString filebase = m_basedir + AppdataFilename; QFile origFile(filebase); if (origFile.open(QIODevice::ReadOnly)) { -- 2.54.0 From bdbbdd1726eed3d4531a7a7ee82d4301c3498239 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 14:37:58 +0100 Subject: [PATCH 0189/1428] Remove priceFor overload The downside of this method is that changes in price don't cause the result to be re-calculated. Its not very declarative, in other words. --- guis/mobile/PayWithQR.qml | 2 +- src/PriceDataProvider.h | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 646289d..c245edc 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -72,7 +72,7 @@ Page { } Flowee.FiatValueField { id: priceFiat - value: Fiat.priceFor(payment.paymentAmount) + value: Fiat.priceFor(payment.paymentAmount, Fiat.price) anchors.top: priceBch.bottom anchors.topMargin: 18 focus: true diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 0a0c927..5d6ab44 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -53,10 +53,6 @@ public: Q_INVOKABLE QString formattedPrice(double amountSats, int price) const; /// Return the price as int (in cents) for the number of sats and the given price. Q_INVOKABLE int priceFor(double amountSats, int price) const; - /// Return the price as int (in cents) for the number of sats and the current price. - Q_INVOKABLE int priceFor(double amountSats) const { - return priceFor(amountSats, m_currentPrice.price); - } /** * Return a formatted string with the locale-defined price of a fiat price from \a cents. -- 2.54.0 From c12f8cea64d61ba5609a6c4bb6e56337d1b65969 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 14:54:13 +0100 Subject: [PATCH 0190/1428] Cleanup QRScanner QML component design Avoid the ugly 'Component.onCompleted' and instead make this is a property 'autostart'. --- guis/mobile/PayWithQR.qml | 2 +- src/QRScanner.cpp | 28 ++++++++++++++++++++++++++++ src/QRScanner.h | 10 ++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index c245edc..319a487 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -28,7 +28,7 @@ Page { QRScanner { id: scanner scanType: QRScanner.PaymentDetails - Component.onCompleted: scanner.start(); + autostart: true onFinished: { var rc = scanResult if (rc === "") { // scanning failed diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index 67aaf3d..bd61e22 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -20,10 +20,19 @@ #include "FloweePay.h" #include "CameraController.h" +#include + QRScanner::QRScanner(QObject *parent) : QObject(parent), m_scanType(static_cast(100)) { + // after construction and after QML setting all the properties, run the 'completed' slot. + QTimer::singleShot(1, this, SLOT(completed())); +} + +QRScanner::~QRScanner() +{ + abort(); } void QRScanner::start() @@ -75,3 +84,22 @@ void QRScanner::resetScanResult() { setScanResult({}); } + +bool QRScanner::autostart() const +{ + return m_autostart; +} + +void QRScanner::setAutostart(bool newAutostart) +{ + if (m_autostart == newAutostart) + return; + m_autostart = newAutostart; + emit autostartChanged(); +} + +void QRScanner::completed() +{ + if (m_autostart) + start(); +} diff --git a/src/QRScanner.h b/src/QRScanner.h index 1eaa98b..034e30f 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -27,8 +27,10 @@ class QRScanner : public QObject Q_OBJECT Q_PROPERTY(ScanType scanType READ scanType WRITE setScanType NOTIFY scanTypeChanged REQUIRED) Q_PROPERTY(QString scanResult READ scanResult WRITE setScanResult RESET resetScanResult NOTIFY scanResultChanged) + Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) public: explicit QRScanner(QObject *parent = nullptr); + ~QRScanner(); Q_INVOKABLE void start(); Q_INVOKABLE void abort(); @@ -51,14 +53,22 @@ public: void setScanResult(const QString &newScanResult); void resetScanResult(); + bool autostart() const; + void setAutostart(bool newAutostart); + +private slots: + void completed(); + signals: void finished(); void scanTypeChanged(); void scanResultChanged(); + void autostartChanged(); private: ScanType m_scanType; QString m_scanResult; + bool m_autostart = false; }; #endif -- 2.54.0 From 331da1fa57b3552275cc93a7a1c2f325af6dfd09 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 14:55:03 +0100 Subject: [PATCH 0191/1428] Make scanner abort on pressing the Esc or Back keys. --- guis/mobile/QRScannerOverlay.qml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 4a47fce..e6cffa8 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -23,18 +23,12 @@ import "../Flowee" as Flowee FocusScope { id: root - focus: true visible: CameraController.visible + enabled: visible + onVisibleChanged: if (visible) root.forceActiveFocus(); - Connections { - target: CameraController - function onVisibleChanged() { - if (CameraController.visible) - root.forceActiveFocus(); - } - } Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { + if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { event.accepted = true; CameraController.abort(); } @@ -45,7 +39,6 @@ FocusScope { color: mainWindow.palette.window } - // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the // feature is actually requisted by the user. Loader { @@ -144,6 +137,7 @@ FocusScope { Camera { id: camera + active: false } CaptureSession { camera: camera @@ -151,7 +145,6 @@ FocusScope { } VideoOutput { id: videoOutput - visible: CameraController.cameraActive fillMode: VideoOutput.Stretch width: parent.width height: parent.height -- 2.54.0 From e1e7e8f14e35ef07e40b4f838f29cf3c8172632b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 15:03:43 +0100 Subject: [PATCH 0192/1428] Move account selector to the titlebar. --- guis/mobile.qrc | 1 - guis/mobile/AccountHistory.qml | 40 ++++--- guis/mobile/MainViewBase.qml | 134 +++++++++++++++++++++-- guis/mobile/MenuOverlay.qml | 2 + guis/mobile/SmallAccountSelector.qml | 155 --------------------------- 5 files changed, 144 insertions(+), 188 deletions(-) delete mode 100644 guis/mobile/SmallAccountSelector.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index d0b37c8..f5fcb88 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -36,7 +36,6 @@ mobile/PayWithQR.qml mobile/SlideToApprove.qml mobile/ReceiveTab.qml - mobile/SmallAccountSelector.qml mobile/GuiSettings.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 4a51dea..437fe7b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -63,12 +63,27 @@ ListView { */ header: ColumnLayout { id: column - spacing: 10 width: root.width - 20 x: 10 y: 10 z: 10 // make sure the wallet Selector can cover the historical items + Flowee.BitcoinAmountLabel { + opacity: Pay.hideBalance ? 0.2 : 1 + fontPixelSize: 34 + value: { + if (Pay.hideBalance) + return 88888888; + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + } + colorize: false + } + + AccountSyncState { + account: portfolio.current + width: parent.width + } + Row { width: parent.width height: 60 @@ -93,27 +108,6 @@ ListView { } } - SmallAccountSelector { - id: smallAccountSelector - width: parent.width - z: 10 - } - Flowee.BitcoinAmountLabel { - opacity: Pay.hideBalance ? 0.2 : 1 - Layout.alignment: Qt.AlignRight - value: { - if (Pay.hideBalance) - return 88888888; - return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed - } - colorize: false - } - - AccountSyncState { - account: portfolio.current - width: parent.width - } - /* TODO "Is archive" / "Unrchive"" @@ -141,6 +135,8 @@ ListView { id: label x: 10 y: 12 + font.bold: true + font.pixelSize: mainWindow.font.pixelSize * 1.1 text: portfolio.current.transactions.groupingPeriod(section); } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 546de40..0f6879e 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -65,8 +65,8 @@ QQC2.Control { Column { id: menuButton spacing: 3 - y: 12 - x: 10 + anchors.verticalCenter: parent.verticalCenter + x: 8 Repeater { model: 3 @@ -85,13 +85,15 @@ QQC2.Control { onClicked: menuOverlay.open = true; } - Item { + QQC2.Control { + id: logo // Here we just want the text part. So clip that out. clip: true - y: 10 - x: 32 + y: 17 + x: 20 width: 122 height: 21 + baselineOffset: 16 Image { source: "qrc:/FloweePay.svg" // ratio: 449 / 77 @@ -101,6 +103,117 @@ QQC2.Control { y: -5 } } + + QQC2.Label { + id: currentWalletName + visible: portfolio.current.isUserOwned || portfolio.accounts.length >= 1 + text: portfolio.current.name + color: "#fcfcfc" + clip: true + anchors.left: logo.right + anchors.right: searchIcon.left + anchors.margins: 12 + anchors.baseline: logo.baseline + + MouseArea { + anchors.fill: parent + onClicked: accountSelector.open(); + } + } + + QQC2.Label { + id: searchIcon + color: "#fcfcfc" + text: "" // placeholder for the magnifying glass search feature to be done in future + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.baseline: logo.baseline + } + + QQC2.Popup { + id: accountSelector + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent + y: header.height + width: root.width + height: columnLayout.height + + Connections { + target: menuOverlay + function onOpenChanged() { accountSelector.close(); } + } + + ColumnLayout { + id: columnLayout + width: parent.width + + Flowee.Label { + text: qsTr("Your Wallets") + font.bold: true + } + + Repeater { // portfolio holds all our accounts + width: parent.width + model: portfolio.accounts + delegate: Item { + width: columnLayout.width + height: accountName.height + lastActive.height + 6 * 3 + Rectangle { + color: root.palette.button + radius: 5 + anchors.fill: parent + visible: modelData === portfolio.current + } + Flowee.Label { + id: accountName + y: 6 + x: 6 + text: modelData.name + } + Flowee.Label { + id: fiat + anchors.top: accountName.top + anchors.right: parent.right + anchors.rightMargin: 6 + text: Fiat.formattedPrice(modelData.balanceConfirmed + modelData.balanceUnconfirmed, Fiat.price) + } + Flowee.Label { + id: lastActive + anchors.top: accountName.bottom + anchors.left: accountName.left + text: qsTr("last active") + ": " + Pay.formatDate(modelData.lastMinedTransaction) + font.pixelSize: root.font.pixelSize * 0.8 + font.bold: false + } + Flowee.BitcoinAmountLabel { + anchors.right: fiat.right + anchors.top: lastActive.top + font.pixelSize: lastActive.font.pixelSize + showFiat: false + value: modelData.balanceConfirmed + modelData.balanceUnconfirmed + colorize: false + } + MouseArea { + anchors.fill: parent + onClicked: { + portfolio.current = modelData + accountSelector.close(); + } + } + } + } + Item { + // horizontal divider. + width: parent.width + height: 1 + Rectangle { + height: 1 + width: parent.width / 10 * 7 + x: (parent.width - width) / 2 // center in column + color: root.palette.highlight + } + } + } + } } Item { @@ -115,7 +228,7 @@ QQC2.Control { Repeater { model: stack.children.length delegate: Rectangle { - height: 80 + height: 55 width: root.width / stack.children.length; color: { modelData === root.currentIndex @@ -124,17 +237,18 @@ QQC2.Control { } Image { source: stack.children[modelData].icon - width: 35 - height: 35 + width: 20 + height: 20 smooth: true - y: 12 + y: 8 anchors.horizontalCenter: parent.horizontalCenter } Flowee.Label { text: stack.children[modelData].title anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom - anchors.bottomMargin: 8 + anchors.bottomMargin: 2 + font.pixelSize: 14 } MouseArea { anchors.fill: parent diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index bc45bad..00a9aed 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -102,6 +102,8 @@ Item { text: { if (mainWindow.isLoading) return "" + if (!portfolio.current.isUserOwned || portfolio.accounts.length === 1) + return qsTr("My Wallet"); return portfolio.current.name } } diff --git a/guis/mobile/SmallAccountSelector.qml b/guis/mobile/SmallAccountSelector.qml deleted file mode 100644 index edc4276..0000000 --- a/guis/mobile/SmallAccountSelector.qml +++ /dev/null @@ -1,155 +0,0 @@ -import QtQuick -import QtQuick.Controls as QQC2 -import QtQuick.Layouts -import "../Flowee" as Flowee -import Flowee.org.pay; - -Item { - id: smallAccountSelector - height: walletSelector.height - property bool open: false - - visible: { - // if there is only an initial, not user-owned wallet, there is nothing to show here. - if (!portfolio.current.isUserOwned) - return false; - if (portfolio.accounts.length === 1) - return false; - return true; - } - - Rectangle { - id: walletSelector - height: currentWalletName.height + 14 - width: parent.width + 5 - color: "#00000000" - border.color: mainWindow.palette.button - border.width: hasMultipleWallets ? 0.7 : 0 - x: -5 - - property bool hasMultipleWallets: portfolio.accounts.length > 1 - - Flowee.Label { - x: 5 - id: currentWalletName - text: portfolio.current.name - anchors.verticalCenter: parent.verticalCenter - } - Flowee.ArrowPoint { - id: arrowPoint - visible: parent.hasMultipleWallets - color: mainWindow.palette.text - anchors.right: parent.right - anchors.rightMargin: 10 - rotation: 90 - anchors.verticalCenter: parent.verticalCenter - } - MouseArea { - anchors.fill: parent - enabled: parent.hasMultipleWallets - onClicked: { - smallAccountSelector.open = !smallAccountSelector.open - // move focus so the 'back' button will close it. - extraWallets.forceActiveFocus(); - } - } - } - - Rectangle { - id: extraWallets - width: parent.width - y: walletSelector.height - height: parent.open ? columnLayout.height + 20 : 0 - color: mainWindow.palette.base - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - clip: true - visible: height > 0 - focus: true - ColumnLayout { - y: 10 - id: columnLayout - width: parent.width - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - - Repeater { // portfolio holds all our accounts - width: parent.width - model: portfolio.accounts - delegate: Item { - width: extraWallets.width - height: accountName.height * 2 + 12 - Rectangle { - color: mainWindow.palette.button - anchors.fill: parent - visible: modelData === portfolio.current - } - Flowee.Label { - id: accountName - y: 6 - text: modelData.name - } - Flowee.Label { - id: lastActive - anchors.top: accountName.bottom - text: qsTr("last active") + ": " - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.Label { - anchors.top: lastActive.top - anchors.left: lastActive.right - text: Pay.formatDate(modelData.lastMinedTransaction) - font.pointSize: mainWindow.font.pointSize * 0.8 - font.bold: false - } - Flowee.BitcoinAmountLabel { - anchors.right: parent.right - anchors.top: accountName.top - showFiat: true - value: modelData.balanceConfirmed + modelData.balanceUnconfirmed - colorize: false - } - MouseArea { - anchors.fill: parent - onClicked: { - portfolio.current = modelData - smallAccountSelector.open = false - } - } - } - } - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: mainWindow.palette.highlight - } - } - } - - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { - event.accepted = true; - smallAccountSelector.open = false - } - } - } -} -- 2.54.0 From eadc87e64ce3b35eb41821df5e5ab9587ae57ded Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Dec 2022 22:14:25 +0100 Subject: [PATCH 0193/1428] Improve account selector in menu Working with the UX guy we massively improved the account selector. Instead of trying to shoe-horn the 'add account' under a non-existing account we realized it fits better on its own. Even the moving of it after adding one will not cause too big a problem due to the different look afterwards. The actual opened menu will also behave better if you have more than one account in this new design and more predictable. --- guis/mobile/MenuOverlay.qml | 97 ++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 00a9aed..16d49b3 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -49,8 +49,17 @@ Item { Rectangle { id: baseArea width: parent.width - height: logo.height + 20 + Math.max(currentAccountName.height, 12) + 10 - + (openAccounts ? extraOptions.height + 10: 0) + height: { + var h = logo.height + 20; + // if its opened + if (openAccounts) + h = h + extraOptions.height + 10 + // but we just don't show the accounts at all if + // this is the initial empty wallet. + if (!isLoading && portfolio.current.isUserOwned) + h = h+ Math.max(currentAccountName.height, 12) + 10 + return h; + } color: Qt.lighter(mainWindow.palette.base) property bool openAccounts: false clip: true @@ -102,8 +111,6 @@ Item { text: { if (mainWindow.isLoading) return "" - if (!portfolio.current.isUserOwned || portfolio.accounts.length === 1) - return qsTr("My Wallet"); return portfolio.current.name } } @@ -132,51 +139,25 @@ Item { ColumnLayout { id: extraOptions width: baseArea.width - 20 - anchors.top: currentAccountName.bottom - anchors.topMargin: 20 + y: logo.height + 20 + (currentAccountName.visible ? currentAccountName.height + 10 : 0) x: 10 Repeater { // portfolio holds all our accounts width: parent.width model: mainWindow.isLoading ? 0 : portfolio.accounts TextButton { text: modelData.name - onClicked: portfolio.current = modelData - } - } - Item { - id: addWalletRow - width: parent.width - height: addWalletButton.height - Rectangle { - id: horizontalBar - width: 10 - height: 2 - x: 2 - color: mainWindow.palette.mid - anchors.verticalCenter:verticalBar.verticalCenter - } - Rectangle { - id: verticalBar - y: 18 // base on our height and the TextButton 10px spacing at the top - width: 2 - height: 10 - anchors.horizontalCenter: horizontalBar.horizontalCenter - color: mainWindow.palette.mid - } - TextButton { - id: addWalletButton - text: qsTr("Add Wallet") - showPageIcon: true - anchors.left: horizontalBar.right - anchors.leftMargin: 6 - anchors.right: parent.right + visible: portfolio.current !== modelData onClicked: { - thePile.push("./NewAccount.qml") - root.open = false + portfolio.current = modelData baseArea.openAccounts = false } } } + Loader { + width: parent.width + active: !isLoading && portfolio.current.isUserOwned + sourceComponent: addWalletRow + } } Behavior on height { NumberAnimation { } } @@ -203,6 +184,11 @@ Item { } } } + Loader { + width: parent.width + active: isLoading || !portfolio.current.isUserOwned + sourceComponent: addWalletRow + } } Flowee.Label { @@ -251,4 +237,39 @@ Item { onClicked: root.open = false; } + Component { + id: addWalletRow + Item { + height: addWalletButton.height + Rectangle { + id: horizontalBar + width: 13 + height: 2 + x: 2 + color: mainWindow.palette.mid + anchors.verticalCenter: parent.verticalCenter + } + Rectangle { + id: verticalBar + width: 2 + height: 13 + anchors.horizontalCenter: horizontalBar.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: mainWindow.palette.mid + } + TextButton { + id: addWalletButton + text: qsTr("Add Wallet") + showPageIcon: true + anchors.left: horizontalBar.right + anchors.leftMargin: 6 + anchors.right: parent.right + onClicked: { + thePile.push("./NewAccount.qml") + root.open = false + baseArea.openAccounts = false + } + } + } + } } -- 2.54.0 From 993f5b1d3efe5bdf3aeb7e2612fd8bd80d43e12d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Dec 2022 11:37:13 +0100 Subject: [PATCH 0194/1428] Put the menu options in a flickable. --- guis/mobile/MenuOverlay.qml | 47 ++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 16d49b3..776988b 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -163,41 +163,50 @@ Item { Behavior on height { NumberAnimation { } } } - ColumnLayout { + Flickable { anchors.top: baseArea.bottom anchors.topMargin: 10 + anchors.bottom: versionLabel.top + contentHeight: contentLayout.height width: parent.width - 20 + clip: true + boundsBehavior: Flickable.StopAtBounds // don't show this is a flickable if there is no space issues x: 10 - - Repeater { - model: MenuModel - - TextButton { - text: model.name - showPageIcon: true - onClicked: { - var target = model.target - if (target !== "") { - thePile.push(model.target) - root.open = false; + ColumnLayout { + id: contentLayout + width: parent.width + Repeater { + model: MenuModel + TextButton { + text: model.name + showPageIcon: true + onClicked: { + var target = model.target + if (target !== "") { + thePile.push(model.target) + root.open = false; + } } } } - } - Loader { - width: parent.width - active: isLoading || !portfolio.current.isUserOwned - sourceComponent: addWalletRow + Loader { + width: parent.width + active: isLoading || !portfolio.current.isUserOwned + sourceComponent: addWalletRow + } } } Flowee.Label { + id: versionLabel + x: 10 anchors.bottom: parent.bottom anchors.bottomMargin: 5 - x: 10 text: "Flowee Pay (mobile) v" + Application.version font.pointSize: mainWindow.font.pointSize * 0.9 font.bold: false + wrapMode: Text.Wrap + width: parent.width - 20 } Behavior on x { NumberAnimation { duration: 100 } } -- 2.54.0 From 5fef6d98644dbcd651de1730fe0a0b1aa48c629a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Dec 2022 19:54:32 +0100 Subject: [PATCH 0195/1428] Add license. --- guis/ControlColors.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index 577d032..c6c792a 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ function applySkin(item) { if (Pay.useDarkSkin) -- 2.54.0 From 80200fa28cbeca6af2f185b78bf9f62665ce9132 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Dec 2022 19:57:43 +0100 Subject: [PATCH 0196/1428] Start new mobile page 'wallet information'. This shows all wallets and wallet details. Additionally, this moves the AccountTypeLabel out of the desktop page to be reusable. Not so much because its hard, but because they have translations and we'd better push shared translations into the common translation unit as opposed to duplicating it. The LabelWithClipboard now has as default context-menu-item text "Copy" instead of "Copy Address". --- guis/Flowee/AccountTypeLabel.qml | 37 ++++++ guis/Flowee/LabelWithClipboard.qml | 4 +- guis/desktop/AccountDetails.qml | 20 +-- guis/desktop/SendTransactionPane.qml | 1 + guis/mobile.qrc | 3 + guis/mobile/AccountPageListItem.qml | 177 +++++++++++++++++++++++++++ guis/mobile/AccountsList.qml | 100 +++++++++++++++ guis/mobile/VisualSeparator.qml | 29 +++++ guis/widgets.qrc | 1 + src/MenuModel.cpp | 4 +- 10 files changed, 355 insertions(+), 21 deletions(-) create mode 100644 guis/Flowee/AccountTypeLabel.qml create mode 100644 guis/mobile/AccountPageListItem.qml create mode 100644 guis/mobile/AccountsList.qml create mode 100644 guis/mobile/VisualSeparator.qml diff --git a/guis/Flowee/AccountTypeLabel.qml b/guis/Flowee/AccountTypeLabel.qml new file mode 100644 index 0000000..b76e5b0 --- /dev/null +++ b/guis/Flowee/AccountTypeLabel.qml @@ -0,0 +1,37 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +/// Simple label which will show as text the type of account provided it. +Label { + id: root + + property QtObject account: null + + wrapMode: Text.WordWrap + visible: root.account.isDecrypted || root.account.needsPinToPay + font.italic: true + text: { + if (root.account.isSingleAddressAccount) + return qsTr("This wallet is a single-address wallet.") + if (root.account.isHDWallet) + return qsTr("This wallet is based on a HD seed-phrase") + // ok we only have one more type so far, so this is rather simple... + return qsTr("This wallet is a simple multiple-address wallet.") + } +} diff --git a/guis/Flowee/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml index be542ca..a622503 100644 --- a/guis/Flowee/LabelWithClipboard.qml +++ b/guis/Flowee/LabelWithClipboard.qml @@ -20,11 +20,11 @@ import QtQuick.Controls 2.11 as QQC2 Label { id: root - elide: Text.ElideMiddle + elide: wrapMode === Text.NoWrap ? Text.ElideMiddle : Text.ElideNone // override the text to be copied to clipboard property string clipboardText: "" - property string menuText: qsTr("Copy Address") + property string menuText: qsTr("Copy") MouseArea { anchors.fill: parent diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index a6dd2df..09f3276 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -112,21 +112,9 @@ Item { return qsTr("Sync Status") + ": " + height + " / " + Pay.chainHeight + time; } } - Label { + Flowee.AccountTypeLabel { Layout.columnSpan: 2 - id: walletType - visible: root.account.isDecrypted || root.account.needsPinToPay - font.italic: true - text: { - if (root.account.isSingleAddressAccount) - return qsTr("This wallet is a single-address wallet.") - - if (root.account.isHDWallet) - return qsTr("This wallet is based on a HD seed-phrase") - - // ok we only have one more type so far, so this is rather simple... - return qsTr("This wallet is a simple multiple-address wallet.") - } + account: root.account } Label { id: xpubLabel @@ -138,8 +126,6 @@ Item { Layout.fillWidth: true visible: xpubLabel.visible text: root.account.xpub - clipboardText: text - menuText: qsTr("Copy") } Label { @@ -251,6 +237,7 @@ Item { y: 5 x: root.account.isHDWallet ? 50 : 0 text: address + menuText: qsTr("Copy Address") } Flowee.BitcoinAmountLabel { @@ -372,7 +359,6 @@ Item { } Flowee.LabelWithClipboard { text: root.account.hdDerivationPath - menuText: qsTr("Copy") } } Label { diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 5c95da3..96db1a4 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -332,6 +332,7 @@ Item { text: paymentDetail.formattedTarget horizontalAlignment: Qt.AlignRight font.italic: true + menuText: qsTr("Copy Address") } Item { id: addressInfo diff --git a/guis/mobile.qrc b/guis/mobile.qrc index f5fcb88..c3e6fba 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -37,5 +37,8 @@ mobile/SlideToApprove.qml mobile/ReceiveTab.qml mobile/GuiSettings.qml + mobile/AccountsList.qml + mobile/AccountPageListItem.qml + mobile/VisualSeparator.qml diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml new file mode 100644 index 0000000..c5de5de --- /dev/null +++ b/guis/mobile/AccountPageListItem.qml @@ -0,0 +1,177 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +QQC2.Control { + id: root + height: column.height + + property QtObject account: null + + ColumnLayout { + id: column + width: parent.width + + Item { + id: nameRow + width: parent.width + height: accountName.height + 10 + y: 5 + + Flowee.Label { + id: accountName + text: account.name + font.pixelSize: root.font.pixelSize * 1.1 + } + + Rectangle { + width: 20 + visible: false // TODO make this work. + height: 20 + anchors.left: accountName.width > root.width ? accountName.left : accountName.right + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + // onClicked: // TODO start editing the name + } + } + } + + Flowee.AccountTypeLabel { + Layout.fillWidth: true + account: root.account + } + TextButton { + Layout.fillWidth: true + text: qsTr("Primary Wallet") + onClicked: root.account.isDefaultWallet = !root.account.isDefaultWallet + + Flowee.CheckBox { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + checked: root.account.isDefaultWallet + onClicked: root.account.isDefaultWallet = checked + } + } + + TextButton { + Layout.fillWidth: true + visible: !root.account.needsPinToOpen + showPageIcon: true + text: { + if (!root.account.needsPinToPay) + return qsTr("Enable Pin to Pay") + if (!root.account.needsPinToOpen) + return qsTr("Enable Pin to Open") + return ""; // already fully encrypted + } + subtext: { + if (root.account.needsPinToPay) + return qsTr("Pin to Pay is enabled"); + return ""; + } + + onClicked: {} // TODO + } + /* + TODO Give opportunity to decrypt here. + */ + + /* + TODO archived wallets functionality + */ + + TextButton { + text: qsTr("Wallet Settings") + showPageIcon: true + Layout.fillWidth: true + onClicked: {} // TODO + } + + TextButton { + text: qsTr("Backup information") + showPageIcon: true + Layout.fillWidth: true + onClicked: thePile.push(root.account.isHDWallet ? hdBackupDetails : backupDetails); + + Component { + id: hdBackupDetails + Page { + headerText: qsTr("Backup Details") + ColumnLayout { + width: parent.width + Flowee.Label { text: "xpub" } + Flowee.LabelWithClipboard { + id: xpub + Layout.fillWidth: true + text: root.account.xpub + } + VisualSeparator { } + Flowee.Label { + text: qsTr("Wallet seed-phrase") + } + Flowee.LabelWithClipboard { + Layout.fillWidth: true + text: root.account.mnemonic + wrapMode: Text.Wrap + } + VisualSeparator { } + Flowee.Label { text: qsTr("Derivation Path") } + Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } + } + } + } + + Component { + id: backupDetails + Page { + headerText: qsTr("Backup Details") + ColumnLayout { + width: parent.width + Flowee.Label { text: "TODO" } + } + } + } + } + } + + + /* + Q_PROPERTY(double balanceConfirmed READ balanceConfirmed NOTIFY balanceChanged) + Q_PROPERTY(double balanceUnconfirmed READ balanceUnconfirmed NOTIFY balanceChanged) + Q_PROPERTY(double balanceImmature READ balanceImmature NOTIFY balanceChanged) + + Q_PROPERTY(int unspentOutputCount READ unspentOutputCount NOTIFY utxosChanged) + Q_PROPERTY(int historicalOutputCount READ historicalOutputCount NOTIFY utxosChanged) + Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) + Q_PROPERTY(int initialBlockHeight READ initialBlockHeight NOTIFY lastBlockSynchedChanged) + Q_PROPERTY(QDateTime lastBlockSynchedTime READ lastBlockSynchedTime NOTIFY lastBlockSynchedChanged) + Q_PROPERTY(QString timeBehind READ timeBehind NOTIFY lastBlockSynchedChanged) + Q_PROPERTY(WalletSecretsModel* secrets READ secretsModel NOTIFY modelsChanged) + Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) + Q_PROPERTY(QDateTime lastMinedTransaction READ lastMinedTransaction NOTIFY balanceChanged) + Q_PROPERTY(bool isDecrypted READ isDecrypted NOTIFY encryptionChanged) + */ + + +} diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml new file mode 100644 index 0000000..57ba376 --- /dev/null +++ b/guis/mobile/AccountsList.qml @@ -0,0 +1,100 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Page { + id: root + headerText: qsTr("Wallet Information") + + ListView { + id: tabBar + model: { + var accounts = portfolio.accounts; + for (let a of portfolio.archivedAccounts) { + accounts.push(a); + } + return accounts; + } + + orientation: Qt.Horizontal + width: parent.width + anchors.top: parent.top + height: 50 + boundsBehavior: Flickable.DragOverBounds + + delegate: Rectangle { + color: { + if (index == tabBar.currentIndex) + return "#1a1a1a" + return "#303030" + } + width: Math.max(tabName.width, 120) + height: tabName.height + 20 + + Text { + id: tabName + color: index == tabBar.currentIndex ? "white" : "#c2c2c2" + text: modelData.name + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: { + // fast move + accountPageListView.positionViewAtIndex(index, ListView.Beginning) + // slow scroll + tabBar.currentIndex = index; + accountPageListView.currentIndex = index; + } + } + } + } + + ListView { + id: accountPageListView + anchors.top: tabBar.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + orientation: Qt.Horizontal + model: tabBar.model + snapMode: ListView.SnapToItem + boundsBehavior: Flickable.DragOverBounds + clip: true + cacheBuffer: 2 + onCurrentIndexChanged: tabBar.currentIndex = currentIndex + onContentXChanged: { + var pos = contentX; + let index = pos / width; + if (index !== Math.floor(index)) // its moving, ignore + return; + currentIndex = index; + } + + delegate: Flickable { + flickableDirection: Flickable.VerticalFlick + boundsBehavior: Flickable.DragOverBounds + width: accountPageListView.width + height: accountPageListView.height + AccountPageListItem { + width: accountPageListView.width + account: modelData + } + } + } +} diff --git a/guis/mobile/VisualSeparator.qml b/guis/mobile/VisualSeparator.qml new file mode 100644 index 0000000..84eea5d --- /dev/null +++ b/guis/mobile/VisualSeparator.qml @@ -0,0 +1,29 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Item { + height: 21 + width: parent.width + Rectangle { + width: parent.width * 0.8 + height: 1.3 + anchors.centerIn: parent + color: mainWindow.palette.mid + } +} diff --git a/guis/widgets.qrc b/guis/widgets.qrc index ab38b4a..fb89aa7 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -30,5 +30,6 @@ Flowee/ObjectShaker.qml Flowee/BroadcastFeedback.qml Flowee/ImageButton.qml + Flowee/AccountTypeLabel.qml diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 7d47ab8..0d2b70c 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -21,10 +21,10 @@ MenuModel::MenuModel(QObject *parent) : QAbstractListModel{parent}, m_current(&m_root) { - // m_root.children.append({tr("Accounts"), "AccountsList.qml", {}}); + m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); + m_root.children.append({tr("Wallet Information"), "AccountsList.qml", {}}); m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); m_root.children.append({tr("About"), "./About.qml", {}}); - m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); /* m_root.children.append({tr("Settings"), "", { { tr("Security"), "", {} }, -- 2.54.0 From 3542fcb2840bb74f434650062acb1129e777a4cc Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Dec 2022 19:59:31 +0100 Subject: [PATCH 0197/1428] Make page label use full space when button is invisible --- guis/mobile/Page.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index d60e248..54aaeb3 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -61,7 +61,7 @@ QQC2.Control { id: headerLabel color: "white" anchors.left: backButton.right - anchors.right: headerButton.left + anchors.right: headerButton.visible ? headerButton.left : parent.right horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } -- 2.54.0 From dd03750b1559c101f26e0689fee8e337c604253f Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Dec 2022 20:19:23 +0100 Subject: [PATCH 0198/1428] Fixes in wallet selection Make the currently active wallet be the one we show initially. Also include the empty / initial wallet in the view if its the only one there is --- guis/mobile/AccountsList.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 57ba376..dbeee12 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -21,6 +21,15 @@ Page { id: root headerText: qsTr("Wallet Information") + function indexOfCurrentAccount() { + var list = tabBar.model; + var cur = portfolio.current + for (let i = 0; i < list.length; i = i + 1) { + if (list[i] === cur) return i; + } + return 0; + } + ListView { id: tabBar model: { @@ -28,6 +37,8 @@ Page { for (let a of portfolio.archivedAccounts) { accounts.push(a); } + if (accounts.length === 0) + accounts.push(portfolio.current) return accounts; } @@ -35,7 +46,8 @@ Page { width: parent.width anchors.top: parent.top height: 50 - boundsBehavior: Flickable.DragOverBounds + boundsBehavior: Flickable.StopAtBounds + currentIndex: indexOfCurrentAccount(); delegate: Rectangle { color: { @@ -77,6 +89,7 @@ Page { boundsBehavior: Flickable.DragOverBounds clip: true cacheBuffer: 2 + currentIndex: indexOfCurrentAccount(); onCurrentIndexChanged: tabBar.currentIndex = currentIndex onContentXChanged: { var pos = contentX; -- 2.54.0 From 6dcc4496ef7064a94db6496b94c3d8f1fe6ea5ba Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 11:57:04 +0100 Subject: [PATCH 0199/1428] Make the progressbar go away on reaching full sync --- guis/mobile/AccountSyncState.qml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 1fac425..40f099b 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -22,22 +22,28 @@ import QtQuick.Shapes // for shape-path Item { id: root - height: indicator.height + 3 + (done ? 0 : circleShape.height) + height: indicator.height + 3 + (root.uptodate ? 0 : circleShape.height) property QtObject account: null - property bool done: false + property bool uptodate: false property int startPos: account.initialBlockHeight - onAccountChanged: { + onAccountChanged: checkIfDone(); + function checkIfDone() { let startPos = account.initialBlockHeight; if (startPos === -2) { // special number, the account is just created and waits for transactions. - done = true; + uptodate = true; return; } let end = Pay.expectedChainHeight; let currentPos = account.lastBlockSynched; // only show the progress-circle when its more than 2 days behind // and we are not synched - done = end - startPos < 300 || end - currentPos <= 6; + uptodate = end - startPos < 300 || end - currentPos <= 6; + } + + Connections { + target: account + function onLastBlockSynchedChanged() { checkIfDone(); } } // The 'progress' circle. @@ -45,7 +51,7 @@ Item { id: circleShape property int goalHeight: Pay.expectedChainHeight - visible: !root.done + visible: !root.uptodate anchors.horizontalCenter: parent.horizontalCenter width: 200 height: 100 @@ -70,8 +76,8 @@ Item { return 0; let currentPos = root.account.lastBlockSynched; let totalDistance = end - startPos; - if (totalDistance == 0) - return 180; // done + if (totalDistance <= 6) + return 180; // uptodate let ourProgress = currentPos - startPos; return 180 * (ourProgress / totalDistance); } @@ -84,7 +90,7 @@ Item { Flowee.Label { id: indicator width: parent.width - y: root.done ? 0 : circleShape.height + 3 + y: root.uptodate ? 0 : circleShape.height + 3 wrapMode: Text.Wrap text: { var buddy = qsTr("Network Status") + ": "; -- 2.54.0 From a322ad5be4bb1d0245ffb2e8b2060bdebbc65f21 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 13:34:18 +0100 Subject: [PATCH 0200/1428] Move CheckBox to extend the control template Interaction for a checkbox has basically been done already in the QQC2 template, so avoid duplicating work and just inherit from it. This fixes a UX issue when sometimes a checkbox loses its binding on click. --- guis/Flowee/CheckBox.qml | 64 ++++++++++------------------------------ 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 4543114..f1ea8d2 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -15,22 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Templates as T + +T.CheckBox { + id: control -Item { - id: root - property bool checked: false property bool sliderOnIndicator: true - property alias text: title.text property string tooltipText: "" - signal clicked; + implicitWidth: slider.width + 6 + title.width + (tooltipText === "" ? 0 : (questionMarkIcon.width + 16)) implicitHeight: Math.max(slider.implicitHeight, title.implicitHeight) clip: true - activeFocusOnTab: true - Item { + indicator: Item { id: slider width: implicitWidth height: implicitHeight @@ -40,8 +39,8 @@ Item { Rectangle { anchors.fill: parent radius: parent.height / 3 - color: root.sliderOnIndicator && root.enabled && root.checked ? (Pay.useDarkSkin ? "#4f7d63" : "#9ec7af") : mainWindow.palette.window - border.color: root.activeFocus ? mainWindow.palette.highlight : mainWindow.palette.button + color: control.sliderOnIndicator && control.enabled && control.checked ? (Pay.useDarkSkin ? "#4f7d63" : "#9ec7af") : mainWindow.palette.window + border.color: control.activeFocus ? mainWindow.palette.highlight : mainWindow.palette.button border.width: 0.8 Behavior on color { ColorAnimation {}} @@ -50,12 +49,12 @@ Item { width: parent.height / 5 * 4 height: width radius: width - x: root.checked ? parent.width - width - 3 : 3 + x: control.checked ? parent.width - width - 3 : 3 anchors.verticalCenter: parent.verticalCenter color: { - if (!root.enabled) + if (!control.enabled) return "darkgray" - if (root.checked && Pay.useDarkSkin) + if (control.checked && Pay.useDarkSkin) return mainWindow.palette.windowText; return mainWindow.palette.highlight } @@ -64,26 +63,9 @@ Item { } } - MouseArea { - id: mousy - width: slider.width + 6 + title.width - height: parent.height - cursorShape: Qt.PointingHandCursor - onClicked: { - root.checked = !root.checked - root.clicked() - } - hoverEnabled: true - - QQC2.ToolTip { - parent: root - text: root.tooltipText - delay: 1500 - visible: mousy.containsMouse && root.tooltipText !== "" - } - } Label { id: title + text: control.text anchors.left: slider.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 6 @@ -92,7 +74,7 @@ Item { Rectangle { id: questionMarkIcon - visible: root.tooltipText !== "" && root.enabled + visible: control.tooltipText !== "" && control.enabled width: q.width + 14 height: width anchors.left: title.right @@ -110,22 +92,8 @@ Item { anchors.margins: -7 cursorShape: Qt.PointingHandCursor id: clicky - onClicked: QQC2.ToolTip.show(root.tooltipText, 15000); + onClicked: QQC2.ToolTip.show(control.tooltipText, 15000); } } } - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Space || event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { - root.checked = !root.checked - event.accepted = true - } - else if (root.checked && event.key === Qt.Key_Left) { - root.checked = false; - event.accepted = true - } - else if (!root.checked && event.key === Qt.Key_Right) { - root.checked = true; - event.accepted = true - } - } } -- 2.54.0 From 40c3222f4a052d989a816472cdf6ff50405b1cd5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 14:06:27 +0100 Subject: [PATCH 0201/1428] Rename property and minor UX cleanups Rename AccountInfo property 'isDefautWallet' to 'isPrimaryAccount'. --- guis/desktop/AccountConfigMenu.qml | 4 +-- guis/mobile/AccountPageListItem.qml | 55 ++++++++++++----------------- guis/mobile/AccountsList.qml | 6 ++-- src/AccountInfo.cpp | 12 ++++--- src/AccountInfo.h | 12 +++---- src/PortfolioDataProvider.cpp | 18 ++++++---- 6 files changed, 52 insertions(+), 55 deletions(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index eeaf08c..27d4e99 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -34,9 +34,9 @@ ConfigItem { onTriggered: root.account.isArchived = !root.account.isArchived } property QtObject primaryAction: Action { - enabled: root.account != null && !root.account.isDefaultWallet + enabled: root.account != null && !root.account.isPrimaryAccount text: enabled ? qsTr("Make Primary") : qsTr("★ Primary") - onTriggered: root.account.isDefaultWallet = !root.account.isDefaultWallet + onTriggered: root.account.isPrimaryAccount = !root.account.isPrimaryAccount } property QtObject encryptAction: Action { text: qsTr("Protect With Pin...") diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index c5de5de..fb13cb7 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -31,49 +31,38 @@ QQC2.Control { id: column width: parent.width - Item { - id: nameRow - width: parent.width - height: accountName.height + 10 - y: 5 - - Flowee.Label { - id: accountName - text: account.name - font.pixelSize: root.font.pixelSize * 1.1 - } - - Rectangle { - width: 20 - visible: false // TODO make this work. - height: 20 - anchors.left: accountName.width > root.width ? accountName.left : accountName.right - - MouseArea { - anchors.fill: parent - anchors.margins: -10 - // onClicked: // TODO start editing the name - } - } - } - Flowee.AccountTypeLabel { Layout.fillWidth: true account: root.account } + Flowee.TextField { + text: account.name + onTextChanged: root.account.name = text + Layout.fillWidth: true + } TextButton { + visible: root.account.isUserOwned Layout.fillWidth: true text: qsTr("Primary Wallet") - onClicked: root.account.isDefaultWallet = !root.account.isDefaultWallet + onClicked: if (!root.account.isArchived) root.account.isPrimaryAccount = !root.account.isPrimaryAccount Flowee.CheckBox { + enabled: !root.account.isArchived anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - checked: root.account.isDefaultWallet - onClicked: root.account.isDefaultWallet = checked + checked: root.account.isPrimaryAccount + onClicked: root.account.isPrimaryAccount = checked + } + + Connections { + target: root.account + function onIsPrimaryAccountChanged() { + console.log("prim changed " + root.account.name + " = " + root.account.isPrimaryAccount) + } } } + /* TextButton { Layout.fillWidth: true visible: !root.account.needsPinToOpen @@ -92,7 +81,7 @@ QQC2.Control { } onClicked: {} // TODO - } + } */ /* TODO Give opportunity to decrypt here. */ @@ -101,12 +90,13 @@ QQC2.Control { TODO archived wallets functionality */ + /* // TODO TextButton { text: qsTr("Wallet Settings") showPageIcon: true Layout.fillWidth: true - onClicked: {} // TODO - } + onClicked: {} + }*/ TextButton { text: qsTr("Backup information") @@ -130,6 +120,7 @@ QQC2.Control { Flowee.Label { text: qsTr("Wallet seed-phrase") } + // TODO allow showing the seed phrase as a QR Flowee.LabelWithClipboard { Layout.fillWidth: true text: root.account.mnemonic diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index dbeee12..7fe3c33 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -51,7 +51,7 @@ Page { delegate: Rectangle { color: { - if (index == tabBar.currentIndex) + if (index === tabBar.currentIndex) return "#1a1a1a" return "#303030" } @@ -60,7 +60,7 @@ Page { Text { id: tabName - color: index == tabBar.currentIndex ? "white" : "#c2c2c2" + color: index === tabBar.currentIndex ? "white" : "#c2c2c2" text: modelData.name anchors.centerIn: parent } @@ -86,7 +86,7 @@ Page { orientation: Qt.Horizontal model: tabBar.model snapMode: ListView.SnapToItem - boundsBehavior: Flickable.DragOverBounds + boundsBehavior: Flickable.StopAtBounds clip: true cacheBuffer: 2 currentIndex: indexOfCurrentAccount(); diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 5bf6d9d..81d9919 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -256,18 +256,20 @@ bool AccountInfo::isDecrypted() const return false; } -void AccountInfo::setDefaultWallet(bool isDefault) +void AccountInfo::setPrimaryAccount(bool isPrimary) { auto segment = m_wallet->segment(); if (!segment) return; - if ((segment->priority() == PrivacySegment::First) == isDefault) + if ((segment->priority() == PrivacySegment::First) == isPrimary) return; - segment->setPriority(isDefault ? PrivacySegment::First : PrivacySegment::Normal); - emit isDefaultWalletChanged(); + segment->setPriority(isPrimary ? PrivacySegment::First : PrivacySegment::Normal); + // emit also notifies the portfolio manager that makes sure + // other accounts get their primary-ness unset. There can be only one! + emit isPrimaryAccountChanged(); } -bool AccountInfo::isDefaultWallet() +bool AccountInfo::isPrimaryAccount() const { auto segment =m_wallet->segment(); if (!segment) diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 643e767..f43b6d5 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -52,7 +52,7 @@ class AccountInfo : public QObject Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(WalletHistoryModel* transactions READ historyModel NOTIFY modelsChanged) Q_PROPERTY(WalletSecretsModel* secrets READ secretsModel NOTIFY modelsChanged) - Q_PROPERTY(bool isDefaultWallet READ isDefaultWallet WRITE setDefaultWallet NOTIFY isDefaultWalletChanged) + Q_PROPERTY(bool isPrimaryAccount READ isPrimaryAccount WRITE setPrimaryAccount NOTIFY isPrimaryAccountChanged) Q_PROPERTY(bool isUserOwned READ userOwnedWallet NOTIFY userOwnedChanged) Q_PROPERTY(bool isSingleAddressAccount READ isSingleAddressAccount NOTIFY encryptionChanged) Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) @@ -97,11 +97,11 @@ public: WalletSecretsModel* secretsModel(); /** - * Sets a wallet to be the first to open (aka default) wallet. + * Sets a account to be the first to open (aka primary). */ - void setDefaultWallet(bool isDefault); - /// returns if a wallet is the main, first, wallet - bool isDefaultWallet(); + void setPrimaryAccount(bool isPrimary); + /// returns if a wallet is the Primary wallet + bool isPrimaryAccount() const; // maps to Wallet::userOwnedWallet bool userOwnedWallet(); @@ -163,7 +163,7 @@ signals: void nameChanged(); void lastBlockSynchedChanged(); void timeBehindChanged(); - void isDefaultWalletChanged(); + void isPrimaryAccountChanged(); void paymentRequestsChanged(); void userOwnedChanged(); void isArchivedChanged(); diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index f9a8ed9..0f221c5 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -154,7 +154,7 @@ void PortfolioDataProvider::addWalletAccount(Wallet *wallet) m_accounts.append(wallet); auto info = new AccountInfo(wallet, this); m_accountInfos.append(info); - connect (info, SIGNAL(isDefaultWalletChanged()), this, SLOT(walletChangedPriority())); + connect (info, SIGNAL(isPrimaryAccountChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(isArchivedChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(balanceChanged()), this, SIGNAL(totalBalanceChanged())); emit accountsChanged(); @@ -162,15 +162,19 @@ void PortfolioDataProvider::addWalletAccount(Wallet *wallet) void PortfolioDataProvider::walletChangedPriority() { - AccountInfo *wallet = qobject_cast(sender()); + const AccountInfo * const wallet = qobject_cast(sender()); if (!wallet) return; - if (wallet->isDefaultWallet()) { + if (wallet->isPrimaryAccount()) { // as this just changed, through the UI, we have to mark any other - // wallet that was a default wallet as no longer being one. - for (auto &info : m_accountInfos) { - if (info != wallet && info->isDefaultWallet()) - info->setDefaultWallet(false); + // account that was the primary wallet as no longer being one. + for (auto iter = m_accountInfos.begin(); iter != m_accountInfos.end(); ++iter) { + // for (auto &info : m_accountInfos) { + logFatal() << (*iter)->name(); + if (*iter != wallet && (*iter)->isPrimaryAccount()) { + logFatal() << " setting to false"; + (*iter)->setPrimaryAccount(false); + } } } if (wallet == current() && wallet->isArchived()) { -- 2.54.0 From 906f90e5c210aabc152f623447ee9adbabecabb9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 16:06:19 +0100 Subject: [PATCH 0202/1428] Re-use secrets view in mobile 'backup' screen. This moves the desktop account-details specific WalletSecretsView component to live in the common area and we then use it from the backup view for a wallet in mobile. The only change is that we automatically detect if the content is too wide to fit and we split it over 3 lines instead of 2. --- guis/Flowee/WalletSecretsView.qml | 159 ++++++++++++++++++++++++++++ guis/desktop/AccountDetails.qml | 103 +----------------- guis/mobile/AccountPageListItem.qml | 43 +++++++- guis/widgets.qrc | 1 + 4 files changed, 200 insertions(+), 106 deletions(-) create mode 100644 guis/Flowee/WalletSecretsView.qml diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml new file mode 100644 index 0000000..6ad4c52 --- /dev/null +++ b/guis/Flowee/WalletSecretsView.qml @@ -0,0 +1,159 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 + +ListView { + id: root + required property QtObject account; + + implicitHeight: root.account.isSingleAddressAccount ? contentHeight : scrollablePage.height / 10 * 7 + clip: true + model: root.account.secrets + delegate: Rectangle { + id: delegateRoot + color: (index % 2) == 0 ? root.palette.base : root.palette.alternateBase + width: ListView.view.width + height: addressLabel.height + 6 + amountLabel.height + 6 + (lineCount === 3 ? coinCountLabel.height + 6: 0) + 12 + + property int lineCount: { + var x = coinCountLabel.x + coinCountLabel.width + 10 + if (schnorrRect.visible) + x += schnorrRect.width; + if (delegateRoot.width < x + amountLabel.width) // they don't fit. + return 3; + return 2; + } + + Label { + text: hdIndex + anchors.baseline: addressLabel.baseline + anchors.right: addressLabel.left + anchors.rightMargin: 10 + visible: root.account.isHDWallet + } + + LabelWithClipboard { + id: addressLabel + y: 5 + x: root.account.isHDWallet ? 50 : 5 + anchors.bottomMargin: 6 + text: address + anchors.left: parent.left + anchors.right: hamburgerMenu.left + anchors.rightMargin: 6 + menuText: qsTr("Copy Address") + } + + Item { + id: hamburgerMenu + width: 12 + height: column.height + anchors.right: parent.right + anchors.bottom: addressLabel.bottom + Column { + id: column + spacing: 3 + y: 1 // move the column down to account for the anti-alias line of the rectangle below + + Repeater { // hamburger + model: 3 + delegate: Rectangle { + color: root.palette.windowText + width: 12 + height: 3 + radius: 2 + } + } + } + MouseArea { + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true // to make sure we eat them and avoid the hover feedback. + acceptedButtons: Qt.RightButton | Qt.LeftButton + cursorShape: Qt.PointingHandCursor + QQC2.Menu { + id: ourMenu + QQC2.Action { + text: qsTr("Copy Address") + onTriggered: Pay.copyToClipboard(address) + } + QQC2.Action { + text: qsTr("Copy Private Key") + onTriggered: Pay.copyToClipboard(privatekey) + } + } + onClicked: ourMenu.popup() + } + } + + BitcoinAmountLabel { + id: amountLabel + value: balance + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + } + Label { + id: coinCountLabel + x: root.width > 400 ? delegateRoot.width / 4 : 35; + y: { + if (delegateRoot.lineCount === 2) { + // then this is on the same line as the amountLabel + return amountLabel.y + amountLabel.baselineOffset - baselineOffset; + } + // then we place ourselves below the address + return addressLabel.y + addressLabel.height + 6 + } + text: qsTr("Coins: %1 / %2").arg(numCoins).arg(historicalCoinCount) + } + Rectangle { + id: schnorrRect + visible: usedSchnorr + width: schnorrIndicator.height + height: width + radius: width / 2 + anchors.left: coinCountLabel.right + anchors.leftMargin: 6 + anchors.verticalCenter: coinCountLabel.verticalCenter + anchors.margins: -4 + color: Pay.useDarkSkin ? "#838383" : "#ccc" + Label { + id: schnorrIndicator + anchors.centerIn: parent + font.bold: true + text: "S" + MouseArea { + id: mousy + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true + + QQC2.ToolTip { + parent: schnorrIndicator + text: qsTr("Signed with Schnorr signatures in the past") + delay: 700 + visible: mousy.containsMouse + } + } + } + } + + + } +} diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 09f3276..43667c1 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -211,114 +211,15 @@ Item { onClicked: root.account.secrets.showUsedAddresses = checked tooltipText: qsTr("Switches between unused and used Bitcoin addresses") } - ListView { + Flowee.WalletSecretsView { id: historyView - model: root.account.secrets Layout.fillWidth: true - implicitHeight: root.account.isSingleAddressAccount ? contentHeight : scrollablePage.height / 10 * 7 - clip: true - delegate: Rectangle { - id: delegateRoot - color: (index % 2) == 0 ? mainWindow.palette.base : mainWindow.palette.alternateBase - width: ListView.view.width + 10 - x: -5 - height: addressLabel.height + 10 + amountLabel.height + 10 - - Label { - text: hdIndex - anchors.baseline: addressLabel.baseline - anchors.right: addressLabel.left - anchors.rightMargin: 10 - visible: root.account.isHDWallet - } - - Flowee.LabelWithClipboard { - id: addressLabel - y: 5 - x: root.account.isHDWallet ? 50 : 0 - text: address - menuText: qsTr("Copy Address") - } - - Flowee.BitcoinAmountLabel { - id: amountLabel - value: balance - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - } - Label { - id: coinCountLabel - anchors.left: parent.left - anchors.leftMargin: delegateRoot.width / 4 - anchors.baseline: amountLabel.baseline - text: qsTr("Coins: %1 / %2").arg(numCoins).arg(historicalCoinCount) - } - Rectangle { - visible: usedSchnorr - width: schnorrIndicator.height - height: width - radius: width / 2 - anchors.left: coinCountLabel.right - anchors.leftMargin: 10 - anchors.verticalCenter: amountLabel.verticalCenter - anchors.margins: -4 - color: Pay.useDarkSkin ? "#838383" : "#ccc" - Label { - id: schnorrIndicator - anchors.centerIn: parent/// .verticalCenter - font.bold: true - text: "S" - MouseArea { - id: mousy - anchors.fill: parent - anchors.margins: -10 - hoverEnabled: true - - ToolTip { - parent: schnorrIndicator - text: qsTr("Signed with Schnorr signatures in the past") - delay: 700 - visible: mousy.containsMouse - } - } - } - } - - ConfigItem { - id: button - anchors.right: parent.right - wide: true - y: 5 - property QtObject copyAddress: Action { - text: qsTr("Copy Address") - onTriggered: Pay.copyToClipboard(address) - } - property QtObject copyPrivKey: Action { - text: qsTr("Copy Private Key") - onTriggered: Pay.copyToClipboard(privatekey) - } - /* - Action { - text: qsTr("Move to New Wallet") - onTriggered: ; - } */ - onAboutToOpen: { - var items = []; - items.push(copyAddress); - if (root.account.isDecrypted) - items.push(copyPrivKey); - setMenuActions(items) - } - } - } ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 20 / activityView.height visible: size < 0.9 } - + account: root.account } } Flowee.GroupBox { diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index fb13cb7..9878ab1 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -110,15 +110,28 @@ QQC2.Control { headerText: qsTr("Backup Details") ColumnLayout { width: parent.width - Flowee.Label { text: "xpub" } + Flowee.Label { text: "xpub" + ":" } Flowee.LabelWithClipboard { id: xpub Layout.fillWidth: true text: root.account.xpub } VisualSeparator { } + Flowee.Label { - text: qsTr("Wallet seed-phrase") + Layout.fillWidth: true + text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile.") + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Flowee.Label { + Layout.fillWidth: true + text: qsTr("Important: Never share your seed-phrase with others!") + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Flowee.Label { + text: qsTr("Wallet seed-phrase") + ":" } // TODO allow showing the seed phrase as a QR Flowee.LabelWithClipboard { @@ -127,7 +140,7 @@ QQC2.Control { wrapMode: Text.Wrap } VisualSeparator { } - Flowee.Label { text: qsTr("Derivation Path") } + Flowee.Label { text: qsTr("Derivation Path") + ":" } Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } } } @@ -137,9 +150,29 @@ QQC2.Control { id: backupDetails Page { headerText: qsTr("Backup Details") - ColumnLayout { + Flowee.CheckBox { + id: usedAddresses + anchors.top: parent.top width: parent.width - Flowee.Label { text: "TODO" } + text: qsTr("Used Addresses"); + visible: !root.account.isSingleAddressAccount + onClicked: root.account.secrets.showUsedAddresses = checked + tooltipText: qsTr("Switches between unused and used Bitcoin addresses") + } + + Flowee.WalletSecretsView { + anchors.top: usedAddresses.visible ? usedAddresses.bottom : parent.top + anchors.topMargin: 10 + anchors.bottom: parent.bottom + width: parent.width + account: root.account + /* + ScrollBar: { + id: thumb + minimumSize: 20 / activityView.height + visible: size < 0.9 + }*/ + clip: true } } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index fb89aa7..dc2ba8b 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -31,5 +31,6 @@ Flowee/BroadcastFeedback.qml Flowee/ImageButton.qml Flowee/AccountTypeLabel.qml + Flowee/WalletSecretsView.qml -- 2.54.0 From 7917c8c70e21addb6de47e14737f972229909dde Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 16:14:12 +0100 Subject: [PATCH 0203/1428] Show not verified peers less clearly --- guis/mobile/NetView.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index 67ca72a..0bfe8df 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -40,6 +40,7 @@ Page { width: listView.width height: peerPane.height + 12 color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base + opacity: modelData.headersReceived ? 1 : 0.5 ColumnLayout { id: peerPane width: listView.width - 20 -- 2.54.0 From 158b51936e1780ad1682fba1d604585cc76edc9a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 16:20:40 +0100 Subject: [PATCH 0204/1428] Minor UX fixlets. --- guis/mobile/AccountPageListItem.qml | 25 +++++++++++++------------ guis/mobile/ReceiveTab.qml | 1 - 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 9878ab1..1fc0234 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -118,18 +118,6 @@ QQC2.Control { } VisualSeparator { } - Flowee.Label { - Layout.fillWidth: true - text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile.") - textFormat: Text.StyledText - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - } - Flowee.Label { - Layout.fillWidth: true - text: qsTr("Important: Never share your seed-phrase with others!") - textFormat: Text.StyledText - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - } Flowee.Label { text: qsTr("Wallet seed-phrase") + ":" } @@ -142,6 +130,19 @@ QQC2.Control { VisualSeparator { } Flowee.Label { text: qsTr("Derivation Path") + ":" } Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } + VisualSeparator { } + Flowee.Label { + Layout.fillWidth: true + text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile.") + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Flowee.Label { + Layout.fillWidth: true + text: qsTr("Important: Never share your seed-phrase with others!") + textFormat: Text.StyledText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } } } } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index a9023bc..0aac18c 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -253,7 +253,6 @@ FocusScope { Layout.fillWidth: true enabled: receiveTab.request != null && receiveTab.request.state === PaymentRequest.Unpaid onTextChanged: receiveTab.request.message = text - focus: true } /* -- 2.54.0 From 192b178989f279a6d0ae3ff93dda1f7ffaa65790 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 16:53:52 +0100 Subject: [PATCH 0205/1428] Encode url before entering in QR. Turns out that the image-source concept takes a special URL which then decodes our earlier encoded bip21 string. So we need to make sure we encode it again before pushing it into a QR. --- src/QRCreator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/QRCreator.cpp b/src/QRCreator.cpp index bbedf9f..1191dd6 100644 --- a/src/QRCreator.cpp +++ b/src/QRCreator.cpp @@ -30,7 +30,8 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ { Q_UNUSED(size); Q_UNUSED(requestedSize); - QRcode *code = QRcode_encodeString(id.toLatin1().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + QUrl url(id); // go via URL to encode spaces and special chars + QRcode *code = QRcode_encodeString(url.toEncoded().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (code == nullptr) { // failed to encode. QImage blank = QImage(37, 37, QImage::Format_RGB32); blank.fill(0x232629); // gray -- 2.54.0 From 95458646c698e01c7940d1161adae148f74b6d1b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 21:01:07 +0100 Subject: [PATCH 0206/1428] Start Transaction Details page --- guis/mobile.qrc | 2 + guis/mobile/AccountHistory.qml | 104 +++++++++++++++++++++++++++++ guis/mobile/MainView.qml | 1 + guis/mobile/PopupOverlay.qml | 82 +++++++++++++++++++++++ guis/mobile/TransactionDetails.qml | 68 +++++++++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 guis/mobile/PopupOverlay.qml create mode 100644 guis/mobile/TransactionDetails.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c3e6fba..e17dafb 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -40,5 +40,7 @@ mobile/AccountsList.qml mobile/AccountPageListItem.qml mobile/VisualSeparator.qml + mobile/PopupOverlay.qml + mobile/TransactionDetails.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 437fe7b..5a720b1 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -212,6 +212,11 @@ ListView { anchors.left: commentLabel.left color: palette.text text: { + var minedHeight = model.height; + if (minedHeight === -1) + return qsTr("Unconfirmed") + if (minedHeight === -2) + return qsTr("Rejected") var date = model.date; var today = new Date(); if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) { @@ -256,6 +261,105 @@ ListView { x: 8 color: root.palette.highlight } + + MouseArea { + anchors.fill: parent + onClicked: { + var newItem = popupOverlay.open(selectedItem, transactionDelegate); + newItem.infoObject = portfolio.current.txInfo(model.walletIndex, newItem) + } + } + Component { + id: selectedItem + GridLayout { + id: transactionOptions + columns: 2 + rowSpacing: 10 + property QtObject infoObject: null + Flowee.LabelWithClipboard { + Layout.fillWidth: true + Layout.columnSpan: 2 + text: { + if (model.height === -2)// -2 is the magic block-height indicating 'rejected' + return qsTr("rejected") + if (typeof model.date === "undefined") + return qsTr("unconfirmed") + var confirmations = Pay.headerChainHeight - model.height + 1; + return qsTr("%1 confirmations (mined in block %2)", "", confirmations) + .arg(confirmations).arg(model.height); + } + clipboardText: model.height + menuText: qsTr("Copy block height") + } + Flowee.Label { + id: paymentTypeLabel + visible: { + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // this is our heuristic to mark it as 'moved' + return false; + return true; + } + text: { + if (model.isCoinbase) + return qsTr("Miner Reward") + ":"; + if (model.isCashFusion) + return qsTr("Cash Fusion") + ":"; + if (model.fundsIn === 0) + return qsTr("Received") + ":"; + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // then the diff is likely just fees. + return qsTr("Moved") + ":"; + return qsTr("Sent") + ":"; + } + } + Flowee.BitcoinAmountLabel { + visible: paymentTypeLabel.visible + value: model.fundsOut - model.fundsIn + fiatTimestamp: model.date + } + Flowee.Label { + id: feesLabel + visible: transactionOptions.infoObject != null && transactionOptions.infoObject.createdByUs + text: qsTr("Fees") + ":" + } + Flowee.BitcoinAmountLabel { + visible: feesLabel.visible + value: { + if (transactionOptions.infoObject == null) + return 0; + if (!transactionOptions.infoObject.createdByUs) + return 0; + var amount = model.fundsIn; + var outputs = transactionOptions.infoObject.outputs + for (var i in outputs) { + amount -= outputs[i].value + } + return amount + } + fiatTimestamp: model.date + colorize: false + } + Flowee.Label { + text: qsTr("Size") + ":" + } + Flowee.Label { + text: transactionOptions.infoObject == null ? "" : + qsTr("%1 bytes", "", transactionOptions.infoObject.size).arg(transactionOptions.infoObject.size) + } + + TextButton { + id: txDetailsButton + Layout.columnSpan: 2 + text: qsTr("Transaction Details") + showPageIcon: true + onClicked: { + var newItem = thePile.push("./TransactionDetails.qml") + popupOverlay.close(); + newItem.transaction = model; + } + } + } + } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index a9ada54..9b1e94f 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -21,6 +21,7 @@ import "../Flowee" as Flowee MainViewBase { AccountHistory { anchors.fill: parent + PopupOverlay { id: popupOverlay } } SendTransactionsTab { anchors.fill: parent diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml new file mode 100644 index 0000000..ba84a44 --- /dev/null +++ b/guis/mobile/PopupOverlay.qml @@ -0,0 +1,82 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee + +FocusScope { + id: root + anchors.fill: parent + enabled: thePopup.visible + + function open(sourceComponent, target) { + thePopup.palette = mainWindow.palette + thePopup.width = target.width + thePopup.x = (width - target.width) / 2 + thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); + loader.sourceComponent = sourceComponent; // last, as it starts the loading + return loader.item; + } + function close() { + thePopup.visible = false; + } + + QQC2.Popup { + id: thePopup + width: parent.width + height: 100 + modal: true + closePolicy: QQC2.Popup.CloseOnEscape + QQC2.Popup.CloseOnReleaseOutside + property rect sourceRect: Qt.rect(0, 0, 0, 0) + onVisibleChanged: { + if (!visible) { // closing + loader.sourceComponent = undefined; + } + } + background: Rectangle { + color: mainWindow.palette.base + border.color: mainWindow.palette.highlight + border.width: 1 + radius: 5 + } + Loader { + id: loader + anchors.fill: parent + onLoaded: { + thePopup.height = item.implictHeight + var h = item.implicitHeight + var rect = thePopup.sourceRect; + if (root.height - rect.bottom >= h) // fits below + thePopup.y = rect.bottom; + else if (h < rect.y) // fits above + thePopup.y = rect.y - h; + else + thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps + thePopup.visible = true; + root.forceActiveFocus(); + } + } + } + + Keys.onReleased: (event)=> { + if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { + event.accepted = true; + root.close(); + } + } +} diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml new file mode 100644 index 0000000..22b345a --- /dev/null +++ b/guis/mobile/TransactionDetails.qml @@ -0,0 +1,68 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +Page { + id: root + property QtObject transaction: null + headerText: qsTr("Transaction Details") + + ColumnLayout { + width: parent.width + Flowee.LabelWithClipboard { + id: txidLabel + text: root.transaction == null ? "" : root.transaction.txid + font.pixelSize: root.font.pixelSize * 0.8 + Layout.fillWidth: true + } + Flowee.Label { + text: { + if (root.transaction == null) + return "" + var h = root.transaction.height; + if (h === -2) + return qsTr("Rejected") + if (h === -1) + return qsTr("Unconfirmed") + return qsTr("Mined") + ": " + Pay.formatDateTime(root.transaction.date) + } + } + Flowee.Label { + text: qsTr("Coinbase") + visible: root.transaction != null && root.transaction.isCoinbase + } + Flowee.Label { + text: qsTr("CashFusion transaction") + visible: root.transaction != null && root.transaction.isCashFusion + } + /* + VisualSeparator {} + Flowee.Label { + text: qsTr("Transaction comment") + ":" + } + QQC2.TextField { + text: root.transaction == null ? "" : root.transaction.comment + Layout.fillWidth: true + onEditingFinished: root.transaction.comment = text + } */ + } +} -- 2.54.0 From 6922ed4e0c9a1ef4e9a21dfa421ce9f8b3351947 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 22:07:00 +0100 Subject: [PATCH 0207/1428] Remove no longer needed task. --- src/WalletHistoryModel.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 7b27407..a2f7e94 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -46,7 +46,6 @@ public: Comment, PlacementInGroup, ///< Is an enum WalletEnums::PlacementInGroup to help with painting outlines. GroupId ///< The index in the m_groups vector - // SavedFiatRate, // TODO }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; -- 2.54.0 From edab1e09f04f8b6cb4c3104012a00f02ca5ba338 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 23:29:01 +0100 Subject: [PATCH 0208/1428] Use non-singleton buffer As this call can be made on deletion of the FloweePay singleton, avoid using the thread-local singletons and avoid deletion-order issues. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index e8251df..6a020be 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -299,7 +299,7 @@ void FloweePay::loadingCompleted() void FloweePay::saveData() { - auto &data = Streaming::pool(m_wallets.size() * 100); + Streaming::BufferPool data(m_wallets.size() * 100); Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { if (wallet->encryptionSeed() != 0) -- 2.54.0 From 6c82e6cd4f46b1a51c148d53b8e53b85444de5e6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 23:29:38 +0100 Subject: [PATCH 0209/1428] Fix being able to run 'make check'. Make the name here match the name registerd in the child cmakelists.txt --- testing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 35bb839..4cddcc8 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -25,7 +25,7 @@ if (${Qt6Test_FOUND}) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS test_wallet - test_wallet_history_model + test_wallethistorymodel test_value test_price_history ) -- 2.54.0 From 1cad0fc3e91ff26e1bf8649c5abcbc3947793791 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Dec 2022 23:43:08 +0100 Subject: [PATCH 0210/1428] Special case 'yesterday' here too. --- src/WalletHistoryModel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 041abee..b2a327d 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -58,6 +58,12 @@ bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) date = date.addDays(-1 * date.day() + 1); const auto weekStart = today.addDays(-1 * today.dayOfWeek() + 1); days = weekStart.day() - 1; + + const auto yesterday = today.addDays(-1); + if (yesterday.year() == date.year() && yesterday.month() == date.month()) { + // don't eat the events that happend yesterday. + days -= 1; + } } else { // any (other) month period = WalletEnums::Month; -- 2.54.0 From ed918d6039cece455aea8b5e300aee72eecb5440 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 Dec 2022 15:17:13 +0100 Subject: [PATCH 0211/1428] Small API fixes Move the signals to the right class and remove an include from the header file. Also provide a context object in QTimer::singleShot. Useful to avoid dangling pointers being dereferenced on shutdown. --- src/Payment.cpp | 2 +- src/Payment.h | 6 ++---- src/PaymentDetailInputs_p.h | 1 + src/PaymentDetailOutput_p.h | 1 + 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index a984dca..087b924 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -577,7 +577,7 @@ void Payment::sentToPeer() */ ++m_sentPeerCount; - QTimer::singleShot(1000, [=]() { + QTimer::singleShot(1000, this, [=]() { if (m_rejectedPeerCount == 0 || m_sentPeerCount > 1) { // When enough peers received the transaction stop broadcasting it. m_infoObject.reset(); diff --git a/src/Payment.h b/src/Payment.h index 63be357..3530c38 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -18,7 +18,8 @@ #ifndef PAYMENT_H #define PAYMENT_H -#include "WalletCoinsModel.h" + +#include #include #include @@ -111,7 +112,6 @@ public: void setPaymentAmount(double amount); /** * Returns the total amount of satoshis that are selected by outputs. - * Notice that if 'max' is requested that this is not counted. */ double paymentAmount(); @@ -284,8 +284,6 @@ signals: void collapsedChanged(); void validChanged(); - void maxAllowedChanged(); - private: const Payment::DetailType m_type; bool m_collapsable = true; diff --git a/src/PaymentDetailInputs_p.h b/src/PaymentDetailInputs_p.h index 8f4fde4..3f4576b 100644 --- a/src/PaymentDetailInputs_p.h +++ b/src/PaymentDetailInputs_p.h @@ -19,6 +19,7 @@ #define PAYMENT_DETAIL_INPUTS_H #include "Payment.h" +#include "WalletCoinsModel.h" #include "Wallet.h" class PaymentDetailInputs : public PaymentDetail diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 4548e7d..436d14a 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -77,6 +77,7 @@ signals: void fiatFollowsChanged(); void maxSelectedChanged(); void forceLegacyOkChanged(); + void maxAllowedChanged(); private: void checkValid(); -- 2.54.0 From e3a9c6a84e58311ea5d879d2e95ccfbcb3c6c737 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 Dec 2022 15:18:05 +0100 Subject: [PATCH 0212/1428] Add support for 'max price' This changes the payment screen to also have a 'max' button. Indecating we'll want to sent all available funds in the payment. --- guis/mobile/PayWithQR.qml | 45 +++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 319a487..38c7c1e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Layouts +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; @@ -48,39 +49,58 @@ Page { onIsValidChanged: if (isValid) prepare() // easier testing values (for when you don't have a camera) - // paymentAmount: 1000000 + // paymentAmount: 100000000 // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" + // userComment: "bla bla bla" } } // if true, the bitcoin value is provided by the user (or QR), and the fiat follows // if false, the user edits the fiat price and the bitcoin value is calculated. + // Notice that 'sendAllButton' overrules both and gets the data from the wallet-total property bool fiatFollowsSats: true Flowee.BitcoinValueField { id: priceBch - y: 30 - value: payment.paymentAmount + y: 10 + value: { + if (sendAllButton.checked) // we use 'max' + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + return payment.paymentAmount + } focus: true fontPixelSize: size - property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize + property double size: (fiatFollowsSats && !sendAllButton.checked) ? 38 : commentLabel.font.pixelSize onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true Behavior on size { NumberAnimation { } } Flowee.ObjectShaker { id: bchShaker } + // auto-unchecks 'max' onValueEdited: payment.paymentAmount = value } Flowee.FiatValueField { id: priceFiat - value: Fiat.priceFor(payment.paymentAmount, Fiat.price) + value: Fiat.priceFor(priceBch.value, payment.fiatPrice); anchors.top: priceBch.bottom anchors.topMargin: 18 focus: true fontPixelSize: size - property double size: !fiatFollowsSats ? 38 : commentLabel.font.pixelSize + property double size: (!fiatFollowsSats && !sendAllButton.checked) ? 38 : commentLabel.font.pixelSize onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false Behavior on size { NumberAnimation { } } Flowee.ObjectShaker { id: fiatShaker } + onValueEdited: payment.details[0].fiatAmount = value + } + + Flowee.Button { + id: sendAllButton + text: qsTr("Send All") + checkable: true + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: priceFiat.bottom + anchors.topMargin: 10 + checked: payment.details[0].maxSelected + onClicked: payment.details[0].maxSelected = checked } Flowee.Label { @@ -88,8 +108,8 @@ Page { text: qsTr("Payment description" + ":") visible: userComment.text !== "" - anchors.top: priceFiat.bottom - anchors.topMargin: 30 + anchors.top: sendAllButton.bottom + anchors.topMargin: 20 } Flowee.Label { id: userComment @@ -136,12 +156,13 @@ Page { anchors.bottom: slideToApprove.top anchors.bottomMargin: 15 width: parent.width + enabled: !sendAllButton.checked Repeater { model: 12 delegate: Item { width: numericKeyboard.width / 3 height: textLabel.height + 20 - Flowee.Label { + QQC2.Label { id: textLabel anchors.centerIn: parent font.pixelSize: 28 @@ -155,6 +176,12 @@ Page { // if (index === 11) return "<-" // TODO use a backspace icon instead. } + // make dim when not enabled. + color: { + if (!enabled) + return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 1.8) : Qt.lighter(palette.buttonText, 2); + return palette.windowText; + } } MouseArea { -- 2.54.0 From 5a88fcbe0436a6d004346b4e214357066ba17254 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 Dec 2022 16:28:49 +0100 Subject: [PATCH 0213/1428] Follow the rowproxy new data storage. We inverted the rowProxy ordering 2 weeks ago, but not all usages of this change were updated. This should cover those unupdated usages. --- src/WalletHistoryModel.cpp | 17 ++++++++++------- src/WalletHistoryModel.h | 10 ++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index b2a327d..9d21014 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -262,7 +262,8 @@ QString WalletHistoryModel::dateForItem(qreal offset) const if (std::isnan(offset) || offset < 0 || offset > 1.0) return QString(); const size_t row = std::round(offset * m_rowsProxy.size()); - auto item = m_wallet->m_walletTransactions.at(m_rowsProxy.at(row)); + auto item = m_wallet->m_walletTransactions.at( + m_rowsProxy.at(m_rowsProxy.size() - row - 1)); if (item.minedBlockHeight <= 0) return QString(); auto timestamp = secsSinceEpochFor(item.minedBlockHeight); @@ -299,7 +300,7 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) void WalletHistoryModel::transactionChanged(int txIndex) { - const int row = m_rowsProxy.indexOf(txIndex); + const int row = m_rowsProxy.size() - m_rowsProxy.indexOf(txIndex) - 1; // update row, the 'minedHeight' went from unset to an actual value beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); @@ -401,18 +402,20 @@ void WalletHistoryModel::resetLastSyncIndicator() { const auto old = m_lastSyncIndicator; m_lastSyncIndicator = m_wallet->segment()->lastBlockSynched(); - int index = 0; - while (index < m_rowsProxy.size()) { + + int index = m_rowsProxy.size() - 1; // newest is at the end + while (index >= 0) { const auto &tx = m_wallet->m_walletTransactions.find(m_rowsProxy.at(index)); if (tx->second.minedBlockHeight < old) break; ++index; } + const int lastRow = m_rowsProxy.size() - index - 1; // refresh the rows that need the 'new' indicator removed. - if (index > 0) { - beginRemoveRows(QModelIndex(), 0, index); + if (lastRow > 0) { + beginRemoveRows(QModelIndex(), 0, lastRow); endRemoveRows(); - beginInsertRows(QModelIndex(), 0, index); + beginInsertRows(QModelIndex(), 0, lastRow); endInsertRows(); } } diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index a2f7e94..263e3c9 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -99,6 +99,16 @@ private: /// Update m_groups to include this transaction. void addTxIndexToGroups(int txIndex, int blockheight); + /* + * Our internal data-model. + * This is a filter of the wallet-internal 'txIndex'. So we map index-in-vector to + * transaction-index as known by the wallet. + * + * The main trick we use is that the vector starts with the oldest transactions while + * our view starts with the newest. As such we simply invert the vector on insert/get. + * (notice that we don't invert the actual vector since append-new would then + * become a costly insert-before). + */ QVector m_rowsProxy; Wallet *m_wallet; QFlags m_includeFlags = WalletEnums::IncludeAll; -- 2.54.0 From 7cedf20844ad9f1aa65ee27c7b5de9a58ab568aa Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 12:24:21 +0100 Subject: [PATCH 0214/1428] Move parsing of '--offline' to be as soon as possible --- src/main_utils.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index e21ef7b..90b2bf1 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -29,7 +29,7 @@ struct CommandLineParserData { - CommandLineParserData(QGuiApplication &qapp) + explicit CommandLineParserData(QGuiApplication &qapp) : connect(QStringList() << "connect", "Connect to HOST", "HOST"), debug(QStringList() << "debug", "Use debug level logging"), verbose(QStringList() << "verbose" << "v", "Be more verbose"), @@ -56,6 +56,8 @@ struct CommandLineParserData // select chain first (before we create the FloweePay singleton) if (parser.isSet(testnet4)) chain = P2PNet::Testnet4Chain; + if (parser.isSet(offline)) + FloweePay::instance()->setOffline(true); } QCommandLineParser parser; @@ -144,10 +146,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); engine.rootContext()->setContextProperty("portfolio", portfolio); - if (cld->parser.isSet(cld->offline)) { - FloweePay::instance()->setOffline(true); - } - else if (cld->parser.isSet(cld->connect)) { + if (!cld->parser.isSet(cld->offline) && cld->parser.isSet(cld->connect)) { app->p2pNet()->connectionManager().peerAddressDb().addOne( // add it to the DB, making sure there is at least one. EndPoint(cld->parser.value(cld->connect).toStdString(), 8333)); } -- 2.54.0 From 0f993e07ca1d89c21471f4ab11d21610aaec8122 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 12:25:28 +0100 Subject: [PATCH 0215/1428] Fix copy paste error. --- src/PriceDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index ee480d6..5a80129 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -51,7 +51,7 @@ PriceDataProvider::PriceDataProvider(QObject *parent) : QObject(parent) } // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") - || m_currency == QLatin1String("JPY")); + || m_currency == QLatin1String("NOK")); QObject::connect(&m_timer, SIGNAL(timeout()), this, SLOT(fetch())); } -- 2.54.0 From 5cb29d39ef563b752c49773e1523116ea2f3619b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 12:28:58 +0100 Subject: [PATCH 0216/1428] Print proper fiat prices for unconfirmed transactions. --- guis/Flowee/BitcoinAmountLabel.qml | 4 ++-- guis/mobile/AccountHistory.qml | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index b5d93a0..3a5cd93 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -34,7 +34,7 @@ QQC2.Control { * date and the fiat price is calculated based on the historical price * This is only used if you fill it with a date object. */ - property var fiatTimestamp: null + property var fiatTimestamp: undefined property color color: palette.windowText property alias fontPixelSize: main.font.pixelSize @@ -127,7 +127,7 @@ QQC2.Control { Layout.alignment: Qt.AlignBaseline text: { var fiatPrice; - if (root.fiatTimestamp == null || fiatHistory == null) + if (root.fiatTimestamp == undefined || fiatHistory == null) fiatPrice = Fiat.price; // todays price else fiatPrice = fiatHistory.historicalPrice(root.fiatTimestamp); diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 5a720b1..787240d 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -246,9 +246,16 @@ ListView { : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background Flowee.Label { id: amount - text: Fiat.formattedPrice(parent.amountBch, fiatHistory.historicalPrice(model.date)); + text: { + var dat = model.date; + if (typeof dat === "undefined") // unconfirmed transactions have no date + var fiatPrice = Fiat.price; + else + fiatPrice = fiatHistory.historicalPrice(dat); + return Fiat.formattedPrice(price.amountBch, fiatPrice); + } anchors.centerIn: parent - opacity: Math.abs(parent.amountBch) < 2000 ? 0.5 : 1 + opacity: Math.abs(price.amountBch) < 2000 ? 0.5 : 1 } } -- 2.54.0 From e78bfe1c8a09b2c56a2328c250f772ba418375d1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 12:49:51 +0100 Subject: [PATCH 0217/1428] Fix lastSyncIndicator when there are unconfirmed TXs --- src/WalletHistoryModel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 9d21014..42d3a3b 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -406,9 +406,9 @@ void WalletHistoryModel::resetLastSyncIndicator() int index = m_rowsProxy.size() - 1; // newest is at the end while (index >= 0) { const auto &tx = m_wallet->m_walletTransactions.find(m_rowsProxy.at(index)); - if (tx->second.minedBlockHeight < old) + if (tx->second.minedBlockHeight > 0 && tx->second.minedBlockHeight < old) break; - ++index; + --index; } const int lastRow = m_rowsProxy.size() - index - 1; // refresh the rows that need the 'new' indicator removed. -- 2.54.0 From 56d3b4fc36811221bdd2a9f9be96079e12b1bde1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 12:58:01 +0100 Subject: [PATCH 0218/1428] Fill price from historical on startup. On mobile its more visible that it takes a couple of seconds to fetch the price from the network. The result is that no prices are visible on unconfirmed transactions and wallet balances for that time. This instead uses the last known correct price from the historical database as the price, so in that couple of seconds we are showing the mostly correct data instead of no data. When the price feed comes in, all values on screen get automatically updated. --- src/FloweePay.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 6a020be..41b8ac3 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -278,15 +278,14 @@ void FloweePay::loadingCompleted() if (m_chain == P2PNet::MainChain) { m_priceHistory.reset(new PriceHistoryDataProvider(m_basedir, QLocale::system().currencySymbol(QLocale::CurrencyIsoCode))); - if (m_offline) { - // if we are offline, take the last known price from our historical module - // to show to the user. - auto price = m_priceHistory->historicalPrice(QDateTime::currentDateTimeUtc()); - if (price == 0) - price = 50000; // if we never fetched, set to 500,- - m_prices->mock(price); - } - else { + + // take the last known price from our historical module to have something + // mostly useful until we manage to fetch the data from the life feeds. + auto price = m_priceHistory->historicalPrice(QDateTime::currentDateTimeUtc()); + if (price == 0) + price = 10000; // if we never fetched, set to 100,- + m_prices->mock(price); + if (!m_offline) { m_priceHistory->initialPopulate(); connect (m_prices.get(), &PriceDataProvider::priceChanged, m_priceHistory.get(), [=](int price) { -- 2.54.0 From a8695db75960beaff482234bae0dc187cf22c705 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 13:15:33 +0100 Subject: [PATCH 0219/1428] Fix focus after QR scan. --- guis/mobile/PayWithQR.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 38c7c1e..6f2e1c1 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -39,6 +39,7 @@ Page { payment.targetAddress = rc if (payment.isValid) payment.prepare(); + priceBch.forceActiveFocus(); } } } -- 2.54.0 From 3112912c576d1230f3bd4d3680ce29d6158ad0d9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 13:15:55 +0100 Subject: [PATCH 0220/1428] New version. --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index bd8d39a..6c253b8 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ - + diff --git a/src/main.cpp b/src/main.cpp index 10054f5..171070f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2022.12.0"); + qapp.setApplicationVersion("2022.12.3"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 4259eede239a2cac4a8a76de0ab0c716dc38df4a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 13:17:34 +0100 Subject: [PATCH 0221/1428] Keep working on Linux QtMultimedia on Linux has the nasty bug that stopping the camera doens't allow us to start it afterwards. So for now, while Qt is still buggy, lets simply not stop it. Notice that this is Linux only, where I expect this class to have very low usage. Android doesn't have this specific issue. --- src/CameraController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 9ca7516..9d37dcc 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -207,7 +207,9 @@ void CameraControllerPrivate::checkState() if (cam->error() != QCamera::NoError) logFatal() << "CameraController found cam error:" << cam->errorString(); +#ifndef TARGET_OS_Linux cam->stop(); // workaround for why some phones don't scan the first time. +#endif cam->setCameraFormat(preferredFormat); cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash -- 2.54.0 From fb3d83f8f35f6da5bb7f625768a6820828766d23 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 13:41:51 +0100 Subject: [PATCH 0222/1428] Make sure we print full date time on transaction popup To make space we move the tx-size to the transaction details page. --- guis/mobile/AccountHistory.qml | 18 ++++++++++-------- guis/mobile/TransactionDetails.qml | 9 +++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 787240d..7925f66 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -273,7 +273,7 @@ ListView { anchors.fill: parent onClicked: { var newItem = popupOverlay.open(selectedItem, transactionDelegate); - newItem.infoObject = portfolio.current.txInfo(model.walletIndex, newItem) + newItem.infoObject = portfolio.current.txInfo(model.walletIndex, parent); } } Component { @@ -298,6 +298,14 @@ ListView { clipboardText: model.height menuText: qsTr("Copy block height") } + Flowee.Label { + visible: model.height > 0 + text: qsTr("Mined") + ":" + } + Flowee.Label { + visible: model.height > 0 + text: model.height > 0 ? Pay.formatDateTime(model.date) : ""; + } Flowee.Label { id: paymentTypeLabel visible: { @@ -346,13 +354,6 @@ ListView { fiatTimestamp: model.date colorize: false } - Flowee.Label { - text: qsTr("Size") + ":" - } - Flowee.Label { - text: transactionOptions.infoObject == null ? "" : - qsTr("%1 bytes", "", transactionOptions.infoObject.size).arg(transactionOptions.infoObject.size) - } TextButton { id: txDetailsButton @@ -363,6 +364,7 @@ ListView { var newItem = thePile.push("./TransactionDetails.qml") popupOverlay.close(); newItem.transaction = model; + newItem.infoObject = transactionOptions.infoObject; } } } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 22b345a..3de29d2 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -24,6 +24,7 @@ import Flowee.org.pay; Page { id: root property QtObject transaction: null + property QtObject infoObject: null headerText: qsTr("Transaction Details") ColumnLayout { @@ -54,6 +55,14 @@ Page { text: qsTr("CashFusion transaction") visible: root.transaction != null && root.transaction.isCashFusion } + VisualSeparator {} + Flowee.Label { + text: qsTr("Size") + ":" + } + Flowee.Label { + text: root.infoObject == null ? "" : + qsTr("%1 bytes", "", root.infoObject.size).arg(root.infoObject.size) + } /* VisualSeparator {} Flowee.Label { -- 2.54.0 From d2213194d444a48575baaa3b0cd2a89f65ab5c53 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 13:46:14 +0100 Subject: [PATCH 0223/1428] Add delay on camera init Move to the next eventloop event the usage of the camera (finding out resolutions etc) which looks to help stability. Also remove the init-on-startup again, which had the nasty side-effect of turning on the camera for a very brief time due to some Qt bug that ignores the 'active' boolean on the camera object. --- src/CameraController.cpp | 9 +++++++-- src/CameraController.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 9d37dcc..5946037 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -234,6 +234,11 @@ void CameraController::checkState2() emit cameraActiveChanged(); // this emit makes QML activate the camera } +void CameraController::initCamera() +{ + d->initCamera(); +} + // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) @@ -432,7 +437,7 @@ CameraController::CameraController(QObject *parent) d(new CameraControllerPrivate(this)) { // pre-load the camera for stability sake - QTimer::singleShot(3000, this, SLOT(initialize())); + // QTimer::singleShot(3000, this, SLOT(initialize())); // The Android permissions requesting stuff returns results in a different thread, // allow an easy way to move back to the main thread using a connection. @@ -533,7 +538,7 @@ void CameraController::setCamera(QObject *object) return; d->camera = object; emit cameraChanged(); - d->initCamera(); + QTimer::singleShot(10, this, SLOT(initCamera())); } QObject *CameraController::camera() const diff --git a/src/CameraController.h b/src/CameraController.h index 908d173..6e39027 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -80,6 +80,7 @@ private slots: void qrScanFinished(); void checkState(); void checkState2(); + void initCamera(); private: CameraControllerPrivate * d; -- 2.54.0 From a0cc58ecb188a0e8f4ff6f14f70897a59cb1e658 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 14:10:13 +0100 Subject: [PATCH 0224/1428] Make slide less long. --- guis/mobile/SlideToApprove.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/SlideToApprove.qml b/guis/mobile/SlideToApprove.qml index 72512e3..965118b 100644 --- a/guis/mobile/SlideToApprove.qml +++ b/guis/mobile/SlideToApprove.qml @@ -46,7 +46,7 @@ Item { } property bool finished: false onXChanged: { - if (x >= root.width - 80) { + if (x >= root.width - 120) { finished = true; root.activated(); } -- 2.54.0 From 8f1cf1ad3948dc15de686235f6f33cbe5599904d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 14:21:04 +0100 Subject: [PATCH 0225/1428] Correct typo in our package identifier. --- android/AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 6c253b8..b3c417e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,5 @@ - - + -- 2.54.0 From f86ea99a3ace1f0bf41e68711f43fec528abab66 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 16:18:59 +0100 Subject: [PATCH 0226/1428] Add link to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7f23e44..ee6aa9b 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ options: # Links +* Learn more about QML? Here is a great place to start; https://www.kdab.com/top-100-qml-resources-kdab/ + * Upstream: https://codeberg.org/Flowee/pay * Website: https://flowee.org * Twitter: https://twitter.com/floweethehub -- 2.54.0 From 08189f9c7d5fa6aeda01b30f3bb7a8c674a495a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 17:45:40 +0100 Subject: [PATCH 0227/1428] Remove debug output --- guis/mobile/AccountPageListItem.qml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 1fc0234..5706474 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -53,13 +53,6 @@ QQC2.Control { checked: root.account.isPrimaryAccount onClicked: root.account.isPrimaryAccount = checked } - - Connections { - target: root.account - function onIsPrimaryAccountChanged() { - console.log("prim changed " + root.account.name + " = " + root.account.isPrimaryAccount) - } - } } /* -- 2.54.0 From 8d29aefbbaf764eee8f0537e70dcb7641519df8a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Dec 2022 17:45:51 +0100 Subject: [PATCH 0228/1428] Make this work. Seems I forgot the derivation path entry field, for a quick release just hardcode it. --- guis/mobile/NewAccount.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 51e826d..8c8cc10 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -176,7 +176,7 @@ Page { headerButtonText: qsTr("Create") headerButtonEnabled: accountName.text.length > 2 onHeaderButtonClicked: { - var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); + var options = Pay.createNewWallet("m/44'/0'/0'", /* password */"", accountName.text); var accounts = portfolio.accounts; for (var i = 0; i < accounts.length; ++i) { var a = accounts[i]; -- 2.54.0 From 0b38d3817619924612245c9ec98c4eb1dd65fb11 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 5 Jan 2023 14:27:24 +0100 Subject: [PATCH 0229/1428] Fix updating price when max is set. --- src/PaymentDetailOutput.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index fdaaacd..fdfd003 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -76,6 +76,12 @@ void PaymentDetailOutput::setPaymentAmount(double amount_) qint64 amount = static_cast(amount_); if (m_paymentAmount == amount) return; + if (m_maxAllowed && m_maxSelected) { + // Check if we got set the exact value we reported. + auto cur = paymentAmount(); + if (cur == amount) + return; + } m_paymentAmount = amount; // implicit changes first, it changes the representation setFiatFollows(true); -- 2.54.0 From 8809a7ac1970422229f5ed2a432606fb24102468 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 5 Jan 2023 22:06:31 +0100 Subject: [PATCH 0230/1428] Use locale specific date formatting --- guis/mobile/AccountHistory.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 7925f66..36b552a 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -224,7 +224,7 @@ ListView { if (date.getDate() === day || date.getDate() === day - 1) { // Then this is an item in the 'today' or the 'yesterday' group. // specify more specific date/time - return date.getHours() + ":" + date.getMinutes() + return Qt.formatTime(date); } } -- 2.54.0 From 8e7a135cc8311b23e7d9d6fd5854924d471e3270 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 5 Jan 2023 22:19:16 +0100 Subject: [PATCH 0231/1428] Add comment --- guis/mobile/PopupOverlay.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index ba84a44..57db33a 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,11 @@ FocusScope { anchors.fill: parent enabled: thePopup.visible + /** + * @param sourceComponent is a Component we set on the loader. + * @param target is the visual item we position next to. + * @returns the item instance of the sourceComponent template + */ function open(sourceComponent, target) { thePopup.palette = mainWindow.palette thePopup.width = target.width -- 2.54.0 From f45408163a27bc0aebb44ab05f745d1ab0d6e2f8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 6 Jan 2023 00:45:12 +0100 Subject: [PATCH 0232/1428] Avoid iterator on container that is being modified. Setting the wallet to null calls us again to remove it from our list, as such it is better to not work on that live list but on a copy. --- src/Wallet.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index e2cbf92..8eadba7 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -95,7 +95,9 @@ Wallet::~Wallet() saveSecrets(); saveWallet(); // tell the payment requests that we are no more. (and avoids callbacks to a deleted object) - for (auto prData = m_paymentRequests.begin(); prData != m_paymentRequests.end(); ++prData) { + auto copy(m_paymentRequests); + for (auto prData = copy.begin(); prData != copy.end(); ++prData) { + assert(prData->pr); prData->pr->setWallet(nullptr); } } -- 2.54.0 From 051e21ef601e8608030caeeac3e18abf41ba698a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 6 Jan 2023 00:47:13 +0100 Subject: [PATCH 0233/1428] Fix testnet4 support --- src/main_utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 90b2bf1..d71b40e 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ struct CommandLineParserData // select chain first (before we create the FloweePay singleton) if (parser.isSet(testnet4)) chain = P2PNet::Testnet4Chain; + FloweePay::selectChain(chain); if (parser.isSet(offline)) FloweePay::instance()->setOffline(true); } -- 2.54.0 From 73fa9621508a5bd2ad08e15c3e7538b652a63b9e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Jan 2023 16:52:18 +0100 Subject: [PATCH 0234/1428] Build boost quietly Since its such a long list of just copying headers, better to just log to file. --- android/docker/scripts/buildBoost.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index d48599a..ebfc0dc 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -34,7 +34,7 @@ echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64 variant=release \ link=static \ threading=multi \ - install + install > ~builduser/boost.build.log 2>&1 # toolset=clang-arm \ # runtime-link=shared \ -- 2.54.0 From d93431d0ce65bd033d592f8b61072ff565f4bba7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Jan 2023 16:52:50 +0100 Subject: [PATCH 0235/1428] Ensure we exit if the Qt tag (/branch) we try to build doesn't exist. --- android/docker/scripts/buildQt.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 81a00d0..a2cc076 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -13,9 +13,15 @@ function checkout ( (cd /usr/local/cache if ! test -d $repo.git; then git clone --bare https://code.qt.io/qt/$repo.git - fi - cd ~builduser - git clone -l /usr/local/cache/$repo.git -b $TAG) + fi) + (cd ~builduser + if git clone -l /usr/local/cache/$repo.git -b $TAG + then + echo ".. OK" + else + echo "Calling exit" + exit 1 + fi) ) # The QtBase builds are different. -- 2.54.0 From 15cf4732e30ae4ad640fab415f68e4407dc1212e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Jan 2023 18:31:39 +0100 Subject: [PATCH 0236/1428] Fix initial save of private wallet data This makes sure that after initial setup of the wallet we save the private data and the wallet that was not owned by the user is stored as such. On Android the destructor often is not properly called, which leads to us not saving this on shutdown and that showed this bug. --- src/FloweePay.cpp | 4 ++-- src/Wallet.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 41b8ac3..82f921f 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -257,8 +257,8 @@ void FloweePay::init() if (m_wallets.isEmpty() && m_createStartWallet) { logInfo() << "Creating startup (initial) wallet"; - auto config = createNewWallet(QLatin1String(DefaultDerivationPath)); - delete config; // the config was just for QML, so avoid a dangling object. + // the config is just for QML, delete when we exit scope. + std::unique_ptr config(createNewWallet(QLatin1String(DefaultDerivationPath))); m_wallets.at(0)->setUserOwnedWallet(false); m_wallets.at(0)->segment()->setPriority(PrivacySegment::Last); m_wallets.at(0)->setName(tr("Initial Wallet")); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 8eadba7..87589ec 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -57,6 +57,7 @@ Wallet::Wallet() : m_walletChanged(true), m_walletVersion(2) { + connect (this, SIGNAL(startDelayedSave()), this, SLOT(delayedSave()), Qt::QueuedConnection); // ensure right thread calls us. } Wallet::Wallet(const boost::filesystem::path &basedir, uint16_t segmentId, uint32_t encryptionSeed) -- 2.54.0 From f1ff7f5e8242cb1b758d5fad6f087a351a7b367d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Jan 2023 19:03:26 +0100 Subject: [PATCH 0237/1428] Minor fixes. Keep it more local. --- guis/mobile/AccountHistory.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 36b552a..01bfd41 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -128,7 +128,7 @@ ListView { height: label.height + 15 width: root.width Rectangle { - color: mainWindow.palette.window + color: root.palette.window anchors.fill: parent } Flowee.Label { @@ -164,7 +164,7 @@ ListView { y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; radius: 20 - color: mainWindow.palette.base + color: root.palette.base border.width: 1 border.color: root.palette.highlight } -- 2.54.0 From 57cae423004cc54de821d30560199fd8d93978a3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Jan 2023 19:15:52 +0100 Subject: [PATCH 0238/1428] Add freeze of model feature. This is a bit of a violation of layers, as a result of the ListView not having any way to do this. Even in a hacky way. The usecase is that we need to stop the listview scrolling and showing new items that are being inserted at the top in some cases. Specifically when the user taps on a single transaction in order to get more information about it, at that point the popup should be displayed next to the item we tapped on and that item should not move due to there being new transactions found. The freezeModel property accomplishes that by simply not telling the model new data has been found as long as the property is true. Afterwards all the updates that have accumulate are flushed. --- src/WalletHistoryModel.cpp | 49 ++++++++++++++++++++++++++++++++++---- src/WalletHistoryModel.h | 18 ++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 42d3a3b..ac1bd38 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -121,7 +121,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const assert(m_rowsProxy.size() > index.row()); assert(index.row() >= 0); - const int txIndex = m_rowsProxy.at(m_rowsProxy.size() - index.row() - 1); // reverse index to make the VIEW have newest at the top + const int txIndex = txIndexFromRow(index.row()); // logDebug() << " getting" << index.row() << "=>" << txIndex; auto itemIter = m_wallet->m_walletTransactions.find(txIndex); assert(itemIter != m_wallet->m_walletTransactions.end()); @@ -262,8 +262,7 @@ QString WalletHistoryModel::dateForItem(qreal offset) const if (std::isnan(offset) || offset < 0 || offset > 1.0) return QString(); const size_t row = std::round(offset * m_rowsProxy.size()); - auto item = m_wallet->m_walletTransactions.at( - m_rowsProxy.at(m_rowsProxy.size() - row - 1)); + auto item = m_wallet->m_walletTransactions.at(txIndexFromRow(row)); if (item.minedBlockHeight <= 0) return QString(); auto timestamp = secsSinceEpochFor(item.minedBlockHeight); @@ -283,7 +282,7 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) addTxIndexToGroups(iter->first, iter->second.minedBlockHeight); } auto insertedCount = m_rowsProxy.size() - oldCount; - if (insertedCount) { + if (m_rowsSilentlyInserted == -1 && insertedCount) { if (oldCount) { // Due to grouping being drawn in relation, we need to force // a change in the previously top item @@ -296,11 +295,16 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) beginInsertRows(QModelIndex(), 0, insertedCount - 1); endInsertRows(); } + else { + m_rowsSilentlyInserted += insertedCount; + } } void WalletHistoryModel::transactionChanged(int txIndex) { - const int row = m_rowsProxy.size() - m_rowsProxy.indexOf(txIndex) - 1; + int row = m_rowsProxy.size() - m_rowsProxy.indexOf(txIndex) - 1; + if (m_rowsSilentlyInserted > 0) + row -= m_rowsSilentlyInserted; // update row, the 'minedHeight' went from unset to an actual value beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); @@ -363,6 +367,16 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) } } +int WalletHistoryModel::txIndexFromRow(int row) const +{ + int newRow = m_rowsProxy.size() - row - 1; + if (m_rowsSilentlyInserted > 0) + newRow -= m_rowsSilentlyInserted; + assert(newRow >= 0); + assert(newRow < m_rowsProxy.size()); + return m_rowsProxy.at(newRow); +} + const QFlags &WalletHistoryModel::includeFlags() const { return m_includeFlags; @@ -381,6 +395,31 @@ void WalletHistoryModel::setIncludeFlags(const QFlags &fla QTimer::singleShot(0, this, SLOT(createMap())); } +void WalletHistoryModel::freezeModel(bool on) +{ + if ((on && m_rowsSilentlyInserted >= 0) || (!on && m_rowsSilentlyInserted < 0)) + return; + if (on) { + m_rowsSilentlyInserted = 0; + } + else { + // process backlog before turning off + if (m_rowsSilentlyInserted > 0) { + beginRemoveRows(QModelIndex(), 0, 0); + endRemoveRows(); + beginInsertRows(QModelIndex(), 0, m_rowsSilentlyInserted); + endInsertRows(); + } + m_rowsSilentlyInserted = -1; + } + emit freezeModelChanged(); +} + +bool WalletHistoryModel::isModelFrozen() const +{ + return m_rowsSilentlyInserted >= 0; +} + uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const { return FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 263e3c9..a732199 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -29,6 +29,13 @@ class WalletHistoryModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(int lastSyncIndicator READ lastSyncIndicator WRITE setLastSyncIndicator RESET resetLastSyncIndicator NOTIFY lastSyncIndicatorChanged) Q_PROPERTY(QFlags includeFlags READ includeFlags WRITE setIncludeFlags NOTIFY includeFlagsChanged) + /** + * The view sometimes wants to ensure that content in the list does not move. For instance + * when a popup related to one of the list-items is showing. + * Freezing the model will stop the model from reporting new rows and un-freezing it aferwards + * will cause all the new items that were found while frozen to be reported for UI update. + */ + Q_PROPERTY(bool freezeModel READ isModelFrozen WRITE freezeModel NOTIFY freezeModelChanged); public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); @@ -79,9 +86,13 @@ public: bool add(int txIndex, uint32_t timestamp); }; + void freezeModel(bool on); + bool isModelFrozen() const; + signals: void lastSyncIndicatorChanged(); void includeFlagsChanged(); + void freezeModelChanged(); protected: // virtual to allow the unit test to not use p2pNet for this @@ -99,6 +110,8 @@ private: /// Update m_groups to include this transaction. void addTxIndexToGroups(int txIndex, int blockheight); + int txIndexFromRow(int row) const; + /* * Our internal data-model. * This is a filter of the wallet-internal 'txIndex'. So we map index-in-vector to @@ -123,6 +136,11 @@ private: int m_lastSyncIndicator = 0; bool m_recreateTriggered = false; + + /// when above zero, we store the numbeer of rows we still need to process updates for + /// while the listview has been frozen. + /// See the freezeModel property for more. + int m_rowsSilentlyInserted = -1; }; #endif -- 2.54.0 From 07908f2aae5c3dff450984f7a5a9206d1ba96662 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Jan 2023 19:18:45 +0100 Subject: [PATCH 0239/1428] [Mobile] Make 'to top' button appear earlier. The button to move the view to the top of the list is hidden when we are at the top of the list. This new version makes the appearing if the button happen based on the amount of rows we scrolled down, making it appear faster and be more predicatable. --- guis/mobile/AccountHistory.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 01bfd41..20997a6 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -31,7 +31,12 @@ ListView { width: 60 height: 60 anchors.right: parent.right - y: (root.contentY > (root.height / 4)) ? 0 : height * -1 + y: { + var indexAtTopOfScreen = root.indexAt(10, root.contentY + 10); + if (indexAtTopOfScreen > 3) + return 0; + return height * -1; // out of screen. + } color: "#66000000" Image { -- 2.54.0 From d4413dad05692869dc6a0f02641991a2d4652a5a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Jan 2023 19:20:57 +0100 Subject: [PATCH 0240/1428] Enable freezing of the listview model on showing popup --- guis/mobile/AccountHistory.qml | 10 ++++++++++ guis/mobile/PopupOverlay.qml | 3 +++ 2 files changed, 13 insertions(+) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 20997a6..07abd51 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -277,8 +277,18 @@ ListView { MouseArea { anchors.fill: parent onClicked: { + root.currentIndex = index; var newItem = popupOverlay.open(selectedItem, transactionDelegate); newItem.infoObject = portfolio.current.txInfo(model.walletIndex, parent); + root.model.freezeModel = true; + } + Connections { + target: popupOverlay + // unfreeze the model on closing of the popup + function onIsOpenChanged() { + if (!popupOverlay.isOpen) + root.model.freezeModel = false; + } } } Component { diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 57db33a..1b0cd23 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -24,6 +24,8 @@ FocusScope { anchors.fill: parent enabled: thePopup.visible + property bool isOpen: false; + /** * @param sourceComponent is a Component we set on the loader. * @param target is the visual item we position next to. @@ -52,6 +54,7 @@ FocusScope { if (!visible) { // closing loader.sourceComponent = undefined; } + root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } background: Rectangle { color: mainWindow.palette.base -- 2.54.0 From aea1ba28f7818514b06428391bb0630a4ac0e102 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 16:47:54 +0100 Subject: [PATCH 0241/1428] Cleanup After introduction of pay_mobile, we now have a static lib of all classes that get linked to the unit tests, as such we can remove optimizations that tried to keep the set of files to compile in tests smaller. --- src/Wallet.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 87589ec..26203d5 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -373,9 +373,7 @@ void Wallet::newTransaction(const Tx &tx) emit utxosChanged(); emit appendedTransactions(firstNewTransaction, 1); -#ifndef IN_TESTS // don't call singleton in unit tests FloweePay::instance()->p2pNet()->notifications().notifyNewTransaction(notification); -#endif if (createdNewKeys) rebuildBloom(); } @@ -502,9 +500,7 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s if (wasUnconfirmed) emit transactionConfirmed(walletTransactionId); if (notification.blockHeight > 0) { -#ifndef IN_TESTS // don't call singleton in unit tests FloweePay::instance()->p2pNet()->notifications().notifyNewTransaction(notification); -#endif } } assert(m_nextWalletTransactionId - firstNewTransaction == int(transactionsToSave.size())); -- 2.54.0 From f80540b7e9593a842d2252e36dcb74eb40979760 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 17:11:35 +0100 Subject: [PATCH 0242/1428] Another cleanup, like parent commit. --- src/Wallet.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 26203d5..87f3dee 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1282,9 +1282,7 @@ void Wallet::broadcastUnconfirmed() bc->moveToThread(thread()); logDebug() << " broadcasting transaction" << tx.createHash() << tx.size(); m_broadcastingTransactions.append(bc); -#ifndef IN_TESTS // don't call singleton in unit tests FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(bc); -#endif } else { logCritical() << "Unconfirmed transaction could not be found on disk!"; -- 2.54.0 From 90cdfd8059fb0c104a40ecff0276239d3e446f23 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 17:12:22 +0100 Subject: [PATCH 0243/1428] Add some comments on state --- src/Wallet.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 87f3dee..69887d4 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -823,6 +823,18 @@ bool Wallet::isLocked(OutputRef outputRef) const int Wallet::walletCreatedHeight() const { + /* + * Wallets know at which heigh of the chain they were created, which is nice + * because they never need to look at history on their addresses before that + * blockheight. + * + * On Pay startup a wallet may be created before we actually saw the tip of + * the headers-chain, meaning that the initial height is impossible to set. + * In that small time-window the wallet will get its initial height set to + * the current (unix) time,which is corrected to an actual block-height the + * moment the headers are fully synched. (see headerSyncComplete()) + * In here, we simply return -1 in that case. + */ if (m_walletSecrets.empty() || m_walletSecrets.begin()->second.initialHeight >= 10000000) return -1; return m_walletSecrets.begin()->second.initialHeight; @@ -944,7 +956,7 @@ void Wallet::rebuildBloom() if (secret.initialHeight >= 10000000) { // is a timestamp, which means that we are waiting for the // headerSyncComplete() to be called and this key is fresh - // and so searching in the history is useless. + // and so searching in the history is pointless. continue; } if (secret.reserved) { -- 2.54.0 From 8a72fd1e6a9aa709cb719cb15c7e352fb8f17a49 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 17:42:27 +0100 Subject: [PATCH 0244/1428] Fix first blockheight property value on AccountInfo Now we correctly have the first blockheight for wallets on creation. --- src/FloweePay.cpp | 7 +++++-- src/FloweePay.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 82f921f..927545d 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -539,8 +539,7 @@ Wallet *FloweePay::createWallet(const QString &name) w->moveToThread(thread()); m_wallets.append(w); - emit walletsChanged(); - emit startSaveDate_priv(); // schedule a save of the new wallet + emit startSaveDate_priv(); // schedule a save of the m_wallets list return w; } @@ -792,6 +791,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; wallet->addPrivateKey(privateKey.trimmed().remove('\n'), startHeight); + emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -843,6 +843,7 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons derivationPath, startHeight); wallet->segment()->blockSynched(startHeight); wallet->segment()->blockSynched(startHeight); // yes, twice + emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -937,6 +938,7 @@ NewWalletConfig* FloweePay::createNewBasicWallet(const QString &walletName) { auto wallet = createWallet(walletName); wallet->createNewPrivateKey(walletStartHeightHint()); + emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); return new NewWalletConfig(wallet); @@ -969,6 +971,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const auto mnemonic = m_hdSeedValidator.generateMnemonic(seed, "en"); std::vector dp = HDMasterKey::deriveFromString(derivationPath.toStdString()); wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint()); + emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. diff --git a/src/FloweePay.h b/src/FloweePay.h index adf1b40..e2c1018 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -283,7 +283,7 @@ private slots: private: void init(); void saveAll(); - // create wallet and add to list. Please consider calling saveData after + // create wallet and add to list. Please consider calling walletsChanged() after Wallet *createWallet(const QString &name); uint32_t walletStartHeightHint() const; -- 2.54.0 From 46531d06cb9333770293418b1cf6f40914a484cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 17:58:00 +0100 Subject: [PATCH 0245/1428] UX fix: show account sync widget on importing wallet Seems like the Column is better for usage in a listview header as it actually honors the height change. --- guis/mobile/AccountHistory.qml | 2 +- guis/mobile/AccountSyncState.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 07abd51..d8db0e0 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -66,7 +66,7 @@ ListView { To avoid a mess of two scroll-areas (of Flickables) we simply make the top part into a header of the listview. */ - header: ColumnLayout { + header: Column { id: column width: root.width - 20 x: 10 diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 40f099b..f85d0b9 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -22,7 +22,7 @@ import QtQuick.Shapes // for shape-path Item { id: root - height: indicator.height + 3 + (root.uptodate ? 0 : circleShape.height) + height: indicator.height + 3 + (uptodate ? 0 : circleShape.height) property QtObject account: null property bool uptodate: false property int startPos: account.initialBlockHeight -- 2.54.0 From 933801339874e4868af2e9676634b0a7e3496f0d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 18:52:01 +0100 Subject: [PATCH 0246/1428] [UX] don't use 'unconfirmed' in the UI Instead use 'seen' for incoming and 'sending' for outgoing transactions. --- guis/mobile/AccountHistory.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index d8db0e0..9c712a3 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -218,8 +218,12 @@ ListView { color: palette.text text: { var minedHeight = model.height; - if (minedHeight === -1) - return qsTr("Unconfirmed") + if (minedHeight === -1) { // unconfirmed. + // We show a more 'in-progress' like string that indicates its not date-stamped yet. + if (model.fundsIn > 0) + return qsTr("Sending"); + return qsTr("Seen"); + } if (minedHeight === -2) return qsTr("Rejected") var date = model.date; -- 2.54.0 From 7d9be8026b94a6b223cfa39f6cc7f7c87eeafff9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jan 2023 19:11:23 +0100 Subject: [PATCH 0247/1428] Update 'behind' text to make progress clearer. We now update per day instead of per week. Nice for old imports. --- guis/mobile/AccountSyncState.qml | 11 +++++------ src/AccountInfo.cpp | 14 ++++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index f85d0b9..802ca94 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,15 +93,14 @@ Item { y: root.uptodate ? 0 : circleShape.height + 3 wrapMode: Text.Wrap text: { - var buddy = qsTr("Network Status") + ": "; if (isLoading) - return buddy; + return ""; var account = portfolio.current; if (account === null) - return buddy; + return ""; if (account.needsPinToOpen && !account.isDecrypted) - return buddy + qsTr("Offline"); - return buddy + account.timeBehind; + return qsTr("Status: Offline"); + return account.timeBehind; } font.italic: true Behavior on y { NumberAnimation { duration: 100 } } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 81d9919..84b1a5c 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -109,7 +109,7 @@ QString AccountInfo::timeBehind() const { const int accountHeight = lastBlockSynched(); if (accountHeight <= 0) // For accounts that only expect tx in the future. - return tr("Up to date"); + return tr("Wallet: Up to date"); const int chainHeight = FloweePay::instance()->chainHeight(); const int diff = chainHeight - accountHeight; @@ -117,16 +117,18 @@ QString AccountInfo::timeBehind() const return "--"; auto days = diff / 144.; auto weeks = diff / 1008.; - if (days > 10) - return tr("%1 weeks behind", "", std::ceil(weeks)).arg(std::ceil(weeks)); + if (days > 10) { + const int w = static_cast(weeks); + return tr("Behind: %1 weeks, %2 days", "counter on weeks", w).arg(w).arg(static_cast(days + 0.5) % 7); + } auto hours = diff / 6.; if (hours > 48) - return tr("%1 days behind", "", std::ceil(days)).arg(std::ceil(days)); + return tr("Behind: %1 days", "", std::ceil(days)).arg(std::ceil(days)); if (diff == 0) return tr("Up to date"); if (diff < 3 && !isArchived()) return tr("Updating"); - return tr("%1 hours behind", "", std::ceil(hours)).arg(std::ceil(hours)); + return tr("Still %1 hours behind", "", std::ceil(hours)).arg(std::ceil(hours)); } WalletHistoryModel *AccountInfo::historyModel() -- 2.54.0 From ca1cd4a9988e7e86accceb435c6e9734967f4301 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Jan 2023 14:06:10 +0100 Subject: [PATCH 0248/1428] Avoid possible deadlock. --- src/NetDataProvider.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 2c1231c..48715f1 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,8 +71,11 @@ void NetDataProvider::deleteNetPeer(int peerId) void NetDataProvider::updatePeers() { auto &conMan = FloweePay::instance()->p2pNet()->connectionManager(); - QMutexLocker l(&m_peerMutex); - QList peers(m_peers); + QList peers; + { + QMutexLocker l(&m_peerMutex); + peers = m_peers; + } bool stopTimer = true; for (auto &p : peers) { auto peer = conMan.peer(p->connectionId()); -- 2.54.0 From 50e2176efdebeae334f771cb23369bd2e68f9695 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Jan 2023 16:25:48 +0100 Subject: [PATCH 0249/1428] Remove unused property --- src/NetDataProvider.cpp | 14 +------------- src/NetDataProvider.h | 5 ----- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 48715f1..209f7d7 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -25,8 +25,7 @@ #include NetDataProvider::NetDataProvider(int initialBlockHeight, QObject *parent) - : QObject(parent), - m_blockHeight(initialBlockHeight) + : QObject(parent) { connect (this, SIGNAL(peerDeleted(int)), this, SLOT(deleteNetPeer(int)), Qt::QueuedConnection); // Make this thread-safe } @@ -90,12 +89,6 @@ void NetDataProvider::updatePeers() m_refreshTimer->stop(); } -void NetDataProvider::blockchainHeightChanged(int newHeight) -{ - m_blockHeight.storeRelease(newHeight); - emit blockHeightChanged(); -} - void NetDataProvider::punishMentChanged(int peerId) { QMutexLocker l(&m_peerMutex); @@ -117,11 +110,6 @@ QList NetDataProvider::peers() const return answer; } -int NetDataProvider::blockheight() const -{ - return m_blockHeight.loadAcquire(); -} - void NetDataProvider::startRefreshTimer() { /* diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index 4a24df3..dc25c77 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -31,19 +31,15 @@ class NetDataProvider : public QObject, public P2PNetInterface { Q_OBJECT Q_PROPERTY(QList peers READ peers NOTIFY peerListChanged) - Q_PROPERTY(int blockheight READ blockheight NOTIFY blockHeightChanged) public: explicit NetDataProvider(int initialBlockHeight, QObject *parent = nullptr); // P2PNetInterface void newPeer(int peerId, const std::string &userAgent, int startHeight, PeerAddress address) override; void lostPeer(int peerId) override; - void blockchainHeightChanged(int newHeight) override; void punishMentChanged(int peerId) override; QList peers() const; - int blockheight() const; - void startRefreshTimer(); signals: @@ -58,7 +54,6 @@ private slots: private: mutable QMutex m_peerMutex; QList m_peers; - QAtomicInt m_blockHeight; QTimer *m_refreshTimer = nullptr; }; -- 2.54.0 From caa340768fedfcce0cd647ebc7cba7c587dc1e55 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Jan 2023 20:36:12 +0100 Subject: [PATCH 0250/1428] Make 'chainHeight' property on Pay work better We now show the estimated tip until the headers chain has been synched. This follows p2pnet listener interfaces changes. This uses the nicer interfaces setup to make available for FloweePay the signal that we have reached the tip of the headerChain --- src/FloweePay.cpp | 19 ++++++++++++------- src/FloweePay.h | 11 ++++++----- src/NetDataProvider.cpp | 2 +- src/NetDataProvider.h | 2 +- src/Wallet.h | 3 ++- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 927545d..7698c8a 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -217,6 +217,7 @@ void FloweePay::init() Wallet *w = new Wallet(m_basedir.toStdString(), parser.intData(), walletEncryptionSeed); w->moveToThread(thread()); dl->addDataListener(w); + dl->addHeaderListener(w); dl->connectionManager().addPrivacySegment(w->segment()); m_wallets.append(w); logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId(); @@ -535,6 +536,7 @@ Wallet *FloweePay::createWallet(const QString &name) Wallet *w = Wallet::createWallet(m_basedir.toStdString(), id, name); dl->addDataListener(w); + dl->addHeaderListener(w); dl->connectionManager().addPrivacySegment(w->segment()); w->moveToThread(thread()); m_wallets.append(w); @@ -593,20 +595,17 @@ int FloweePay::expectedChainHeight() const int FloweePay::chainHeight() { - if (m_initialHeaderChainHeight <= 0) - m_initialHeaderChainHeight = headerChainHeight(); - const int hch = headerChainHeight(); - if (m_initialHeaderChainHeight == headerChainHeight()) { + if (!m_gotHeadersSyncedOnce) { const int expected = expectedChainHeight(); const int behind = expected - hch; // num blocks we are behind theoretical height - if (behind > 3) // don't report expected when variance could explain the diff + if (behind > 6) // don't report expected when variance could explain the diff return expected; } return headerChainHeight(); } -void FloweePay::blockchainHeightChanged(int newHeight) +void FloweePay::setHeaderSyncHeight(int newHeight) { if (m_wallets.count() > 1) { for (auto *wallet : m_wallets) { @@ -632,6 +631,12 @@ void FloweePay::blockchainHeightChanged(int newHeight) emit headerChainHeightChanged(); } +void FloweePay::headerSyncComplete() +{ + m_gotHeadersSyncedOnce = true; + emit headerChainHeightChanged(); +} + bool FloweePay::darkSkin() const { return m_darkSkin; @@ -1043,7 +1048,7 @@ DownloadManager *FloweePay::p2pNet() { if (m_downloadManager == nullptr) { m_downloadManager.reset(new DownloadManager(ioService(), m_basedir.toStdString(), m_chain)); - m_downloadManager->addP2PNetListener(this); + m_downloadManager->addHeaderListener(this); m_downloadManager->notifications().addListener(&m_notifications); QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); diff --git a/src/FloweePay.h b/src/FloweePay.h index e2c1018..7cdcc25 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include @@ -47,7 +47,7 @@ class CameraController; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); -class FloweePay : public QObject, public WorkerThreads, P2PNetInterface +class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterface { Q_OBJECT Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) @@ -205,8 +205,9 @@ public: */ int chainHeight(); - // P2PNetInterface interface - void blockchainHeightChanged(int newHeight); + // HeaderSyncInterface interface + void setHeaderSyncHeight(int height) override; + void headerSyncComplete() override; /** * Returns true if this is the mainchain. @@ -302,12 +303,12 @@ private: int m_dspTimeout = 5000; int m_windowWidth = 500; int m_windowHeight = 500; - int m_initialHeaderChainHeight = 0; int m_fontScaling = 100; bool m_darkSkin = true; bool m_createStartWallet = false; bool m_hideBalance = false; bool m_offline = false; + bool m_gotHeadersSyncedOnce = false; }; #endif diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 209f7d7..4ddc6e8 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -89,7 +89,7 @@ void NetDataProvider::updatePeers() m_refreshTimer->stop(); } -void NetDataProvider::punishMentChanged(int peerId) +void NetDataProvider::punishmentChanged(int peerId) { QMutexLocker l(&m_peerMutex); QList peers(m_peers); diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index dc25c77..f78b151 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -37,7 +37,7 @@ public: // P2PNetInterface void newPeer(int peerId, const std::string &userAgent, int startHeight, PeerAddress address) override; void lostPeer(int peerId) override; - void punishMentChanged(int peerId) override; + void punishmentChanged(int peerId) override; QList peers() const; void startRefreshTimer(); diff --git a/src/Wallet.h b/src/Wallet.h index 4b9a20e..927bce3 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -41,7 +42,7 @@ namespace P2PNet { struct Notification; } -class Wallet : public QObject, public DataListenerInterface +class Wallet : public QObject, public DataListenerInterface, public HeaderSyncInterface { Q_OBJECT public: -- 2.54.0 From 28d787b9c11b30fc72c874042b32d14d89aad262 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Jan 2023 20:36:40 +0100 Subject: [PATCH 0251/1428] On app shutdown, unsubscribe listeners. --- src/FloweePay.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 7698c8a..40532f9 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -177,7 +177,15 @@ FloweePay::~FloweePay() { saveData(); + auto *dl = m_downloadManager.get(); + if (dl) // p2pNet follows lazy initialization. + dl->removeHeaderListener(this); for (auto wallet : m_wallets) { + if (dl) { + dl->removeDataListener(wallet); + dl->removeHeaderListener(wallet); + dl->connectionManager().removePrivacySegment(wallet->segment()); + } try { wallet->saveWallet(); } catch (const std::exception &e) { -- 2.54.0 From d5d5c74cb428a06c305f663bec589bbc9381a33e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Feb 2023 12:46:30 +0100 Subject: [PATCH 0252/1428] Fixlet in unit test. --- testing/walletHistoryModel/TestWalletHistoryModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.cpp b/testing/walletHistoryModel/TestWalletHistoryModel.cpp index eaa0fb3..33f3a2b 100644 --- a/testing/walletHistoryModel/TestWalletHistoryModel.cpp +++ b/testing/walletHistoryModel/TestWalletHistoryModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -113,8 +113,8 @@ void TestWalletHistoryModel::basic() const auto now = QDateTime::currentDateTime(); auto today = now.date(); logFatal() << "dayOfWeek" << today.dayOfWeek(); - if (today.dayOfWeek() > 2) { - // that means we should have a 'EarlierThisWeek' group. + if (today.dayOfWeek() > 2 && today.dayOfWeek() <= today.day()) { + // that means we should have an 'EarlierThisWeek' group. groupId = model->data(model->index(24 * 6 * (today.dayOfWeek() - 1), 0), WalletHistoryModel::GroupId); QVERIFY(groupId.isValid()); -- 2.54.0 From 8b4e264c1d872d7d08fd71e2c988002e20dae3ee Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Feb 2023 12:47:08 +0100 Subject: [PATCH 0253/1428] Make grammar of comment easier to understand. --- src/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 69887d4..6c5df1f 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -338,7 +338,7 @@ void Wallet::newTransaction(const Tx &tx) // Remember the signature type used for specific private keys updateSignatureTypes(signatureTypes); - // Mark UTXOs locked that this tx spent to avoid double spending them. + // Mark as locked the UTXOs that this tx spent to avoid double spending them. for (auto i = wtx.inputToWTX.begin(); i != wtx.inputToWTX.end(); ++i) { m_lockedOutputs.insert(std::make_pair(i->second, m_nextWalletTransactionId)); } -- 2.54.0 From 9b1ec8e271c846c7cb320569957f82f5f59deb54 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Feb 2023 12:48:12 +0100 Subject: [PATCH 0254/1428] Make coinbase maturation smarter. Instead of recalculating every single block, we now use the existing locking of UTXOs feature to lock coinbase outputs to a certain maturation height. This is the traditional trade-off where we store some more data to avoid work in the common codepath. --- src/Wallet.cpp | 60 ++++++++++++++++++++++++++++++++++++++------------ src/Wallet.h | 10 ++++++--- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 6c5df1f..310e542 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -464,15 +464,24 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s m_lockedOutputs.insert(std::make_pair(key, -1)); // -1 is our reason } } + if (wtx.isCoinbase) { + // a coinbase can not be spent until its is under a list of blocks of MATURATION_AGE + // as such we need to lock the output from being used. + m_lockedOutputs.insert(std::make_pair(key, (blockHeight + MATURATION_AGE) * -1)); + } - // check the payment requests + // Check and update the private key data, if needed. const int privKeyId = i->second.walletSecretId; auto ws = m_walletSecrets.find(privKeyId); if (ws != m_walletSecrets.end() && ws->second.initialHeight == 0) { + // a private key has initialHeight set to zero if it comes from a HD seed, + // update this to the actual initial usage as we find a transaction matching it ws->second.initialHeight = blockHeight; + m_secretsChanged = true; needNewBloom = true; // make sure we let the remote know about our 'gap' addresses } + // check the payment requests for (auto prData : qAsConst(m_paymentRequests)) { if (prData.pr->m_privKeyId == privKeyId) { prData.pr->addPayment(key, i->second.value, blockHeight); @@ -1238,10 +1247,24 @@ void Wallet::setLastSynchedBlockHeight(int height) m_lastBlockHeightSeen = height; emit lastBlockSynchedChanged(); - // TODO the next line is very expensive and unneeded for 99.99% of the users. - // probably want a bool stating that this wallet has immature coinbases. - recalculateBalance(); - emit utxosChanged(); // in case there was an immature coinbase, this updates the balance + bool oneMatured = false; + auto i = m_lockedOutputs.begin(); + while (i != m_lockedOutputs.end()) { + if (i->second < -1) { + const auto matureHeight = i->second * -1; + if (matureHeight <= height) { + oneMatured = true; + i = m_lockedOutputs.erase(i); + continue; + } + } + ++i; + } + if (oneMatured) { + // when an immature coinbase became spendable, this updates the balance + recalculateBalance(); + emit utxosChanged(); + } if (height == FloweePay::instance()->headerChainHeight()) { // start this in my own thread and free of mutex-locks @@ -1858,10 +1881,8 @@ void Wallet::loadWallet() if (highestBlockHeight > 0) { m_segment->blockSynched(highestBlockHeight); m_segment->blockSynched(highestBlockHeight); // yes, twice. - } else { - // otherwise the blockSynced() implicitly calls this. - recalculateBalance(); } + recalculateBalance(); #ifdef DEBUGUTXO for (auto output : m_unspentOutputs) { @@ -1897,10 +1918,18 @@ void Wallet::loadWallet() for (auto &pair : m_lockedOutputs) { auto utxoLink = m_unspentOutputs.find(pair.first); assert(utxoLink != m_unspentOutputs.end()); - assert(pair.second >= -1); - if (pair.second > 0) { // zero means its user-locked, -1 is spam-locked + // zero means its user-locked, -1 is spam-locked + // -101 and further is a coinbase maturation height, times -1 + if (pair.second > 0) { auto w = m_walletTransactions.find(pair.second); - assert (w != m_walletTransactions.end()); + assert(w != m_walletTransactions.end()); + } + if (pair.second < -1) { // is coinbase lock + assert(pair.second < -1 * MATURATION_AGE); + const OutputRef ref(pair.first); + auto w = m_walletTransactions.find(ref.txIndex()); + assert(w != m_walletTransactions.end()); + assert(w->second.isCoinbase); } } #endif @@ -2070,13 +2099,16 @@ void Wallet::recalculateBalance() if (loi != m_lockedOutputs.end()) { // locked utxos are the result of already sent but not yet mined transactions // but also when the user goes and locks one manually. - if (loi->second != 0) // zero means user-locked. + if (loi->second < -1) { // locked coinbase where value is (the negative) maturation height + balanceImmature += utxo.second; continue; + } + else if (loi->second != 0) { // zero means user-locked. + continue; + } } if (h == WalletPriv::Unconfirmed) balanceUnconfirmed += utxo.second; - else if (wtx->second.isCoinbase && h + MATURATION_AGE > m_lastBlockHeightSeen) - balanceImmature += utxo.second; else balanceConfirmed += utxo.second; } diff --git a/src/Wallet.h b/src/Wallet.h index 927bce3..aa9a8c8 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -516,6 +516,7 @@ private: typedef boost::unordered_map TxIdCash; TxIdCash m_txidCache; // txid -> m_walletTransactions-id + boost::filesystem::path m_basedir; /* * Our little UTXO. * OutputRef -> value (in sat) @@ -525,12 +526,15 @@ private: * Unspent outputs can be 'locked' from spending for several reasons. User decided or * simply because an unconfirmed tx spent it. * We remember those here, where the 'key-value' pair follows the same concept for the - * key as the m_unspentoUutputs and + * key as the m_unspentOutputs and * the 'value' is the WalletTransaction index that actually was responsible for locking it. * or it is 0 if the locking was user-decided. + * + * The '-1' value is used to indicate this is a dusting attack UTXO. + * Other negative numbers are used to indicate the UTXO is a coinbase and the value is the + * height at which it matures, times -1. (example: -101 for block at height 1) */ std::map m_lockedOutputs; - boost::filesystem::path m_basedir; qint64 m_balanceConfirmed = 0; qint64 m_balanceImmature = 0; -- 2.54.0 From 42a4449ede4cd81328c6ff2128e2306a0cd68717 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Feb 2023 13:28:18 +0100 Subject: [PATCH 0255/1428] Move the popup component to its own file. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 91 +------------------------- guis/mobile/TxInfoSmall.qml | 113 +++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 90 deletions(-) create mode 100644 guis/mobile/TxInfoSmall.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index e17dafb..41c1691 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -42,5 +42,6 @@ mobile/VisualSeparator.qml mobile/PopupOverlay.qml mobile/TransactionDetails.qml + mobile/TxInfoSmall.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 9c712a3..46d3437 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -297,96 +297,7 @@ ListView { } Component { id: selectedItem - GridLayout { - id: transactionOptions - columns: 2 - rowSpacing: 10 - property QtObject infoObject: null - Flowee.LabelWithClipboard { - Layout.fillWidth: true - Layout.columnSpan: 2 - text: { - if (model.height === -2)// -2 is the magic block-height indicating 'rejected' - return qsTr("rejected") - if (typeof model.date === "undefined") - return qsTr("unconfirmed") - var confirmations = Pay.headerChainHeight - model.height + 1; - return qsTr("%1 confirmations (mined in block %2)", "", confirmations) - .arg(confirmations).arg(model.height); - } - clipboardText: model.height - menuText: qsTr("Copy block height") - } - Flowee.Label { - visible: model.height > 0 - text: qsTr("Mined") + ":" - } - Flowee.Label { - visible: model.height > 0 - text: model.height > 0 ? Pay.formatDateTime(model.date) : ""; - } - Flowee.Label { - id: paymentTypeLabel - visible: { - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // this is our heuristic to mark it as 'moved' - return false; - return true; - } - text: { - if (model.isCoinbase) - return qsTr("Miner Reward") + ":"; - if (model.isCashFusion) - return qsTr("Cash Fusion") + ":"; - if (model.fundsIn === 0) - return qsTr("Received") + ":"; - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // then the diff is likely just fees. - return qsTr("Moved") + ":"; - return qsTr("Sent") + ":"; - } - } - Flowee.BitcoinAmountLabel { - visible: paymentTypeLabel.visible - value: model.fundsOut - model.fundsIn - fiatTimestamp: model.date - } - Flowee.Label { - id: feesLabel - visible: transactionOptions.infoObject != null && transactionOptions.infoObject.createdByUs - text: qsTr("Fees") + ":" - } - Flowee.BitcoinAmountLabel { - visible: feesLabel.visible - value: { - if (transactionOptions.infoObject == null) - return 0; - if (!transactionOptions.infoObject.createdByUs) - return 0; - var amount = model.fundsIn; - var outputs = transactionOptions.infoObject.outputs - for (var i in outputs) { - amount -= outputs[i].value - } - return amount - } - fiatTimestamp: model.date - colorize: false - } - - TextButton { - id: txDetailsButton - Layout.columnSpan: 2 - text: qsTr("Transaction Details") - showPageIcon: true - onClicked: { - var newItem = thePile.push("./TransactionDetails.qml") - popupOverlay.close(); - newItem.transaction = model; - newItem.infoObject = transactionOptions.infoObject; - } - } - } + TxInfoSmall { } } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml new file mode 100644 index 0000000..2586e14 --- /dev/null +++ b/guis/mobile/TxInfoSmall.qml @@ -0,0 +1,113 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +GridLayout { + id: root + columns: 2 + rowSpacing: 10 + property QtObject infoObject: null + Flowee.LabelWithClipboard { + Layout.fillWidth: true + Layout.columnSpan: 2 + text: { + if (model.height === -2)// -2 is the magic block-height indicating 'rejected' + return qsTr("rejected") + if (typeof model.date === "undefined") + return qsTr("unconfirmed") + var confirmations = Pay.headerChainHeight - model.height + 1; + return qsTr("%1 confirmations (mined in block %2)", "", confirmations) + .arg(confirmations).arg(model.height); + } + clipboardText: model.height + menuText: qsTr("Copy block height") + } + Flowee.Label { + visible: model.height > 0 + text: qsTr("Mined") + ":" + } + Flowee.Label { + visible: model.height > 0 + text: model.height > 0 ? Pay.formatDateTime(model.date) : ""; + } + Flowee.Label { + id: paymentTypeLabel + visible: { + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // this is our heuristic to mark it as 'moved' + return false; + return true; + } + text: { + if (model.isCoinbase) + return qsTr("Miner Reward") + ":"; + if (model.isCashFusion) + return qsTr("Cash Fusion") + ":"; + if (model.fundsIn === 0) + return qsTr("Received") + ":"; + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // then the diff is likely just fees. + return qsTr("Moved") + ":"; + return qsTr("Sent") + ":"; + } + } + Flowee.BitcoinAmountLabel { + visible: paymentTypeLabel.visible + value: model.fundsOut - model.fundsIn + fiatTimestamp: model.date + } + Flowee.Label { + id: feesLabel + visible: root.infoObject != null && root.infoObject.createdByUs + text: qsTr("Fees") + ":" + } + Flowee.BitcoinAmountLabel { + visible: feesLabel.visible + value: { + if (root.infoObject == null) + return 0; + if (!root.infoObject.createdByUs) + return 0; + var amount = model.fundsIn; + var outputs = root.infoObject.outputs + for (var i in outputs) { + amount -= outputs[i].value + } + return amount + } + fiatTimestamp: model.date + colorize: false + } + + TextButton { + id: txDetailsButton + Layout.columnSpan: 2 + text: qsTr("Transaction Details") + showPageIcon: true + onClicked: { + var newItem = thePile.push("./TransactionDetails.qml") + popupOverlay.close(); + newItem.transaction = model; + newItem.infoObject = root.infoObject; + } + } +} -- 2.54.0 From 0fad45975b984d938398f52919a971efbda546e5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Feb 2023 19:57:40 +0100 Subject: [PATCH 0256/1428] Cleanup --- src/FloweePay.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 7cdcc25..faa1a92 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -41,7 +41,6 @@ class Wallet; class NewWalletConfig; class KeyId; class PriceDataProvider; -class PriceHistoryDataProvider; class CameraController; const std::string &chainPrefix(); -- 2.54.0 From 8ae25cbf64eaf4d46a86a27ab80a5fedf2096110 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Feb 2023 19:59:02 +0100 Subject: [PATCH 0257/1428] Refactor Fiat and historical. Move the historical fiat prices to share the API with the current ones and avoid complicating the code in the GUI layer (one less object). --- guis/Flowee/BitcoinAmountLabel.qml | 4 ++-- guis/mobile/AccountHistory.qml | 2 +- src/FloweePay.cpp | 26 +++----------------------- src/FloweePay.h | 2 -- src/PriceDataProvider.cpp | 27 +++++++++++++++++++++++++++ src/PriceDataProvider.h | 13 ++++++++++++- src/PriceHistoryDataProvider.h | 3 --- src/main_utils.cpp | 1 - src/main_utils_android.cpp | 1 - 9 files changed, 45 insertions(+), 34 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 3a5cd93..189200b 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -127,10 +127,10 @@ QQC2.Control { Layout.alignment: Qt.AlignBaseline text: { var fiatPrice; - if (root.fiatTimestamp == undefined || fiatHistory == null) + if (root.fiatTimestamp == undefined) fiatPrice = Fiat.price; // todays price else - fiatPrice = fiatHistory.historicalPrice(root.fiatTimestamp); + fiatPrice = Fiat.historicalPrice(root.fiatTimestamp); Fiat.formattedPrice(root.value, fiatPrice) } } diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 46d3437..7ce7d6a 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -260,7 +260,7 @@ ListView { if (typeof dat === "undefined") // unconfirmed transactions have no date var fiatPrice = Fiat.price; else - fiatPrice = fiatHistory.historicalPrice(dat); + fiatPrice = Fiat.historicalPrice(dat); return Fiat.formattedPrice(price.amountBch, fiatPrice); } anchors.centerIn: parent diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 40532f9..f9b496a 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -282,25 +282,10 @@ void FloweePay::loadingCompleted() for (auto wallet : m_wallets) { wallet->performUpgrades(); } - if (!m_offline && m_chain == P2PNet::MainChain) - m_prices->start(); if (m_chain == P2PNet::MainChain) { - m_priceHistory.reset(new PriceHistoryDataProvider(m_basedir, - QLocale::system().currencySymbol(QLocale::CurrencyIsoCode))); - - // take the last known price from our historical module to have something - // mostly useful until we manage to fetch the data from the life feeds. - auto price = m_priceHistory->historicalPrice(QDateTime::currentDateTimeUtc()); - if (price == 0) - price = 10000; // if we never fetched, set to 100,- - m_prices->mock(price); - if (!m_offline) { - m_priceHistory->initialPopulate(); - connect (m_prices.get(), &PriceDataProvider::priceChanged, - m_priceHistory.get(), [=](int price) { - m_priceHistory->addPrice(m_prices->currencyName(), QDateTime::currentSecsSinceEpoch(), price); - }); - } + m_prices->loadPriceHistory(m_basedir); + if (!m_offline) + m_prices->start(); } emit loadComplete(); } @@ -721,11 +706,6 @@ PriceDataProvider *FloweePay::prices() const return m_prices.get(); } -PriceHistoryDataProvider *FloweePay::priceHistory() const -{ - return m_priceHistory.get(); -} - bool FloweePay::isOffline() const { return m_offline; diff --git a/src/FloweePay.h b/src/FloweePay.h index faa1a92..3c1bf45 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -90,7 +90,6 @@ public: DownloadManager* p2pNet(); PriceDataProvider *prices() const; - PriceHistoryDataProvider *priceHistory() const; /// return the amount of milli-seconds we wait for a double-spent-proof int dspTimeout() const; @@ -295,7 +294,6 @@ private: std::string m_chainPrefix; std::unique_ptr m_downloadManager; std::unique_ptr m_prices; - std::unique_ptr m_priceHistory; NotificationManager m_notifications; CameraController* m_cameraController; QList m_wallets; diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 5a80129..6c5cce4 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -57,6 +57,8 @@ PriceDataProvider::PriceDataProvider(QObject *parent) : QObject(parent) void PriceDataProvider::start() { + if (m_priceHistory.get()) + m_priceHistory->initialPopulate(); m_timer.start(ReloadTimeout); fetch(); } @@ -127,6 +129,14 @@ QString PriceDataProvider::formattedPrice(int fiatValue) const } } +int PriceDataProvider::historicalPrice(const QDateTime ×tamp) const +{ + if (m_priceHistory.get() == nullptr) + return m_currentPrice.price; + + return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch()); +} + QString PriceDataProvider::priceToStringSimple(int cents) const { auto value = QString::number(cents); @@ -211,6 +221,23 @@ QString PriceDataProvider::currencySymbolPost() const return m_currencySymbolPost; } +void PriceDataProvider::loadPriceHistory(const QString &basedir) +{ + m_priceHistory.reset(new PriceHistoryDataProvider(basedir, + QLocale::system().currencySymbol(QLocale::CurrencyIsoCode))); + + // take the last known price from our historical module to have something + // mostly useful until we manage to fetch the data from the life feeds. + auto price = historicalPrice(QDateTime::currentDateTimeUtc()); + if (price == 0) + price = 10000; // if we never fetched, set to 100,- + m_currentPrice.price = price; + connect (this, &PriceDataProvider::priceChanged, + m_priceHistory.get(), [=](int price) { + m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); + }); +} + QString PriceDataProvider::currencySymbolPrefix() const { return m_currencySymbolPrefix; diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 5d6ab44..b1f0522 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -18,8 +18,10 @@ #ifndef PRICEDATAPROVIDER_H #define PRICEDATAPROVIDER_H -#include #include + +#include "PriceHistoryDataProvider.h" +#include #include class PriceDataProvider : public QObject @@ -59,6 +61,11 @@ public: */ Q_INVOKABLE QString formattedPrice(int cents) const; + /** + * Return the price at a certain time in the past. + */ + Q_INVOKABLE int historicalPrice(const QDateTime ×tamp) const; + /// return a string with the given price and needed decimal separator. /// Please note that the currency indicators are not included, unlike in formattedPrice() /// \see currencySymbolPrefix() @@ -87,6 +94,8 @@ public: */ QString currencySymbolPost() const; + void loadPriceHistory(const QString &basedir); + signals: void priceChanged(int32_t price); void currencySymbolChanged(); @@ -109,6 +118,8 @@ private: bool m_displayCents = true; // if true, display 2 digits behind the unit-separator int m_failedCount = 0; QTimer m_timer; + + std::unique_ptr m_priceHistory; }; #endif diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index 36045ec..7c0de80 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -35,9 +35,6 @@ public: void addPrice(const QString ¤cy, uint32_t timestamp, int price); - Q_INVOKABLE int historicalPrice(const QDateTime ×tamp) const { - return historicalPrice(timestamp.toSecsSinceEpoch()); - } int historicalPrice(uint32_t timestamp) const; QString currencyName() const; diff --git a/src/main_utils.cpp b/src/main_utils.cpp index d71b40e..cbc8fca 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -145,7 +145,6 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c portfolio->selectDefaultWallet(); engine.rootContext()->setContextProperty("net", netData); - engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); engine.rootContext()->setContextProperty("portfolio", portfolio); if (!cld->parser.isSet(cld->offline) && cld->parser.isSet(cld->connect)) { app->p2pNet()->connectionManager().peerAddressDb().addOne( // add it to the DB, making sure there is at least one. diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index b9df643..2aeda2c 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -108,7 +108,6 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) portfolio->selectDefaultWallet(); engine.rootContext()->setContextProperty("net", netData); - engine.rootContext()->setContextProperty("fiatHistory", FloweePay::instance()->priceHistory()); engine.rootContext()->setContextProperty("portfolio", portfolio); app->startNet(); // lets go! } -- 2.54.0 From 1a23c29f0108336cbc5b714b54c0e7fe3ad18b4c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Feb 2023 19:59:45 +0100 Subject: [PATCH 0258/1428] Iterate on the UX for the small tx info screen. Show more things we think actual users want to see. --- guis/mobile/TxInfoSmall.qml | 105 +++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 2586e14..35b5f72 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -21,33 +21,52 @@ import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; + GridLayout { id: root columns: 2 rowSpacing: 10 property QtObject infoObject: null - Flowee.LabelWithClipboard { - Layout.fillWidth: true + + property int minedHeight: model.height + + QQC2.Label { Layout.columnSpan: 2 + + property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' text: { - if (model.height === -2)// -2 is the magic block-height indicating 'rejected' - return qsTr("rejected") - if (typeof model.date === "undefined") - return qsTr("unconfirmed") - var confirmations = Pay.headerChainHeight - model.height + 1; - return qsTr("%1 confirmations (mined in block %2)", "", confirmations) - .arg(confirmations).arg(model.height); + if (isRejected) + return qsTr("Transaction is rejected") + if (typeof root.minedHeight < 1) + return qsTr("Processing") + return ""; + } + visible: text !== "" + color: { + if (isRejected) { + // Transaction is rejected by network + return Pay.useDarkSkin ? "#ec2327" : "#b41214"; + } + return mainWindow.palette.windowText } - clipboardText: model.height - menuText: qsTr("Copy block height") } + Flowee.Label { - visible: model.height > 0 + visible: root.minedHeight > 0 text: qsTr("Mined") + ":" } Flowee.Label { - visible: model.height > 0 - text: model.height > 0 ? Pay.formatDateTime(model.date) : ""; + Layout.fillWidth: true + visible: root.minedHeight > 0 + text: { + if (root.minedHeight <= 0) + return ""; + var rc = Pay.formatDateTime(model.date); + var confirmations = Pay.headerChainHeight - root.minedHeight + 1; + if (confirmations > 0 && confirmations < 100) + rc += " (" + qsTr("%1 blocks ago", confirmations).arg(confirmations) + ")"; + return rc; + } } Flowee.Label { id: paymentTypeLabel @@ -71,31 +90,53 @@ GridLayout { } } Flowee.BitcoinAmountLabel { + Layout.fillWidth: true visible: paymentTypeLabel.visible value: model.fundsOut - model.fundsIn fiatTimestamp: model.date + showFiat: false // might not fit + } + + // price at mining + // value in exchange gained + Flowee.Label { + id: priceAtMining + visible: { + if (root.minedHeight < 1) + return false; + if (model.isCashFusion) + return false; + let diff = model.fundsOut - model.fundsIn; + if (diff < 0 && diff > -1000) // then the diff is likely just fees. + return false; + return true; + } + text: qsTr("Value then") + ":" } Flowee.Label { - id: feesLabel - visible: root.infoObject != null && root.infoObject.createdByUs - text: qsTr("Fees") + ":" + Layout.fillWidth: true + id: valueThenLabel + visible: priceAtMining.visible + property int fiatPrice: visible ? Fiat.historicalPrice(model.date) : 0; + text: Fiat.formattedPrice(Math.abs(model.fundsOut - model.fundsIn), fiatPrice) } - Flowee.BitcoinAmountLabel { - visible: feesLabel.visible - value: { - if (root.infoObject == null) - return 0; - if (!root.infoObject.createdByUs) - return 0; - var amount = model.fundsIn; - var outputs = root.infoObject.outputs - for (var i in outputs) { - amount -= outputs[i].value - } - return amount + Flowee.Label { + visible: priceAtMining.visible + text: qsTr("Value now") + ":" + } + Flowee.Label { + Layout.fillWidth: true + visible: priceAtMining.visible + text: { + if (root.minedHeight <= 0) + return ""; + var fiatPriceNow = Fiat.price; + var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 + + var sats = Math.abs(model.fundsOut - model.fundsIn); + return Fiat.formattedPrice(sats, fiatPriceNow) + + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; } - fiatTimestamp: model.date - colorize: false } TextButton { -- 2.54.0 From 69b82238ec760fa32f53b6eeb5da08519b471fb9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 Feb 2023 22:42:37 +0100 Subject: [PATCH 0259/1428] New main year. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 171070f..7f6deaa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2022.12.3"); + qapp.setApplicationVersion("2023.01.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 996e5644a5d84580d4799fcc6a2c5cd262a54463 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 19:44:53 +0100 Subject: [PATCH 0260/1428] Share code, split out HamburgerMenu.qml --- guis/Flowee/HamburgerMenu.qml | 36 +++++++++++++++++++++++++++++++++++ guis/desktop/ConfigItem.qml | 19 ++++-------------- guis/mobile/MainViewBase.qml | 14 ++------------ guis/widgets.qrc | 1 + 4 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 guis/Flowee/HamburgerMenu.qml diff --git a/guis/Flowee/HamburgerMenu.qml b/guis/Flowee/HamburgerMenu.qml new file mode 100644 index 0000000..490bafb --- /dev/null +++ b/guis/Flowee/HamburgerMenu.qml @@ -0,0 +1,36 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Column { + id: root + spacing: 3 + y: 1 // move the column down to account for the anti-alias line of the rectangle below + + property bool wide: false + + Repeater { + model: 3 + delegate: Rectangle { + color: "white" + width: root.wide ? 12 : 4 + height: 3 + radius: 2 + } + } +} diff --git a/guis/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml index a74de5d..3aeebfb 100644 --- a/guis/desktop/ConfigItem.qml +++ b/guis/desktop/ConfigItem.qml @@ -17,13 +17,14 @@ */ import QtQuick import QtQuick.Controls +import "../Flowee" as Flowee Item { id: root width: wide ? 12 : 4 height: column.height property color color: Pay.useDarkSkin ? "white" : "black" - property bool wide: false + property alias wide: hamburgerMenu default property alias actions: ourMenu.contentData /// emitted when the menu is about to open. signal aboutToOpen; @@ -40,20 +41,8 @@ Item { } } - Column { - id: column - spacing: 3 - y: 1 // move the column down to account for the anti-alias line of the rectangle below - - Repeater { - model: 3 - delegate: Rectangle { - color: root.color - width: root.wide ? 12 : 4 - height: 3 - radius: 2 - } - } + Flowee.HamburgerMenu { + id: hamburgerMenu } MouseArea { anchors.fill: parent diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 0f6879e..63cd88a 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -62,21 +62,11 @@ QQC2.Control { height: 50 color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue - Column { + Flowee.HamburgerMenu { id: menuButton - spacing: 3 anchors.verticalCenter: parent.verticalCenter + wide: true x: 8 - - Repeater { - model: 3 - delegate: Rectangle { - color: "white" - width: 12 - height: 3 - radius: 2 - } - } } MouseArea { anchors.fill: menuButton diff --git a/guis/widgets.qrc b/guis/widgets.qrc index dc2ba8b..7f4f7e1 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -32,5 +32,6 @@ Flowee/ImageButton.qml Flowee/AccountTypeLabel.qml Flowee/WalletSecretsView.qml + Flowee/HamburgerMenu.qml -- 2.54.0 From 7f2deb9399373a74c25c4951062bb4c334a814d8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 20:29:03 +0100 Subject: [PATCH 0261/1428] Make AccountSelector its own widget. --- guis/mobile.qrc | 1 + guis/mobile/AccountSelector.qml | 100 ++++++++++++++++++++++++++++++++ guis/mobile/MainViewBase.qml | 83 +------------------------- 3 files changed, 103 insertions(+), 81 deletions(-) create mode 100644 guis/mobile/AccountSelector.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 41c1691..6bc8b7a 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -43,5 +43,6 @@ mobile/PopupOverlay.qml mobile/TransactionDetails.qml mobile/TxInfoSmall.qml + mobile/AccountSelector.qml diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml new file mode 100644 index 0000000..7ee0417 --- /dev/null +++ b/guis/mobile/AccountSelector.qml @@ -0,0 +1,100 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee + +QQC2.Popup { + id: accountSelector + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent + height: columnLayout.height + 10 + + Connections { + target: menuOverlay + function onOpenChanged() { accountSelector.close(); } + } + + background: Rectangle { + color: mainWindow.palette.base + border.color: mainWindow.palette.highlight + border.width: 1 + radius: 5 + } + + ColumnLayout { + id: columnLayout + width: parent.width + + Flowee.Label { + text: qsTr("Your Wallets") + font.bold: true + } + + Repeater { // portfolio holds all our accounts + width: parent.width + model: portfolio.accounts + delegate: Item { + width: columnLayout.width + height: accountName.height + lastActive.height + 6 * 3 + Rectangle { + color: root.palette.button + radius: 5 + anchors.fill: parent + visible: modelData === portfolio.current + } + Flowee.Label { + id: accountName + y: 6 + x: 6 + text: modelData.name + } + Flowee.Label { + id: fiat + anchors.top: accountName.top + anchors.right: parent.right + anchors.rightMargin: 6 + text: Fiat.formattedPrice(modelData.balanceConfirmed + modelData.balanceUnconfirmed, Fiat.price) + } + Flowee.Label { + id: lastActive + anchors.top: accountName.bottom + anchors.left: accountName.left + text: qsTr("last active") + ": " + Pay.formatDate(modelData.lastMinedTransaction) + font.pixelSize: root.font.pixelSize * 0.8 + font.bold: false + } + Flowee.BitcoinAmountLabel { + anchors.right: fiat.right + anchors.top: lastActive.top + font.pixelSize: lastActive.font.pixelSize + showFiat: false + value: modelData.balanceConfirmed + modelData.balanceUnconfirmed + colorize: false + } + MouseArea { + anchors.fill: parent + onClicked: { + portfolio.current = modelData + accountSelector.close(); + } + } + } + } + } +} diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 63cd88a..5ef1ef0 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -120,89 +120,10 @@ QQC2.Control { anchors.baseline: logo.baseline } - QQC2.Popup { + AccountSelector { id: accountSelector - closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent - y: header.height width: root.width - height: columnLayout.height - - Connections { - target: menuOverlay - function onOpenChanged() { accountSelector.close(); } - } - - ColumnLayout { - id: columnLayout - width: parent.width - - Flowee.Label { - text: qsTr("Your Wallets") - font.bold: true - } - - Repeater { // portfolio holds all our accounts - width: parent.width - model: portfolio.accounts - delegate: Item { - width: columnLayout.width - height: accountName.height + lastActive.height + 6 * 3 - Rectangle { - color: root.palette.button - radius: 5 - anchors.fill: parent - visible: modelData === portfolio.current - } - Flowee.Label { - id: accountName - y: 6 - x: 6 - text: modelData.name - } - Flowee.Label { - id: fiat - anchors.top: accountName.top - anchors.right: parent.right - anchors.rightMargin: 6 - text: Fiat.formattedPrice(modelData.balanceConfirmed + modelData.balanceUnconfirmed, Fiat.price) - } - Flowee.Label { - id: lastActive - anchors.top: accountName.bottom - anchors.left: accountName.left - text: qsTr("last active") + ": " + Pay.formatDate(modelData.lastMinedTransaction) - font.pixelSize: root.font.pixelSize * 0.8 - font.bold: false - } - Flowee.BitcoinAmountLabel { - anchors.right: fiat.right - anchors.top: lastActive.top - font.pixelSize: lastActive.font.pixelSize - showFiat: false - value: modelData.balanceConfirmed + modelData.balanceUnconfirmed - colorize: false - } - MouseArea { - anchors.fill: parent - onClicked: { - portfolio.current = modelData - accountSelector.close(); - } - } - } - } - Item { - // horizontal divider. - width: parent.width - height: 1 - Rectangle { - height: 1 - width: parent.width / 10 * 7 - x: (parent.width - width) / 2 // center in column - color: root.palette.highlight - } - } - } + y: header.height } } -- 2.54.0 From b5877442b06e8e86428d07c201010d1c78279805 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 20:30:33 +0100 Subject: [PATCH 0262/1428] Fix sizing and positioning issues. Using a layout as a top level reusable widget may to give weird results on usage if the user doesn't realize its a layout, so wrap it in an item. --- guis/Flowee/HamburgerMenu.qml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/guis/Flowee/HamburgerMenu.qml b/guis/Flowee/HamburgerMenu.qml index 490bafb..7a12bbc 100644 --- a/guis/Flowee/HamburgerMenu.qml +++ b/guis/Flowee/HamburgerMenu.qml @@ -17,20 +17,23 @@ */ import QtQuick -Column { +Item { id: root - spacing: 3 - y: 1 // move the column down to account for the anti-alias line of the rectangle below - + implicitWidth: root.wide ? 12 : 4 + implicitHeight: 16 property bool wide: false - Repeater { - model: 3 - delegate: Rectangle { - color: "white" - width: root.wide ? 12 : 4 - height: 3 - radius: 2 + Column { + spacing: 3 + y: 1 // move the column down to account for the anti-alias line of the rectangle below + Repeater { + model: 3 + delegate: Rectangle { + color: "white" + width: root.wide ? 12 : 4 + height: 3 + radius: 2 + } } } } -- 2.54.0 From a224ceb7bcc0d7fbc7851b086dacd6e5bae00b36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 21:54:04 +0100 Subject: [PATCH 0263/1428] Remove flickable This does not conceptually seem to work as intended, maybe try something else later.. --- guis/mobile/Page.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 54aaeb3..528cec2 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -85,13 +85,12 @@ QQC2.Control { FocusScope { id: focusScope anchors.fill: parent - Flickable { + QQC2.Control { id: child width: root.width - 20 x: 10 y: header.height + 10 height: root.height - y - clip: true } } Keys.onPressed: (event)=> { -- 2.54.0 From 7e04e5c26ce35ea47127069a1773f0cebdebd81f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 21:54:55 +0100 Subject: [PATCH 0264/1428] Remove no longer needed hack. Since we moved the editing to no longer need this, the width hack is no longer needed. --- guis/Flowee/BitcoinAmountLabel.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 189200b..554b22e 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -39,7 +39,7 @@ QQC2.Control { property alias fontPixelSize: main.font.pixelSize implicitHeight: row.implicitHeight - implicitWidth: Math.max(row.maxWidth, row.implicitWidth) + implicitWidth: row.implicitWidth height: main.height width: implicitWidth @@ -51,9 +51,6 @@ QQC2.Control { id: row height: parent.height - property int maxWidth: 0 - onImplicitWidthChanged: maxWidth = Math.max(maxWidth, implicitWidth) - // calculated property string amountString: ""; Connections { -- 2.54.0 From eced0ffa1581addd132762c58a69e41c85874fb5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 21:55:54 +0100 Subject: [PATCH 0265/1428] Next iteration of the PayWithQR page this solves a bunch of UX problems, but we need to re-add some features in popups. Which is for tomorrow. --- guis/images/bch.svg | 4 + guis/mobile.qrc | 1 + guis/mobile/AccountSelector.qml | 17 +-- guis/mobile/MainViewBase.qml | 2 + guis/mobile/PayWithQR.qml | 177 +++++++++++++++++++++++++------- 5 files changed, 155 insertions(+), 46 deletions(-) create mode 100644 guis/images/bch.svg diff --git a/guis/images/bch.svg b/guis/images/bch.svg new file mode 100644 index 0000000..8896981 --- /dev/null +++ b/guis/images/bch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6bc8b7a..701b708 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -2,6 +2,7 @@ images/FloweePay-light.svg images/FloweePay.svg + images/bch.svg mobile/images/back-arrow.svg mobile/images/maslenica.svg mobile/images/moon.svg diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml index 7ee0417..7f82cbb 100644 --- a/guis/mobile/AccountSelector.qml +++ b/guis/mobile/AccountSelector.qml @@ -21,18 +21,21 @@ import QtQuick.Layouts import "../Flowee" as Flowee QQC2.Popup { - id: accountSelector + id: root closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent height: columnLayout.height + 10 + focus: true // to allow Escape to close it + + property QtObject selectedAccount: portfolio.current Connections { target: menuOverlay - function onOpenChanged() { accountSelector.close(); } + function onOpenChanged() { root.close(); } } background: Rectangle { - color: mainWindow.palette.base - border.color: mainWindow.palette.highlight + color: root.palette.base + border.color: root.palette.highlight border.width: 1 radius: 5 } @@ -56,7 +59,7 @@ QQC2.Popup { color: root.palette.button radius: 5 anchors.fill: parent - visible: modelData === portfolio.current + visible: modelData === root.selectedAccount } Flowee.Label { id: accountName @@ -90,8 +93,8 @@ QQC2.Popup { MouseArea { anchors.fill: parent onClicked: { - portfolio.current = modelData - accountSelector.close(); + root.selectedAccount = modelData + root.close(); } } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 5ef1ef0..4bc00cf 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -124,6 +124,8 @@ QQC2.Control { id: accountSelector width: root.width y: header.height + + onSelectedAccountChanged: portfolio.current = selectedAccount } } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 6f2e1c1..92abe53 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,11 +25,12 @@ Page { id: root headerText: qsTr("Approve Payment") + Item { // data QRScanner { id: scanner scanType: QRScanner.PaymentDetails - autostart: true + // autostart: true onFinished: { var rc = scanResult if (rc === "") { // scanning failed @@ -50,67 +51,70 @@ Page { onIsValidChanged: if (isValid) prepare() // easier testing values (for when you don't have a camera) - // paymentAmount: 100000000 - // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" - // userComment: "bla bla bla" + paymentAmount: 100000000 + targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" + userComment: "bla bla bla" + } + + AccountSelector { + id: accountSelector + width: root.width + x: -10 // to correct the indent added in the fullPage + y: (root.height - height) / 2 + onSelectedAccountChanged: payment.account = selectedAccount + selectedAccount: payment.account } } // if true, the bitcoin value is provided by the user (or QR), and the fiat follows // if false, the user edits the fiat price and the bitcoin value is calculated. - // Notice that 'sendAllButton' overrules both and gets the data from the wallet-total + // Notice that 'send all' overrules both and gets the data from the wallet-total property bool fiatFollowsSats: true Flowee.BitcoinValueField { id: priceBch - y: 10 - value: { - if (sendAllButton.checked) // we use 'max' - return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed - return payment.paymentAmount - } + y: root.fiatFollowsSats ? 5 : 68 + value: payment.paymentAmount focus: true fontPixelSize: size - property double size: (fiatFollowsSats && !sendAllButton.checked) ? 38 : commentLabel.font.pixelSize + property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize* 0.8 onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true Behavior on size { NumberAnimation { } } - Flowee.ObjectShaker { id: bchShaker } + Behavior on y { NumberAnimation { } } + Flowee.ObjectShaker { id: bchShaker } // 'shake' to give feedback on mistakes - // auto-unchecks 'max' + // this unchecks 'max' on user editing of the value onValueEdited: payment.paymentAmount = value } + MouseArea { + /* Since the valueField is centred but only allows clicking on its active surface, + we provide this one to make it possible to click on the full width and activate it. + */ + width: root.width + height: priceBch.height + y: priceBch.y + onClicked: priceBch.forceActiveFocus(); + } + Flowee.FiatValueField { id: priceFiat value: Fiat.priceFor(priceBch.value, payment.fiatPrice); - anchors.top: priceBch.bottom - anchors.topMargin: 18 + y: root.fiatFollowsSats ? 68 : 5 focus: true fontPixelSize: size - property double size: (!fiatFollowsSats && !sendAllButton.checked) ? 38 : commentLabel.font.pixelSize + property double size: !fiatFollowsSats ? 38 : commentLabel.font.pixelSize * 0.8 onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false Behavior on size { NumberAnimation { } } + Behavior on y { NumberAnimation { } } Flowee.ObjectShaker { id: fiatShaker } onValueEdited: payment.details[0].fiatAmount = value } - Flowee.Button { - id: sendAllButton - text: qsTr("Send All") - checkable: true - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: priceFiat.bottom - anchors.topMargin: 10 - checked: payment.details[0].maxSelected - onClicked: payment.details[0].maxSelected = checked - } - Flowee.Label { id: commentLabel text: qsTr("Payment description" + ":") visible: userComment.text !== "" - - anchors.top: sendAllButton.bottom - anchors.topMargin: 20 + y: 100 } Flowee.Label { id: userComment @@ -121,21 +125,116 @@ Page { anchors.top: commentLabel.bottom anchors.topMargin: 5 } + + Rectangle { + id: inputChooser + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: userComment.bottom + border.width: 1 + border.color: root.palette.highlight + color: root.palette.base + width: inputs.width + 20 + height: 40 + radius: 15 + + Row { + id: inputs + x: 10 + y: 7.5 + spacing: 16 + Image { + id: logo + source: "qrc:/bch.svg" + width: 25 + height: 25 + smooth: true + + MouseArea { + anchors.fill: parent + onClicked: priceBch.forceActiveFocus(); + } + } + + Flowee.Label { + text: Fiat.currencySymbolPost + Fiat.currencySymbolPrefix + width: 24 + font.pixelSize: 32 + horizontalAlignment: Text.horizontalCenter + anchors.baseline: logo.bottom + + MouseArea { + anchors.fill: parent + onClicked: priceFiat.forceActiveFocus(); + } + } + + /* TODO + Flowee.HamburgerMenu { + wide: true + y: 6 + MouseArea { + anchors.fill: parent + // onClicked: + // TODO show small menu with last 5 chosen fiat currencies + // and a 'more' item which leads to a separate page for all + // possible currencies. + } + }*/ + } + + Rectangle { + color: root.palette.text + opacity: 0.3 + radius: 6 + width: 35 + height: parent.height - 10 + y: 5 + x: root.fiatFollowsSats ? 5 : 45 + + Behavior on x { NumberAnimation { } } + } + } + + + Flowee.Label { id: errorLabel text: payment.error visible: payment.isValid color: Pay.useDarkSkin ? "#cc5454" : "#6a0c0c" - anchors.top: userComment.bottom + anchors.bottom: walletNameBackground.top wrapMode: Text.Wrap - anchors.topMargin: 20 + anchors.bottomMargin: 10 width: parent.width } + Rectangle { + id: walletNameBackground + anchors.top: currentWalletLabel.top + anchors.topMargin: -5 + width: parent.width + anchors.bottom: currentWalletValue.bottom + anchors.bottomMargin: -5 + color: root.palette.alternateBase + + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (mouse.x < parent.width / 2) { + accountSelector.open(); + } else { + // TODO open popup on price. + // this should include a 'max-price' option like this; + // payment.details[0].maxSelected = checked + } + } + } + } + Flowee.Label { id: currentWalletLabel - text: portfolio.current.name - visible: portfolio.current.isUserOwned + text: payment.account.name + visible: payment.account.isUserOwned anchors.baseline: parent.width > currentWalletLabel.width + currentWalletValue.width ? currentWalletValue.baseline : undefined anchors.bottom: parent.width > currentWalletLabel.width + currentWalletValue.width @@ -145,9 +244,9 @@ Page { id: currentWalletValue anchors.right: parent.right anchors.bottom: numericKeyboard.top - anchors.bottomMargin: 20 + anchors.bottomMargin: 10 value: { - var wallet = portfolio.current; + var wallet = payment.account; return wallet.balanceConfirmed + wallet.balanceUnconfirmed; } } @@ -157,7 +256,7 @@ Page { anchors.bottom: slideToApprove.top anchors.bottomMargin: 15 width: parent.width - enabled: !sendAllButton.checked + enabled: !payment.details[0].maxSelected Repeater { model: 12 delegate: Item { -- 2.54.0 From 834d59d82e6cf566d995117f3a3967ccc4a5cd64 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Feb 2023 21:56:37 +0100 Subject: [PATCH 0266/1428] Fix reference --- android/build-pay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 941e00d..89d5d68 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -138,7 +138,7 @@ if test -z "\$DOCKERID"; then --volume=`pwd`:/home/builds/build \\ --volume=$floweePaySrcDir:/home/builds/src \\ --volume=$_thehub_dir_:/home/builds/floweelibs \\ - flowee/buildenv-android:v6.4.1 /bin/bash\` + ${_docker_name_} /bin/bash\` echo "\$DOCKERID" > .docker fi execInDocker="docker container exec --workdir /home/builds --user \`id -u\` \$DOCKERID" -- 2.54.0 From 55dc1a61a5b34126af50a33183186f1eb2a11aa8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Feb 2023 11:44:31 +0100 Subject: [PATCH 0267/1428] Another case where Column beats ColumnLayout While the latter is supposed to be next-gen, its also quite limited. In this case the first (header) row-height was duplicated to all rows in the column, causing the sizing to be quite bad. Seems that the Column is needed when you have non-equal height rows. --- guis/mobile/AccountSelector.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml index 7f82cbb..611edd0 100644 --- a/guis/mobile/AccountSelector.qml +++ b/guis/mobile/AccountSelector.qml @@ -40,7 +40,7 @@ QQC2.Popup { radius: 5 } - ColumnLayout { + Column { id: columnLayout width: parent.width -- 2.54.0 From 4b10ba1d2088e26eebc4f1b8b962755e89d6235a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Feb 2023 12:14:04 +0100 Subject: [PATCH 0268/1428] [UX] make sizing buttons bigger to easier hit them. --- guis/mobile/GuiSettings.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 69447d0..9370d3c 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,11 +36,10 @@ Page { property double buttonWidth: width / 6 Repeater { model: 6 - delegate: MouseArea { + delegate: Item { width: fontSizing.buttonWidth height: 30 property int target: index * 25 + 75 - onClicked: Pay.fontScaling = target Rectangle { width: parent.width - 5 @@ -55,10 +54,13 @@ Page { anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter } + MouseArea { + anchors.fill: parent + anchors.topMargin: -30 + onClicked: Pay.fontScaling = target + } } } - } - } } -- 2.54.0 From 00dae95023f274b15e91f872e8da39a26ea5851f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Feb 2023 14:38:33 +0100 Subject: [PATCH 0269/1428] Allow changing of the currently used currency. Provide a QML point where a different locale can be given for a different currency. This then swaps out the historical file to the new one and fetches from the online stream in that currency. --- src/PriceDataProvider.cpp | 64 ++++++++++++++++++++++++++------------- src/PriceDataProvider.h | 9 ++++-- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 6c5cce4..919c368 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -34,9 +34,31 @@ static constexpr int ReloadTimeout = 7 * 60 * 1000; PriceDataProvider::PriceDataProvider(QObject *parent) : QObject(parent) { - QLocale here(QLocale::system()); - m_currency = here.currencySymbol(QLocale::CurrencyIsoCode); - m_currencySymbolPrefix = here.currencySymbol(QLocale::CurrencySymbol); + setCurrency(QLocale::system()); + QObject::connect(&m_timer, SIGNAL(timeout()), this, SLOT(fetch())); +} + +void PriceDataProvider::start() +{ + if (m_priceHistory.get()) + m_priceHistory->initialPopulate(); + m_timer.start(ReloadTimeout); + fetch(); +} + +void PriceDataProvider::mock(int price) +{ + m_currentPrice.price = price; +} + +void PriceDataProvider::setCurrency(const QLocale &countryLocale) +{ + auto newCurrency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); + if (m_currency == newCurrency) + return; + m_currency = newCurrency; + m_currencySymbolPrefix = countryLocale.currencySymbol(QLocale::CurrencySymbol); + m_currencySymbolPost.clear(); if (m_currency == QLatin1String("AZN") || m_currency == QLatin1String("ILS") || m_currency == QLatin1String("IRR") @@ -52,20 +74,21 @@ PriceDataProvider::PriceDataProvider(QObject *parent) : QObject(parent) // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") || m_currency == QLatin1String("NOK")); - QObject::connect(&m_timer, SIGNAL(timeout()), this, SLOT(fetch())); + + emit currencySymbolChanged(); + if (!m_basedir.isEmpty()) { + assert(m_priceHistory.get()); + // we need to replace the history of our coin to the new history of the new coin. + loadPriceHistory(m_basedir); + + if (m_timer.isActive()) // implies we are start()-ed + start(); + } } -void PriceDataProvider::start() +void PriceDataProvider::setCurrency(const QString &countrycode) { - if (m_priceHistory.get()) - m_priceHistory->initialPopulate(); - m_timer.start(ReloadTimeout); - fetch(); -} - -void PriceDataProvider::mock(int price) -{ - m_currentPrice.price = price; + setCurrency(QLocale(countrycode)); } QString PriceDataProvider::formattedPrice(double amountSats, int price) const @@ -223,15 +246,14 @@ QString PriceDataProvider::currencySymbolPost() const void PriceDataProvider::loadPriceHistory(const QString &basedir) { - m_priceHistory.reset(new PriceHistoryDataProvider(basedir, - QLocale::system().currencySymbol(QLocale::CurrencyIsoCode))); - + m_basedir = basedir; + m_priceHistory.reset(new PriceHistoryDataProvider(basedir, m_currency)); // take the last known price from our historical module to have something // mostly useful until we manage to fetch the data from the life feeds. - auto price = historicalPrice(QDateTime::currentDateTimeUtc()); - if (price == 0) - price = 10000; // if we never fetched, set to 100,- - m_currentPrice.price = price; + auto lastKnownPrice = historicalPrice(QDateTime::currentDateTimeUtc()); + if (lastKnownPrice == 0) + lastKnownPrice = 10000; // if we never fetched, set to 100,- + m_currentPrice.price = lastKnownPrice; connect (this, &PriceDataProvider::priceChanged, m_priceHistory.get(), [=](int price) { m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index b1f0522..e21874f 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -29,9 +29,9 @@ class PriceDataProvider : public QObject Q_OBJECT Q_PROPERTY(int price READ price NOTIFY priceChanged) Q_PROPERTY(QString currencyName READ currencyName NOTIFY currencySymbolChanged) - Q_PROPERTY(bool displayCents READ displayCents CONSTANT) - Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix CONSTANT) - Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost CONSTANT) + Q_PROPERTY(bool displayCents READ displayCents NOTIFY currencySymbolChanged) + Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) + Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost NOTIFY currencySymbolChanged) public: explicit PriceDataProvider(QObject *parent = nullptr); @@ -46,6 +46,8 @@ public: int price() const { return m_currentPrice.price; } + void setCurrency(const QLocale &countryLocale); + Q_INVOKABLE void setCurrency(const QString &countrycode); /** * Return a formatted string with the locale-defined price of the amount of \a sats. @@ -119,6 +121,7 @@ private: int m_failedCount = 0; QTimer m_timer; + QString m_basedir; // where the priceHistory is stored. std::unique_ptr m_priceHistory; }; -- 2.54.0 From 6c95cd3c093ed428c2fc8f387e232b4d85465b42 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Feb 2023 17:51:29 +0100 Subject: [PATCH 0270/1428] Make currency changes persist between restarts. --- guis/mobile/defaults.ini | 3 ++- src/FloweePay.cpp | 32 ++++++++++++++++++++++++++++++-- src/FloweePay.h | 6 ++++++ src/PriceDataProvider.cpp | 9 ++++++--- src/PriceDataProvider.h | 4 ++-- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/guis/mobile/defaults.ini b/guis/mobile/defaults.ini index 3550a45..bffbf29 100644 --- a/guis/mobile/defaults.ini +++ b/guis/mobile/defaults.ini @@ -16,9 +16,10 @@ unit=0 # wallet so the user can instantly start receiving. create-start-wallet=true +countryCodes=en_US, en_GB, nl_NL, zh_CN + [payment] # Double Spend Proof timeout in milliseconds. # After the timeout is reached we deem the tx safe. #dsp-timeout=3000 - diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index f9b496a..19bcf26 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -53,6 +53,8 @@ constexpr const char *DARKSKIN = "darkSkin"; constexpr const char *HIDEBALANCE = "hideBalance"; constexpr const char *USERAGENT = "net/useragent"; constexpr const char *DSPTIMEOUT = "payment/dsp-timeout"; +constexpr const char *CURRENCY_COUNTRIES = "countryCodes"; // historical +constexpr const char *CURRENCY_COUNTRY = "countryCode"; // current constexpr const char *AppdataFilename = "/appdata"; // used for the default wallet @@ -69,8 +71,7 @@ static P2PNet::Chain s_chain = P2PNet::MainChain; FloweePay::FloweePay() : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), - m_chain(s_chain), - m_prices(new PriceDataProvider()) + m_chain(s_chain) { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); @@ -122,6 +123,7 @@ FloweePay::FloweePay() m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); + m_prices.reset(new PriceDataProvider(appConfig.value(CURRENCY_COUNTRY).toString())); // Update expected chain-height every 5 minutes QTimer *timer = new QTimer(this); @@ -1009,6 +1011,32 @@ QString FloweePay::nameOfUnit(FloweePay::UnitOfBitcoin unit) const } } +void FloweePay::setCountry(const QString &countrycode) +{ + m_prices->setCountry(countrycode); + QSettings appConfig; + appConfig.setValue(CURRENCY_COUNTRY, countrycode); + auto list = recentCountries(); + if (!list.isEmpty() && list.first() == countrycode) + return; + list.removeAll(countrycode); // avoid duplicates + list.insert(0, countrycode); + if (list.size() > 5) + list.resize(5); + appConfig.setValue(CURRENCY_COUNTRIES, list); +} + +QStringList FloweePay::recentCountries() const +{ + QSettings appConfig; + auto list = appConfig.value(CURRENCY_COUNTRIES).toStringList(); + if (list.isEmpty()) { + QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); + list = defaultConfig.value(CURRENCY_COUNTRIES).toStringList(); + } + return list; +} + int FloweePay::unitAllowedDecimals() const { switch (m_unit) { diff --git a/src/FloweePay.h b/src/FloweePay.h index 3c1bf45..24b234d 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -157,6 +157,12 @@ public: /// return a string version of the \a unit name. tBCH for instance. Q_INVOKABLE QString nameOfUnit(FloweePay::UnitOfBitcoin unit) const; + /** + * Change the currency based on the country code (nl_NL / en_US) + */ + Q_INVOKABLE void setCountry(const QString &countrycode); + Q_INVOKABLE QStringList recentCountries() const; + /// returns the unit of our prices. BCH, for instance. QString unitName() const; /** diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 919c368..a4839ce 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -32,9 +32,12 @@ static const char *CoinGeckoJSONRoot = "bitcoin-cash"; static constexpr int ReloadTimeout = 7 * 60 * 1000; -PriceDataProvider::PriceDataProvider(QObject *parent) : QObject(parent) +PriceDataProvider::PriceDataProvider(const QString &countryCode, QObject *parent) : QObject(parent) { - setCurrency(QLocale::system()); + if (countryCode.isEmpty()) + setCurrency(QLocale::system()); + else + setCountry(countryCode); QObject::connect(&m_timer, SIGNAL(timeout()), this, SLOT(fetch())); } @@ -86,7 +89,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) } } -void PriceDataProvider::setCurrency(const QString &countrycode) +void PriceDataProvider::setCountry(const QString &countrycode) { setCurrency(QLocale(countrycode)); } diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index e21874f..a516a8c 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -33,7 +33,7 @@ class PriceDataProvider : public QObject Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost NOTIFY currencySymbolChanged) public: - explicit PriceDataProvider(QObject *parent = nullptr); + explicit PriceDataProvider(const QString &countryCode = QString(), QObject *parent = nullptr); void start(); void mock(int price); @@ -47,7 +47,7 @@ public: return m_currentPrice.price; } void setCurrency(const QLocale &countryLocale); - Q_INVOKABLE void setCurrency(const QString &countrycode); + void setCountry(const QString &countrycode); /** * Return a formatted string with the locale-defined price of the amount of \a sats. -- 2.54.0 From 0273982ea0ec37fb1c35095802cccbd1b1f0fec5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Feb 2023 21:00:44 +0100 Subject: [PATCH 0271/1428] Add corrency selector in the pay screen. --- guis/mobile.qrc | 1 + guis/mobile/CurrencySelector.qml | 102 +++++++++++++++++++++++++++++++ guis/mobile/PayWithQR.qml | 59 +++++++++++++++--- 3 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 guis/mobile/CurrencySelector.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 701b708..6ee15ad 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -45,5 +45,6 @@ mobile/TransactionDetails.qml mobile/TxInfoSmall.qml mobile/AccountSelector.qml + mobile/CurrencySelector.qml diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml new file mode 100644 index 0000000..a3964e7 --- /dev/null +++ b/guis/mobile/CurrencySelector.qml @@ -0,0 +1,102 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +// import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay; + +Page { + id: root + ListView { + anchors.fill: parent + model: [ + "nl_NL", + "en_US", + "ar_AE", // "aed" United Arab Emirates dirham + "es_AR", // "ars" Argentine peso + "en_AU", // "aud" Australian dollar + "bn_BD", // "bdt" Bangladeshi taka + "ar_BH", // "bhd" Bahraini dinar + "en-BM", // "bmd" Bermudian dollar + "pt_BR", // "brl" Brazilian real + "en_CA", // "cad" Canada + "de_CH", // "chf" Switzerland + "es_CL", // "clp" Chilean peso + "zh", // "cny" + "cs", // "czk" + "da", // "dkk" + "en_GG", // "gbp" + "zh_HK", // "hkd" + "hu", // "huf" + "id", // "idr" + "he", // "ils" + "hi", // "inr" + "ja", // "jpy" + "ko", // "krw" + "ar_KW", // "kwd" + "si", // "lkr" + "my", // "mmk" + "es_MX", // "mxn" + "ms", // "myr" + "cch", // "ngn" + "no_NO", // "nok" + "en_PN", // "nzd" + "fil", // "php" + "pl_PL", // pln + "ur", // "pkr" + "ru", // "rub" + "ar_SA", // "sar" + "sv", // "sek" + "zh_SG", // "sgd" + "th", // "thb" + "tr", // "try" + "zh_TW", // "twd" + "uk", // "uah" + "es_VE", // "vef" + "vi", // "vnd" + "en_LS", // "zar" + ] + + delegate: Rectangle { + width: ListView.view.width + height: label.height + 10 + color: (index % 2) == 0 ? root.palette.base : root.palette.alternateBase + + Flowee.Label { + id: label + y: 5 + width: parent.width - 10 + text: { + var loc = Qt.locale(modelData); + return loc.currencySymbol(Locale.XCurrencyIsoCode) + "] " + + loc.currencySymbol(Locale.CurrencySymbol) + " " + + loc.currencySymbol(Locale.CurrencyDisplayName); + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + Pay.setCountry(modelData); + thePile.pop(); + } + } + } + + } +} diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 92abe53..19ac4df 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -141,7 +141,8 @@ Page { id: inputs x: 10 y: 7.5 - spacing: 16 + height: parent.height + spacing: 4 Image { id: logo source: "qrc:/bch.svg" @@ -154,6 +155,7 @@ Page { onClicked: priceBch.forceActiveFocus(); } } + Item { width: 10; height: 1 } // spacer Flowee.Label { text: Fiat.currencySymbolPost + Fiat.currencySymbolPrefix @@ -167,19 +169,56 @@ Page { onClicked: priceFiat.forceActiveFocus(); } } + Rectangle { + width: 1 + y: inputs.y * -1 + height: parent.height// -h inputs.y + color: root.palette.text + } - /* TODO Flowee.HamburgerMenu { - wide: true y: 6 MouseArea { anchors.fill: parent - // onClicked: - // TODO show small menu with last 5 chosen fiat currencies - // and a 'more' item which leads to a separate page for all - // possible currencies. + anchors.margins: -12 + anchors.rightMargin: -25 + onClicked: languageMenu.open() } - }*/ + + Loader { + id: languageMenu + function open() { + sourceComponent = languageMenuComponent + } + onLoaded: item.open(); + + } + + Component { + id: languageMenuComponent + + QQC2.Menu { + Repeater { + model: Pay.recentCountries() + QQC2.MenuItem { + text: { + var loc = Qt.locale(modelData); + return loc.currencySymbol(Locale.CurrencySymbol) + " " + + loc.currencySymbol(Locale.CurrencyDisplayName); + } + onClicked: Pay.setCountry(modelData); + } + } + QQC2.MenuItem { + text: qsTr("All Languages") + onClicked: thePile.push("./CurrencySelector.qml") + } + + onVisibleChanged: if (!visible) languageMenu.sourceComponent = undefined; // unload us + } + } + } + Item { width: 5; height: 1 } // spacer } Rectangle { @@ -187,8 +226,8 @@ Page { opacity: 0.3 radius: 6 width: 35 - height: parent.height - 10 - y: 5 + height: parent.height - 4 + y: 2 x: root.fiatFollowsSats ? 5 : 45 Behavior on x { NumberAnimation { } } -- 2.54.0 From 0f4a5d80cf13a7169e6015e7b9450aedef99d0e0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 09:28:48 +0100 Subject: [PATCH 0272/1428] Its 2023 now Whee! --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 4d98477..142db6a 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -49,7 +49,7 @@ Page { } TextButton { text: qsTr("Credits") - subtext: qsTr("© 2020-2022 Tom Zander and contributors") + subtext: qsTr("© 2020-2023 Tom Zander and contributors") showPageIcon: true onClicked: thePile.push(creditsPage) -- 2.54.0 From 2aefc200236f85b55f9a988db2092351832d2cae Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 10:05:39 +0100 Subject: [PATCH 0273/1428] Use === instead of ==, following linter suggestion. --- guis/desktop/AccountConfigMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 27d4e99..7e5d944 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -58,7 +58,7 @@ ConfigItem { onAboutToOpen: { var items = []; var onMainView = (accountOverlay.state === "showTransactions") - if (onMainView || accountOverlay.state == "accountDetails") + if (onMainView || accountOverlay.state === "accountDetails") items.push(detailsAction); var encrypted = root.account.needsPinToOpen; var decrypted = root.account.isDecrypted; -- 2.54.0 From 738009522c10e423deb07bb33852d156e4ab9a28 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 10:06:18 +0100 Subject: [PATCH 0274/1428] New Qt. --- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 88c3175..b2f252e 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.4.1 +ARG QtVersion=v6.4.2 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 46fc57d..f803748 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.4.1 +QtVersion=v6.4.2 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion -- 2.54.0 From 7b1333862c4603542ba8dbce4bbe0812697e3eb6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 10:23:28 +0100 Subject: [PATCH 0275/1428] Make the non-detailed payment API use 'max'. The Payment::paymentAmount() method is for the usecase where the GUI doesn't care about the input/output details. Its unused in the desktop GUI. This method now takes into account the concept of 'max', returning the full wallet amount if its set. --- src/Payment.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 087b924..c768348 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,13 +71,32 @@ void Payment::setPaymentAmount(double amount) double Payment::paymentAmount() { int64_t sats = 0; + bool useMax = false; + int64_t inputs = 0; for (auto d : m_paymentDetails) { - if (d->isOutput()) { + if (!useMax && d->isOutput()) { auto o = d->toOutput(); - if (!(o->maxAllowed() && o->maxSelected())) + if (o->maxAllowed() && o->maxSelected()) + useMax = true; + else sats += o->paymentAmount(); } + else if (d->isInputs()) { + auto in = d->toInputs(); + inputs += in->selectedValue(); + } } + if (useMax) { + // we either use the full wallet amount, or we use explicitly what is provided in the inputs. + if (inputs == 0) { + assert(m_account); + sats = m_account->balanceConfirmed() + m_account->balanceUnconfirmed(); + } + else { + sats = inputs; + } + } + return static_cast(sats); } -- 2.54.0 From 939deb21cf19334e0e3792c3cf5962d59f7f1cb9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 14:08:10 +0100 Subject: [PATCH 0276/1428] Layout improvements. --- guis/mobile/CurrencySelector.qml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index a3964e7..54b043f 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -74,17 +74,24 @@ Page { delegate: Rectangle { width: ListView.view.width - height: label.height + 10 + height: label.height + 20 color: (index % 2) == 0 ? root.palette.base : root.palette.alternateBase + Flowee.Label { + id: iso + y: 10 + text: Qt.locale(modelData).currencySymbol(Locale.XCurrencyIsoCode) + } + Flowee.Label { id: label - y: 5 - width: parent.width - 10 + y: 10 + anchors.left: iso.right + anchors.leftMargin: 10 + anchors.right: parent.right text: { var loc = Qt.locale(modelData); - return loc.currencySymbol(Locale.XCurrencyIsoCode) + "] " - + loc.currencySymbol(Locale.CurrencySymbol) + " " + return "(" + loc.currencySymbol(Locale.CurrencySymbol) + ") " + loc.currencySymbol(Locale.CurrencyDisplayName); } } -- 2.54.0 From 49499ed44978ebbfa5640abf9013d813ed2ab5d0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 14:11:26 +0100 Subject: [PATCH 0277/1428] Add option for menu to generic Page object We use "Page" as anything that can be stacked on top of the rest, now this can have a simple list of menu actions set which will make the header get a hamburger-menu button to show those actions in the menu. --- guis/mobile/Page.qml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 528cec2..79a721e 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -31,12 +31,28 @@ QQC2.Control { property alias headerButtonVisible: headerButton.visible property alias headerButtonText: headerButton.text property alias headerButtonEnabled: headerButton.enabled + /** + * A list of action objects to populate the menu with + * Setting this will make a hamburger button show up in the header + */ + property var menuItems: [ ] signal headerButtonClicked function takeFocus() { focusScope.forceActiveFocus(); } + onMenuItemsChanged: { + // remove old ones first + while (headerMenu.count > 0) { + headerMenu.takeItem(0); + } + // set new ones + for (let i = 0; i < menuItems.length; ++i) { + headerMenu.addAction(menuItems[i]); + } + } + Rectangle { id: header width: parent.width @@ -74,6 +90,20 @@ QQC2.Control { anchors.verticalCenter: parent.verticalCenter onClicked: root.headerButtonClicked() } + Flowee.HamburgerMenu { + visible: root.menuItems.length > 0 + anchors.right: parent.right + anchors.rightMargin: 15 + anchors.verticalCenter: parent.verticalCenter + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: headerMenu.open(); + } + QQC2.Menu { + id: headerMenu + } + } } Rectangle { -- 2.54.0 From a475a993710b3aaa6714ea24d0dfb37b4e45aa8b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 14:14:06 +0100 Subject: [PATCH 0278/1428] Add feature: autoprepare For the payment object it now is possible to insta-commit to changes and as such always prepare a transaction ready to be sent with the latest of the settings. This is enabled with a boolean property 'autoPrepare' (default off). --- src/Payment.cpp | 33 ++++++++++++++++++++++++++++++++- src/Payment.h | 9 ++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index c768348..e17ba18 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -47,6 +47,7 @@ void Payment::setFeePerByte(int sats) return; m_fee = sats; emit feePerByteChanged(); + doAutoPrepare(); } int Payment::feePerByte() @@ -66,6 +67,7 @@ PaymentDetailOutput *Payment::soleOut() const void Payment::setPaymentAmount(double amount) { soleOut()->setPaymentAmount(amount); + doAutoPrepare(); } double Payment::paymentAmount() @@ -133,6 +135,7 @@ void Payment::setTargetAddress(const QString &address_) out->setAddress(address); emit targetAddressChanged(); + doAutoPrepare(); } QString Payment::targetAddress() @@ -170,6 +173,7 @@ void Payment::decrypt(const QString &password) m_error = tr("Invalid PIN"); emit errorChanged(); } + doAutoPrepare(); } bool Payment::validate() @@ -426,6 +430,20 @@ void Payment::addDetail(PaymentDetail *detail) assert(!m_paymentDetails.isEmpty()); emit paymentDetailsChanged(); emit validChanged(); // pretty sure we are invalid after ;-) + doAutoPrepare(); +} + +bool Payment::autoPrepare() const +{ + return m_autoPrepare; +} + +void Payment::setAutoPrepare(bool newAutoPrepare) +{ + if (m_autoPrepare == newAutoPrepare) + return; + m_autoPrepare = newAutoPrepare; + emit autoPrepareChanged(); } const QString &Payment::userComment() const @@ -464,6 +482,7 @@ void Payment::setCurrentAccount(AccountInfo *account) for (auto detail : m_paymentDetails) { detail->setWallet(account->wallet()); } + doAutoPrepare(); } int Payment::fiatPrice() const @@ -477,12 +496,13 @@ void Payment::setFiatPrice(int pricePerCoin) return; m_fiatPrice = pricePerCoin; emit fiatPriceChanged(); + doAutoPrepare(); } QList Payment::paymentDetails() const { QList pds; - for (auto *detail : m_paymentDetails) { + for (auto *const detail : m_paymentDetails) { pds.append(detail); } return pds; @@ -566,6 +586,7 @@ void Payment::remove(PaymentDetail *detail) } } detail->deleteLater(); + doAutoPrepare(); } QString Payment::txid() const @@ -632,6 +653,15 @@ void Payment::recalcAmounts() emit validChanged(); } +void Payment::doAutoPrepare() +{ + if (!m_autoPrepare) + return; + try { + prepare(); + } catch(...) {} +} + bool Payment::preferSchnorr() const { return m_preferSchnorr; @@ -644,6 +674,7 @@ void Payment::setPreferSchnorr(bool preferSchnorr) m_preferSchnorr = preferSchnorr; emit preferSchnorrChanged(); + doAutoPrepare(); } bool Payment::txPrepared() const diff --git a/src/Payment.h b/src/Payment.h index 3530c38..8775da4 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,6 +50,7 @@ class Payment : public QObject Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY userCommentChanged) Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged); + Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared @@ -193,6 +194,9 @@ public: const QString &userComment() const; void setUserComment(const QString &comment); + bool autoPrepare() const; + void setAutoPrepare(bool newAutoPrepare); + private slots: void sentToPeer(); void txRejected(short reason, const QString &message); @@ -213,8 +217,10 @@ signals: void currentAccountChanged(); void userCommentChanged(); void walletPinChanged(); + void autoPrepareChanged(); private: + void doAutoPrepare(); friend class PaymentDetailOutput; /// Helper method to get the output, assuming that is the only detail. @@ -226,6 +232,7 @@ private: // Payment Variable initialization in reset() please QList m_paymentDetails; + bool m_autoPrepare = false; bool m_txPrepared; bool m_txBroadcastStarted; bool m_preferSchnorr; -- 2.54.0 From a2969750c98d3018e56fb5c03501426851ca4e26 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 14:29:02 +0100 Subject: [PATCH 0279/1428] Fully switch currency on failure. Also add some logging and add an emit on the new currency being set because sometimes the fetch from server takes very long. --- src/PriceDataProvider.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index a4839ce..53eccd3 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -87,6 +87,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) if (m_timer.isActive()) // implies we are start()-ed start(); } + emit priceChanged(m_currentPrice.price); } void PriceDataProvider::setCountry(const QString &countrycode) @@ -182,16 +183,20 @@ void PriceDataProvider::fetch() { QString url(CoinGeckoURL); QNetworkRequest req(QUrl(url.arg(m_currency.toLower()))); + logInfo() << "fetch" << m_currency; m_reply = m_network.get(req); connect(m_reply, SIGNAL(finished()), this, SLOT(finishedDownload())); } void PriceDataProvider::finishedDownload() { + logInfo() << "finishDownload"; if (m_reply == nullptr) return; const auto data = m_reply->readAll(); const bool failed = m_reply->error() != QNetworkReply::NoError || data.isEmpty(); + if (failed) + logCritical() << " failed"; m_reply->deleteLater(); m_reply = nullptr; if (failed) { @@ -220,12 +225,8 @@ void PriceDataProvider::finishedDownload() auto section = root.value(CoinGeckoJSONRoot).toObject(); auto price = section.value(m_currency.toLower()); if (price.isUndefined()) { // our provider does not support this coin. - if (m_currency != "USD") { - m_currency = "USD"; - m_currencySymbolPost.clear(); - m_currencySymbolPrefix = "$"; - fetch(); - } + logCritical() << " provider does not support this coin" << m_currency; + setCountry("en_US"); return; } m_currentPrice.price = price.toDouble() * 100; @@ -238,7 +239,7 @@ void PriceDataProvider::finishedDownload() m_timer.start(20 * 1000); return; } - logInfo() << "Current fiat price: " << m_currencySymbolPrefix << m_currentPrice.price << m_currencySymbolPost; + logCritical() << "Current fiat price: " << m_currencySymbolPrefix << m_currentPrice.price << m_currencySymbolPost; m_timer.start(ReloadTimeout); } -- 2.54.0 From 028c4967127953c7d61bc939155c55f5d7a07348 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 14:30:14 +0100 Subject: [PATCH 0280/1428] Various bugfixes and tweaks. Also remove the testing setup again. --- guis/mobile/PayWithQR.qml | 70 ++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 19ac4df..886ad6a 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -25,12 +25,25 @@ Page { id: root headerText: qsTr("Approve Payment") + property QtObject sendAllAction: QQC2.Action { + checkable: true + checked: payment.details[0].maxSelected + text: qsTr("Send All", "all money in wallet") + onTriggered: { + payment.details[0].maxSelected = checked + if (payment.isValid) + payment.prepare(); // auto-prepare doesn't act on changes done on the details. + } + } + menuItems: [ + sendAllAction + ] Item { // data QRScanner { id: scanner scanType: QRScanner.PaymentDetails - // autostart: true + autostart: true onFinished: { var rc = scanResult if (rc === "") { // scanning failed @@ -38,8 +51,6 @@ Page { } else { payment.targetAddress = rc - if (payment.isValid) - payment.prepare(); priceBch.forceActiveFocus(); } } @@ -48,12 +59,12 @@ Page { id: payment account: portfolio.current fiatPrice: Fiat.price - onIsValidChanged: if (isValid) prepare() + autoPrepare: true // easier testing values (for when you don't have a camera) - paymentAmount: 100000000 - targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" - userComment: "bla bla bla" + // paymentAmount: 100000000 + // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" + // userComment: "bla bla bla" } AccountSelector { @@ -130,6 +141,7 @@ Page { id: inputChooser anchors.horizontalCenter: parent.horizontalCenter anchors.top: userComment.bottom + anchors.topMargin: 10 border.width: 1 border.color: root.palette.highlight color: root.palette.base @@ -155,13 +167,13 @@ Page { onClicked: priceBch.forceActiveFocus(); } } - Item { width: 10; height: 1 } // spacer + Item { width: 8; height: 1 } // spacer Flowee.Label { - text: Fiat.currencySymbolPost + Fiat.currencySymbolPrefix + text: (Fiat.currencySymbolPost + Fiat.currencySymbolPrefix).trim() width: 24 font.pixelSize: 32 - horizontalAlignment: Text.horizontalCenter + horizontalAlignment: Text.AlignRight anchors.baseline: logo.bottom MouseArea { @@ -172,7 +184,7 @@ Page { Rectangle { width: 1 y: inputs.y * -1 - height: parent.height// -h inputs.y + height: parent.height color: root.palette.text } @@ -210,7 +222,7 @@ Page { } } QQC2.MenuItem { - text: qsTr("All Languages") + text: qsTr("All Currencies") onClicked: thePile.push("./CurrencySelector.qml") } @@ -234,8 +246,6 @@ Page { } } - - Flowee.Label { id: errorLabel text: payment.error @@ -247,9 +257,10 @@ Page { width: parent.width } + Rectangle { id: walletNameBackground - anchors.top: currentWalletLabel.top + anchors.top: currentWalletValue.visible ? currentWalletValue.top : currentWalletLabel.top anchors.topMargin: -5 width: parent.width anchors.bottom: currentWalletValue.bottom @@ -260,25 +271,38 @@ Page { anchors.fill: parent onClicked: (mouse) => { if (mouse.x < parent.width / 2) { - accountSelector.open(); + if (portfolio.accounts.length > 1) + accountSelector.open(); } else { - // TODO open popup on price. - // this should include a 'max-price' option like this; - // payment.details[0].maxSelected = checked + while (priceMenu.count > 0) { + priceMenu.takeItem(0); + } + priceMenu.addAction(sendAllAction); + priceMenu.x = root.width / 2 + priceMenu.y = -40 + priceMenu.open(); } } } + QQC2.Menu { + id: priceMenu + } + } + + Flowee.HamburgerMenu { + anchors.verticalCenter: currentWalletLabel.verticalCenter + visible: portfolio.accounts.length > 1 } Flowee.Label { id: currentWalletLabel text: payment.account.name visible: payment.account.isUserOwned - anchors.baseline: parent.width > currentWalletLabel.width + currentWalletValue.width - ? currentWalletValue.baseline : undefined - anchors.bottom: parent.width > currentWalletLabel.width + currentWalletValue.width - ? undefined : currentWalletValue.top + x: 10 + width: parent.width - 10 + anchors.bottom: currentWalletValue.top } + Flowee.BitcoinAmountLabel { id: currentWalletValue anchors.right: parent.right -- 2.54.0 From 40b0b6e3b8f3f3a88c4f1500a22d48f71a034de5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 15:03:24 +0100 Subject: [PATCH 0281/1428] Fix spacing of currency field. --- guis/Flowee/FiatValueField.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index e56c3a5..0762f06 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,16 +32,16 @@ MoneyValueField { RowLayout { id: row - spacing: 4 height: parent.height + spacing: 0 property string amountString: Fiat.priceToStringSimple(root.value) Label { text: Fiat.currencySymbolPrefix font.pixelSize: fiat.fontPixelSize color: fiat.color + visible: text != "" } - LabelWithCursor { id: fiat fullString: row.amountString @@ -53,6 +53,7 @@ MoneyValueField { text: Fiat.currencySymbolPost font.pixelSize: fiat.fontPixelSize color: fiat.color + visible: text != "" } } } -- 2.54.0 From 9b87590bd9e0c316aba3c69f65fe7eba577456a3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 15:06:26 +0100 Subject: [PATCH 0282/1428] Fixes in currency number construction Now tested with more currencies. --- src/PriceDataProvider.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 53eccd3..d1ee33f 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -68,11 +68,13 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) || m_currency == QLatin1String("KHR") || m_currency == QLatin1String("KZT") || m_currency == QLatin1String("PLN") + || m_currency == QLatin1String("NOK") || m_currency == QLatin1String("PYG") || m_currency == QLatin1String("RUB") || m_currency == QLatin1String("VND")) { - // these currencies format the symbol after the numbers part. - std::swap(m_currencySymbolPost, m_currencySymbolPrefix); + // these currencies format the name after the numbers part. + m_currencySymbolPost = QString(" ") + m_currencySymbolPrefix; + m_currencySymbolPrefix.clear(); } // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") @@ -137,20 +139,20 @@ QString PriceDataProvider::formattedPrice(int fiatValue) const buf[i + add] = actualPrice.at(i).unicode(); } dummy = QString::fromLatin1(buf); - actualPrice = dummy.left(dummy.size()); + actualPrice = dummy.left(dummy.size() - (m_displayCents ? 0 : 2)); } if (m_displayCents) { - return (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) - % m_currencySymbolPrefix + return m_currencySymbolPrefix + % (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) % actualPrice % QLocale::system().decimalPoint() % centsPrice.right(2) % m_currencySymbolPost; } else { - return (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) - % m_currencySymbolPrefix + return m_currencySymbolPrefix + % (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) % actualPrice % m_currencySymbolPost; } -- 2.54.0 From f1f0f113d7ee255889ea7f3205caf157cef16a8f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 17:57:29 +0100 Subject: [PATCH 0283/1428] Remove some of the camera work arounds Since we have seen a lot of bugfixes in the multimedia library, we can make this all faster and simpler. --- src/CameraController.cpp | 37 ++++++++++++++----------------------- src/CameraController.h | 1 - 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 5946037..b5a6e05 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -193,8 +193,8 @@ void CameraControllerPrivate::checkState() emit q->visibleChanged(); emit q->loadCameraChanged(); - // then wait 300ms before turning on the actual camera - QTimer::singleShot(300, q, SLOT(checkState())); + // then wait an event before turning on the actual camera + QTimer::singleShot(30, q, SLOT(checkState())); return; } if (camera && videoSink && !cameraStarted && scanRequest.get()) { @@ -207,33 +207,24 @@ void CameraControllerPrivate::checkState() if (cam->error() != QCamera::NoError) logFatal() << "CameraController found cam error:" << cam->errorString(); -#ifndef TARGET_OS_Linux - cam->stop(); // workaround for why some phones don't scan the first time. -#endif cam->setCameraFormat(preferredFormat); cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash - QTimer::singleShot(300, q, SLOT(checkState2())); + cameraStarted = true; + QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { + currentFrame = frame; + + if (!m_scanningThread) { + m_scanningThread = new QRScanningThread(this); + QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); + m_scanningThread->start(); + } + }); + logDebug() << "Camera active is now true"; + emit q->cameraActiveChanged(); // this emit makes QML activate the camera } } -void CameraController::checkState2() -{ - d->cameraStarted = true; - auto sink = qobject_cast(d->videoSink); - QObject::connect(sink, &QVideoSink::videoFrameChanged, this, [=](const QVideoFrame &frame) { - d->currentFrame = frame; - - if (!d->m_scanningThread) { - d->m_scanningThread = new QRScanningThread(d); - QObject::connect (d->m_scanningThread, SIGNAL(finished()), this, SLOT(qrScanFinished()), Qt::QueuedConnection); - d->m_scanningThread->start(); - } - }); - logDebug() << "Camera active is now true"; - emit cameraActiveChanged(); // this emit makes QML activate the camera -} - void CameraController::initCamera() { d->initCamera(); diff --git a/src/CameraController.h b/src/CameraController.h index 6e39027..e09d0d4 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -79,7 +79,6 @@ private slots: void initialize(); void qrScanFinished(); void checkState(); - void checkState2(); void initCamera(); private: -- 2.54.0 From 9ba23d20e7bdc4beb35fd209ec30e206a0d29883 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 20:01:24 +0100 Subject: [PATCH 0284/1428] Actually add a backspace image. --- guis/mobile.qrc | 2 ++ guis/mobile/PayWithQR.qml | 11 +++++++++-- guis/mobile/images/backspace-light.svg | 7 +++++++ guis/mobile/images/backspace.svg | 7 +++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 guis/mobile/images/backspace-light.svg create mode 100644 guis/mobile/images/backspace.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6ee15ad..6283791 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -17,6 +17,8 @@ mobile/images/receive.svg mobile/images/qr-code.svg mobile/images/qr-code-light.svg + mobile/images/backspace.svg + mobile/images/backspace-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 886ad6a..69760ec 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -336,8 +336,7 @@ Page { return Qt.locale().decimalPoint if (index === 10) return "0" - // if (index === 11) - return "<-" // TODO use a backspace icon instead. + return ""; // index === 11, the backspace. } // make dim when not enabled. color: { @@ -346,6 +345,14 @@ Page { return palette.windowText; } } + Image { + visible: index === 11 + anchors.centerIn: parent + source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" + width: 27 + height: 17 + opacity: enabled ? 1 : 0.4 + } MouseArea { anchors.fill: parent diff --git a/guis/mobile/images/backspace-light.svg b/guis/mobile/images/backspace-light.svg new file mode 100644 index 0000000..5faf02b --- /dev/null +++ b/guis/mobile/images/backspace-light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/guis/mobile/images/backspace.svg b/guis/mobile/images/backspace.svg new file mode 100644 index 0000000..39aee3d --- /dev/null +++ b/guis/mobile/images/backspace.svg @@ -0,0 +1,7 @@ + + + + + + + -- 2.54.0 From 9f39a9f54fd21998665e90b5814e4aca1d9edcef Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 20:55:24 +0100 Subject: [PATCH 0285/1428] Make the logic simpler Move the logic for fiat to the payment object too and simplify the need for code by defining this can only be called in the single-output mode. --- guis/mobile/PayWithQR.qml | 6 +++--- src/Payment.cpp | 40 ++++++++++++--------------------------- src/Payment.h | 6 +++++- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 69760ec..cf486d7 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -109,7 +109,7 @@ Page { Flowee.FiatValueField { id: priceFiat - value: Fiat.priceFor(priceBch.value, payment.fiatPrice); + value: payment.paymentAmountFiat y: root.fiatFollowsSats ? 68 : 5 focus: true fontPixelSize: size @@ -118,7 +118,7 @@ Page { Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } Flowee.ObjectShaker { id: fiatShaker } - onValueEdited: payment.details[0].fiatAmount = value + onValueEdited: payment.paymentAmountFiat = value } Flowee.Label { @@ -260,7 +260,7 @@ Page { Rectangle { id: walletNameBackground - anchors.top: currentWalletValue.visible ? currentWalletValue.top : currentWalletLabel.top + anchors.top: currentWalletLabel.visible ? currentWalletLabel.top : currentWalletValue.top anchors.topMargin: -5 width: parent.width anchors.bottom: currentWalletValue.bottom diff --git a/src/Payment.cpp b/src/Payment.cpp index e17ba18..8dfbc27 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -70,36 +70,20 @@ void Payment::setPaymentAmount(double amount) doAutoPrepare(); } -double Payment::paymentAmount() +double Payment::paymentAmount() const { - int64_t sats = 0; - bool useMax = false; - int64_t inputs = 0; - for (auto d : m_paymentDetails) { - if (!useMax && d->isOutput()) { - auto o = d->toOutput(); - if (o->maxAllowed() && o->maxSelected()) - useMax = true; - else - sats += o->paymentAmount(); - } - else if (d->isInputs()) { - auto in = d->toInputs(); - inputs += in->selectedValue(); - } - } - if (useMax) { - // we either use the full wallet amount, or we use explicitly what is provided in the inputs. - if (inputs == 0) { - assert(m_account); - sats = m_account->balanceConfirmed() + m_account->balanceUnconfirmed(); - } - else { - sats = inputs; - } - } + return soleOut()->paymentAmount(); +} - return static_cast(sats); +void Payment::setPaymentAmountFiat(double amount) +{ + soleOut()->setFiatAmount(amount); + doAutoPrepare(); +} + +double Payment::paymentAmountFiat() const +{ + return soleOut()->fiatAmount(); } void Payment::setTargetAddress(const QString &address_) diff --git a/src/Payment.h b/src/Payment.h index 8775da4..a9fce61 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -37,6 +37,7 @@ class Payment : public QObject Q_OBJECT Q_PROPERTY(int feePerByte READ feePerByte WRITE setFeePerByte NOTIFY feePerByteChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) + Q_PROPERTY(double paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTargetAddress READ formattedTargetAddress NOTIFY targetAddressChanged) @@ -114,7 +115,10 @@ public: /** * Returns the total amount of satoshis that are selected by outputs. */ - double paymentAmount(); + double paymentAmount() const; + + void setPaymentAmountFiat(double amount); + double paymentAmountFiat() const; /** * Sets the address to pay to. -- 2.54.0 From cf76158e53cae52520b9e4428c141c12336d2341 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 8 Feb 2023 21:11:20 +0100 Subject: [PATCH 0286/1428] Last minute tweaks Make the error not overlap when using larger text. --- guis/mobile/PayWithQR.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index cf486d7..6e426c8 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -134,7 +134,6 @@ Page { color: palette.highlight font.italic: true anchors.top: commentLabel.bottom - anchors.topMargin: 5 } Rectangle { @@ -253,7 +252,6 @@ Page { color: Pay.useDarkSkin ? "#cc5454" : "#6a0c0c" anchors.bottom: walletNameBackground.top wrapMode: Text.Wrap - anchors.bottomMargin: 10 width: parent.width } -- 2.54.0 From f1f8a87fc66734b19ba70bea1627f9ab9144133c Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 11:37:26 +0100 Subject: [PATCH 0287/1428] Move line back to the desktop usage this got moved to the shared component, but it only works in the single usage of the desktop. Move it back there. --- guis/Flowee/WalletSecretsView.qml | 1 - guis/desktop/AccountDetails.qml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 6ad4c52..132f160 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -22,7 +22,6 @@ ListView { id: root required property QtObject account; - implicitHeight: root.account.isSingleAddressAccount ? contentHeight : scrollablePage.height / 10 * 7 clip: true model: root.account.secrets delegate: Rectangle { diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 43667c1..46a4f40 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -220,6 +220,7 @@ Item { visible: size < 0.9 } account: root.account + implicitHeight: root.account.isSingleAddressAccount ? contentHeight : scrollablePage.height / 10 * 7 } } Flowee.GroupBox { -- 2.54.0 From 76be7f2473d19bd666af6db09cabc93d9b97e37c Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 11:37:57 +0100 Subject: [PATCH 0288/1428] Add showing of the keys/addresses for any HD wallet. --- guis/mobile/AccountPageListItem.qml | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 5706474..23cdfe8 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -145,8 +145,17 @@ QQC2.Control { Page { headerText: qsTr("Backup Details") Flowee.CheckBox { - id: usedAddresses + id: changeAddresses + text: qsTr("Change Addresses") anchors.top: parent.top + visible: root.account.isHDWallet + onClicked: root.account.secrets.showChangeChain = checked + tooltipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") + } + Flowee.CheckBox { + id: usedAddresses + anchors.top: changeAddresses.bottom + anchors.topMargin: 16 width: parent.width text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount @@ -160,17 +169,18 @@ QQC2.Control { anchors.bottom: parent.bottom width: parent.width account: root.account - /* - ScrollBar: { - id: thumb - minimumSize: 20 / activityView.height - visible: size < 0.9 - }*/ clip: true } } } } + TextButton { + text: qsTr("Addresses and Keys") + visible: root.account.isHDWallet + showPageIcon: true + Layout.fillWidth: true + onClicked: thePile.push(backupDetails); + } } -- 2.54.0 From 29a9a7d2ead30b8db9bed7bbf9536737f6a42931 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 12:29:38 +0100 Subject: [PATCH 0289/1428] Add some more wallet details --- guis/mobile/AccountPageListItem.qml | 32 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 23cdfe8..dbd9331 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,16 +30,32 @@ QQC2.Control { ColumnLayout { id: column width: parent.width - Flowee.AccountTypeLabel { Layout.fillWidth: true account: root.account } + + Flowee.Label { + text: qsTr("Sync Status") + ":" + } + Flowee.Label { + text: { + var height = root.account.lastBlockSynched + if (height < 1) + return "" + var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); + if (time !== "") + time = " (" + time + ")"; + return height + " / " + Pay.chainHeight + time; + } + } + Flowee.TextField { text: account.name onTextChanged: root.account.name = text Layout.fillWidth: true } + TextButton { visible: root.account.isUserOwned Layout.fillWidth: true @@ -123,6 +139,10 @@ QQC2.Control { VisualSeparator { } Flowee.Label { text: qsTr("Derivation Path") + ":" } Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } + VisualSeparator { } + Flowee.Label { text: qsTr("Starting Height", "height refers to block-height") + ":" } + Flowee.LabelWithClipboard { text: root.account.initialBlockHeight } + VisualSeparator { } Flowee.Label { Layout.fillWidth: true @@ -143,7 +163,7 @@ QQC2.Control { Component { id: backupDetails Page { - headerText: qsTr("Backup Details") + headerText: qsTr("Wallet keys") Flowee.CheckBox { id: changeAddresses text: qsTr("Change Addresses") @@ -185,12 +205,6 @@ QQC2.Control { /* - Q_PROPERTY(double balanceConfirmed READ balanceConfirmed NOTIFY balanceChanged) - Q_PROPERTY(double balanceUnconfirmed READ balanceUnconfirmed NOTIFY balanceChanged) - Q_PROPERTY(double balanceImmature READ balanceImmature NOTIFY balanceChanged) - - Q_PROPERTY(int unspentOutputCount READ unspentOutputCount NOTIFY utxosChanged) - Q_PROPERTY(int historicalOutputCount READ historicalOutputCount NOTIFY utxosChanged) Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) Q_PROPERTY(int initialBlockHeight READ initialBlockHeight NOTIFY lastBlockSynchedChanged) Q_PROPERTY(QDateTime lastBlockSynchedTime READ lastBlockSynchedTime NOTIFY lastBlockSynchedChanged) -- 2.54.0 From a7551e1ab278d7557b78f2cfb47d85543968c732 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 14:56:39 +0100 Subject: [PATCH 0290/1428] Avoid multiple copies, add shared QRWidget --- guis/Flowee/QRWidget.qml | 72 ++++++++++++++++ guis/desktop/ReceiveTransactionPane.qml | 94 ++++++--------------- guis/mobile/ReceiveTab.qml | 104 +++++++----------------- guis/widgets.qrc | 1 + 4 files changed, 124 insertions(+), 147 deletions(-) create mode 100644 guis/Flowee/QRWidget.qml diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml new file mode 100644 index 0000000..b355f7b --- /dev/null +++ b/guis/Flowee/QRWidget.qml @@ -0,0 +1,72 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import Flowee.org.pay + +Image { + id: root + property QtObject request: null + + width: height + height: { + var h = parent.height - 220; + return Math.min(h, 256) + } + source: root.request == null ? "" : "image://qr/" + qr.request.qr + smooth: false + opacity: root.request == null || qr.request.state === PaymentRequest.Unpaid ? 1: 0 + + MouseArea { + anchors.fill: parent + onClicked: { + Pay.copyToClipboard(root.request.qr) + clipboardFeedback.opacity = 1 + } + } + + Rectangle { + id: clipboardFeedback + opacity: 0 + width: feedbackText.width + 20 + height: feedbackText.height + 20 + radius: 10 + color: Pay.useDarkSkin ? "#333" : "#ddd" + anchors.top: parent.bottom + anchors.topMargin: -13 + anchors.horizontalCenter: parent.horizontalCenter + + Label { + id: feedbackText + x: 10 + y: 10 + text: qsTr("Copied to clipboard") + wrapMode: Text.WordWrap + } + + Behavior on opacity { OpacityAnimator {} } + + /// after 10 seconds, remove feedback. + Timer { + interval: 10000 + running: clipboardFeedback.opacity >= 1 + onTriggered: clipboardFeedback.opacity = 0 + } + } + + Behavior on opacity { OpacityAnimator {} } +} diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index c6230c9..e64b2f8 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -26,20 +26,19 @@ Pane { id: receivePane property QtObject account: portfolio.current - property QtObject request: null onAccountChanged: { if (account == null) return; - if (request == null) - request = account.createPaymentRequest(receivePane) + if (qr.request == null) + qr.request = account.createPaymentRequest(receivePane) else - request.switchAccount(portfolio.current); + qr.request.switchAccount(portfolio.current); } function reset() { - if (request.saveState !== PaymentRequest.Stored) - request.destroy(); - request = account.createPaymentRequest(receivePane) + if (qr.request.saveState !== PaymentRequest.Stored) + qr.request.destroy(); + qr.request = account.createPaymentRequest(receivePane) description.text = ""; bitcoinValueField.reset(); } @@ -52,58 +51,11 @@ Pane { opacity: 0.5 } - Image { - id: qrCode - width: height - height: { - var h = parent.height - 220; - return Math.min(h, 256) - } - source: "image://qr/" + receivePane.request.qr - smooth: false + Flowee.QRWidget { + id: qr anchors.horizontalCenter: parent.horizontalCenter anchors.top: instructions.bottom anchors.topMargin: 20 - opacity: receivePane.request.state === PaymentRequest.Unpaid ? 1: 0 - - MouseArea { - anchors.fill: parent - onClicked: { - Pay.copyToClipboard(receivePane.request.qr) - clipboardFeedback.opacity = 1 - } - } - - Rectangle { - id: clipboardFeedback - opacity: 0 - width: feedbackText.width + 20 - height: feedbackText.height + 20 - radius: 10 - color: Pay.useDarkSkin ? "#333" : "#ddd" - anchors.top: parent.bottom - anchors.topMargin: -13 - anchors.horizontalCenter: parent.horizontalCenter - - Label { - id: feedbackText - x: 10 - y: 10 - text: qsTr("Copied to clipboard") - wrapMode: Text.WordWrap - } - - Behavior on opacity { OpacityAnimator {} } - - /// after 10 seconds, remove feedback. - Timer { - interval: 10000 - running: clipboardFeedback.opacity === 1 - onTriggered: clipboardFeedback.opacity = 0 - } - } - - Behavior on opacity { OpacityAnimator {} } } // the "payment received" screen. @@ -112,13 +64,13 @@ Pane { anchors.topMargin: 20 anchors.left: parent.left anchors.right: parent.right - anchors.bottom: qrCode.bottom + anchors.bottom: qr.bottom radius: 10 gradient: Gradient { GradientStop { position: 0.6 color: { - var state = receivePane.request.state; + var state = qr.request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) return receivePane.palette.base if (state === PaymentRequest.DoubleSpentSeen) @@ -132,7 +84,7 @@ Pane { color: receivePane.palette.base } } - opacity: receivePane.request.state === PaymentRequest.Unpaid ? 0: 1 + opacity: qr.request.state === PaymentRequest.Unpaid ? 0: 1 // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) @@ -141,7 +93,7 @@ Pane { width: 160 height: 160 y: (parent.height - height) / 3 * 2 - visible: receivePane.request.state !== PaymentRequest.DoubleSpentSeen + visible: qr.request.state !== PaymentRequest.DoubleSpentSeen Shape { id: circleShape anchors.fill: parent @@ -160,7 +112,7 @@ Pane { centerY: 80 radiusX: 70; radiusY: 70 startAngle: -80 - sweepAngle: receivePane.request.state === PaymentRequest.Unpaid ? 0: 340 + sweepAngle: qr.request.state === PaymentRequest.Unpaid ? 0: 340 Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } } @@ -186,7 +138,7 @@ Pane { Label { id: feedbackLabel text: { - var s = receivePane.request.state; + var s = qr.request.state; if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received return qsTr("Transaction high risk") @@ -206,7 +158,7 @@ Pane { font.pointSize: 20 } Label { - visible: receivePane.request.state === PaymentRequest.DoubleSpentSeen + visible: qr.request.state === PaymentRequest.DoubleSpentSeen anchors.top: feedbackLabel.bottom anchors.right: parent.right anchors.rightMargin: 10 @@ -224,7 +176,7 @@ Pane { id: grid anchors.left: parent.left anchors.right: parent.right - anchors.top: qrCode.bottom + anchors.top: qr.bottom anchors.topMargin: 30 columns: 2 Label { @@ -233,8 +185,8 @@ Pane { Flowee.TextField { id: description Layout.fillWidth: true - enabled: receivePane.request.state === PaymentRequest.Unpaid - onTextChanged: receivePane.request.message = text + enabled: qr.request.state === PaymentRequest.Unpaid + onTextChanged: qr.request.message = text focus: true } @@ -246,8 +198,8 @@ Pane { spacing: 10 Flowee.BitcoinValueField { id: bitcoinValueField - enabled: receivePane.request.state === PaymentRequest.Unpaid - onValueChanged: receivePane.request.amount = value + enabled: qr.request.state === PaymentRequest.Unpaid + onValueChanged: qr.request.amount = value } Label { Layout.alignment: Qt.AlignBaseline @@ -264,14 +216,14 @@ Pane { } Button { text: qsTr("Remember", "payment request") - visible: receivePane.request.state === PaymentRequest.Unpaid || receivePane.request.state === PaymentRequest.DoubleSpentSeen + visible: qr.request.state === PaymentRequest.Unpaid || qr.request.state === PaymentRequest.DoubleSpentSeen onClicked: { - receivePane.request.stored = true + qr.request.stored = true reset(); } } Button { - text: receivePane.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") + text: qr.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") onClicked: { reset(); } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 0aac18c..2848dc2 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -29,7 +29,6 @@ FocusScope { focus: true property QtObject account: portfolio.current - property QtObject request: null /* TZ: I apologize for the design, I made it when I just had learned QML / cpp mixing. @@ -39,23 +38,23 @@ FocusScope { */ onAccountChanged: { - if (request != null) { - if (request.state === PaymentRequest.Unpaid) - request.switchAccount(portfolio.current); + if (qr.request != null) { + if (qr.request.state === PaymentRequest.Unpaid) + qr.request.switchAccount(portfolio.current); else - request = null; + qr.request = null; } } onActiveFocusChanged: { - if (activeFocus && request == null) { - request = account.createPaymentRequest(receiveTab) + if (activeFocus && qr.request == null) { + qr.request = account.createPaymentRequest(receiveTab) } } function reset() { - if (request.saveState !== PaymentRequest.Stored) - request.destroy(); - request = account.createPaymentRequest(receiveTab) + if (qr.request.saveState !== PaymentRequest.Stored) + qr.request.destroy(); + qr.request = account.createPaymentRequest(receiveTab) description.text = ""; // bitcoinValueField.reset(); } @@ -68,58 +67,11 @@ FocusScope { opacity: 0.5 } - Image { - id: qrCode - width: height - height: { - var h = parent.height - 220; - return Math.min(h, 256) - } - source: receiveTab.request == null ? "" : "image://qr/" + receiveTab.request.qr - smooth: false + Flowee.QRWidget { + id: qr anchors.horizontalCenter: parent.horizontalCenter anchors.top: instructions.bottom anchors.topMargin: 20 - opacity: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? 1: 0 - - MouseArea { - anchors.fill: parent - onClicked: { - Pay.copyToClipboard(receiveTab.request.qr) - clipboardFeedback.opacity = 1 - } - } - - Rectangle { - id: clipboardFeedback - opacity: 0 - width: feedbackText.width + 20 - height: feedbackText.height + 20 - radius: 10 - color: Pay.useDarkSkin ? "#333" : "#ddd" - anchors.top: parent.bottom - anchors.topMargin: -13 - anchors.horizontalCenter: parent.horizontalCenter - - Flowee.Label { - id: feedbackText - x: 10 - y: 10 - text: qsTr("Copied to clipboard") - wrapMode: Text.WordWrap - } - - Behavior on opacity { OpacityAnimator {} } - - /// after 10 seconds, remove feedback. - Timer { - interval: 10000 - running: clipboardFeedback.opacity === 1 - onTriggered: clipboardFeedback.opacity = 0 - } - } - - Behavior on opacity { OpacityAnimator {} } } // the "payment received" screen. @@ -128,17 +80,17 @@ FocusScope { anchors.topMargin: 20 anchors.left: parent.left anchors.right: parent.right - anchors.bottom: qrCode.bottom + anchors.bottom: qr.bottom radius: 10 gradient: Gradient { GradientStop { position: 0.6 color: { - if (receiveTab.request == null) + if (qr.request == null) return "red" - var state = receiveTab.request.state; + var state = qr.request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) - return receiveTab.palette.base + return qr.palette.base if (state === PaymentRequest.DoubleSpentSeen) return "#640e0f" // red return "#3e8b4e" // in all other cases: green @@ -150,7 +102,7 @@ FocusScope { color: receiveTab.palette.base } } - opacity: receiveTab.request == null ? 0 : (receiveTab.request.state === PaymentRequest.Unpaid ? 0: 1) + opacity: qr.request == null ? 0 : (qr.request.state === PaymentRequest.Unpaid ? 0: 1) // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) @@ -159,11 +111,11 @@ FocusScope { width: 160 height: 160 y: (parent.height - height) / 3 * 2 - visible: receiveTab.request == null || receiveTab.request.state !== PaymentRequest.DoubleSpentSeen + visible: qr.request == null || qr.request.state !== PaymentRequest.DoubleSpentSeen Shape { id: circleShape anchors.fill: parent - opacity: progressCircle.sweepAngle === 340 ? 0 : 1 + opacity: progressCircle.sweepAngle <= 340 ? 0 : 1 x: 40 ShapePath { strokeWidth: 20 @@ -178,7 +130,7 @@ FocusScope { centerY: 80 radiusX: 70; radiusY: 70 startAngle: -80 - sweepAngle: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? 0: 340 + sweepAngle: qr.request == null || qr.request.state === PaymentRequest.Unpaid ? 0: 340 Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } } @@ -204,9 +156,9 @@ FocusScope { Flowee.Label { id: feedbackLabel text: { - if (receiveTab.request == null) + if (qr.request == null) return ""; - var s = receiveTab.request.state; + var s = qr.request.state; if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received return qsTr("Transaction high risk") @@ -226,7 +178,7 @@ FocusScope { font.pointSize: 20 } Flowee.Label { - visible: receiveTab.request == null || receiveTab.request.state === PaymentRequest.DoubleSpentSeen + visible: qr.request == null || qr.request.state === PaymentRequest.DoubleSpentSeen anchors.top: feedbackLabel.bottom anchors.right: parent.right anchors.rightMargin: 10 @@ -243,7 +195,7 @@ FocusScope { ColumnLayout { anchors.left: parent.left anchors.right: parent.right - anchors.top: qrCode.bottom + anchors.top: qr.bottom anchors.topMargin: 30 Flowee.Label { text: qsTr("Description") + ":" @@ -251,8 +203,8 @@ FocusScope { Flowee.TextField { id: description Layout.fillWidth: true - enabled: receiveTab.request != null && receiveTab.request.state === PaymentRequest.Unpaid - onTextChanged: receiveTab.request.message = text + enabled: qr.request != null && qr.request.state === PaymentRequest.Unpaid + onTextChanged: qr.request.message = text } /* @@ -264,8 +216,8 @@ FocusScope { spacing: 10 Flowee.BitcoinValueField { id: bitcoinValueField - enabled: receiveTab.request != null && receiveTab.request.state === PaymentRequest.Unpaid - onValueChanged: receiveTab.request.amount = value + enabled: qr.request != null && qr.request.state === PaymentRequest.Unpaid + onValueChanged: qr.request.amount = value } Flowee.Label { Layout.alignment: Qt.AlignBaseline @@ -277,7 +229,7 @@ FocusScope { Flowee.Button { Layout.alignment: Qt.AlignRight - text: receiveTab.request == null || receiveTab.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") + text: qr.request == null || qr.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") onClicked: reset(); } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 7f4f7e1..9b85b12 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -33,5 +33,6 @@ Flowee/AccountTypeLabel.qml Flowee/WalletSecretsView.qml Flowee/HamburgerMenu.qml + Flowee/QRWidget.qml -- 2.54.0 From 5ab02908154a5e844bf45084046fe90d5d793c9b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 14:56:56 +0100 Subject: [PATCH 0291/1428] Fix reference missing issue. --- guis/desktop/ConfigItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml index 3aeebfb..4a5449c 100644 --- a/guis/desktop/ConfigItem.qml +++ b/guis/desktop/ConfigItem.qml @@ -22,7 +22,7 @@ import "../Flowee" as Flowee Item { id: root width: wide ? 12 : 4 - height: column.height + height: hamburgerMenu.height property color color: Pay.useDarkSkin ? "white" : "black" property alias wide: hamburgerMenu default property alias actions: ourMenu.contentData -- 2.54.0 From b8ea2079a3b43c01e4f3ad58ab113b4ba54a7acd Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 15:37:22 +0100 Subject: [PATCH 0292/1428] fix qrwidget. --- guis/Flowee/QRWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index b355f7b..b47a3c8 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -27,9 +27,9 @@ Image { var h = parent.height - 220; return Math.min(h, 256) } - source: root.request == null ? "" : "image://qr/" + qr.request.qr + source: root.request == null ? "" : "image://qr/" + root.request.qr smooth: false - opacity: root.request == null || qr.request.state === PaymentRequest.Unpaid ? 1: 0 + opacity: root.request == null || root.request.state === PaymentRequest.Unpaid ? 1: 0 MouseArea { anchors.fill: parent -- 2.54.0 From 6f36028ebbcd22f77bf2e4c55aca7bab56a101de Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 18:21:57 +0100 Subject: [PATCH 0293/1428] Fix functioning of Esc in netview. --- guis/mobile/NetView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index 0bfe8df..bebf028 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -31,7 +31,7 @@ Page { focus: true Keys.onPressed: (event)=> { if (event.key === Qt.Key_Escape) { - root.visible = false; + thePile.pop(); event.accepted = true } } -- 2.54.0 From 5920d410f42cd77a6d72b9ec17c1186280d458ce Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 18:22:27 +0100 Subject: [PATCH 0294/1428] Move feedback text to not fall outside widget --- guis/Flowee/QRWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index b47a3c8..1ee0717 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -46,8 +46,8 @@ Image { height: feedbackText.height + 20 radius: 10 color: Pay.useDarkSkin ? "#333" : "#ddd" - anchors.top: parent.bottom - anchors.topMargin: -13 + anchors.bottom: parent.bottom + anchors.bottomMargin: -10 anchors.horizontalCenter: parent.horizontalCenter Label { -- 2.54.0 From 7cbea5f5a4343c5f43ca547d919353ff39ceb5be Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 18:23:11 +0100 Subject: [PATCH 0295/1428] Add title to currency selector. --- guis/mobile/CurrencySelector.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index 54b043f..f2dbfb3 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -16,12 +16,12 @@ * along with this program. If not, see . */ import QtQuick -// import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; Page { id: root + headerText: qsTr("Select Currency") ListView { anchors.fill: parent model: [ -- 2.54.0 From ab75be981e42d4e70ede873dcc43a80e5ef152cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 18:25:10 +0100 Subject: [PATCH 0296/1428] Add landing page For all times we start with a completely empty Flowee Pay, show a landing page which guides the user to create a new wallet or deposit funds into the default wallet. --- guis/mobile.qrc | 2 + guis/mobile/ImportWalletPage.qml | 178 +++++++++++++++++++++++++++++++ guis/mobile/NewAccount.qml | 161 +--------------------------- guis/mobile/StartupScreen.qml | 163 ++++++++++++++++++++++++++++ guis/mobile/main.qml | 5 +- 5 files changed, 348 insertions(+), 161 deletions(-) create mode 100644 guis/mobile/ImportWalletPage.qml create mode 100644 guis/mobile/StartupScreen.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6283791..c123b64 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -48,5 +48,7 @@ mobile/TxInfoSmall.qml mobile/AccountSelector.qml mobile/CurrencySelector.qml + mobile/StartupScreen.qml + mobile/ImportWalletPage.qml diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml new file mode 100644 index 0000000..2180a1c --- /dev/null +++ b/guis/mobile/ImportWalletPage.qml @@ -0,0 +1,178 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay + +Page { + id: importAccount + headerText: qsTr("Import Wallet") + + headerButtonVisible: true + headerButtonText: qsTr("Create") + headerButtonEnabled: accountName.text.length > 2 && finished + property var typedData: Pay.identifyString(secrets.text) + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool isPrivateKey: typedData === Wallet.PrivateKey + + onHeaderButtonClicked: { + var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); + if (importAccount.isMnemonic) + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + else + options = Pay.createImportedWallet(secrets.text, accountName.text, sh) + + options.forceSingleAddress = singleAddress.checked; + + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + + GridLayout { + width: parent.width + columns: 2 + Flowee.Label { + text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") + Layout.fillWidth: true + Layout.columnSpan: 2 + wrapMode: Text.Wrap + } + Flowee.Label { + text: qsTr("Secret", "The seed-phrase or private key") + ":" + Layout.columnSpan: 2 + } + Flowee.MultilineTextField { + id: secrets + Layout.fillWidth: true + focus: true + nextFocusTarget: accountName + clip: true + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + Layout.alignment: Qt.AlignTop + } + + Flowee.Label { + id: detectedType + color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color + text: { + var typedData = importAccount.typedData + if (typedData === Wallet.PrivateKey) + return qsTr("Private key", "description of type") // TODO print address to go with it + if (typedData === Wallet.CorrectMnemonic) + return qsTr("BIP 39 seed-phrase", "description of type") + if (typedData === Wallet.PartialMnemonicWithTypo) + return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") + if (typedData === Wallet.MissingLexicon) + return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this + return "" + } + Layout.columnSpan: 2 + } + + Flowee.Label { + text: qsTr("Name") + ":" + Layout.columnSpan: 2 + } + Flowee.TextField { + id: accountName + Layout.columnSpan: 2 + Layout.fillWidth: true + } + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + visible: importAccount.isPrivateKey + Layout.columnSpan: 2 + } + Flowee.Label { + text: qsTr("Alternate phrase") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + Layout.columnSpan: 2 + } + Flowee.Label { + Layout.columnSpan: 2 + text: qsTr("Oldest Transaction") + ":" + } + Flow { + spacing: 10 + QQC2.ComboBox { + id: month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; + } + } + QQC2.ComboBox { + id: year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 9; + } + } + Flowee.Label { + text: qsTr("Derivation") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + id: derivationPath + Layout.columnSpan: 2 + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most BCH wallets are created with + visible: !importAccount.isPrivateKey + color: Pay.checkDerivation(text) ? palette.text : "red" + } + } +} diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 8c8cc10..6ffcba8 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -34,7 +34,7 @@ Page { if (selectedKey === 1) page = newHDWalletScreen; if (selectedKey === 2) - page = importWalletScreen; + page = "./ImportWalletPage.qml"; thePile.push(page); } @@ -208,164 +208,5 @@ Page { } } } - Component { - id: importWalletScreen - - Page { - id: importAccount - headerText: qsTr("Import Wallet") - - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 && finished - property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; - property bool isPrivateKey: typedData === Wallet.PrivateKey - - onHeaderButtonClicked: { - var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - - options.forceSingleAddress = singleAddress.checked; - - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } - - GridLayout { - width: parent.width - columns: 2 - Flowee.Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.fillWidth: true - Layout.columnSpan: 2 - wrapMode: Text.Wrap - } - Flowee.Label { - text: qsTr("Secret", "The seed-phrase or private key") + ":" - Layout.columnSpan: 2 - } - Flowee.MultilineTextField { - id: secrets - Layout.fillWidth: true - focus: true - nextFocusTarget: accountName - clip: true - } - Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - Layout.alignment: Qt.AlignTop - } - - Flowee.Label { - id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color - text: { - var typedData = importAccount.typedData - if (typedData === Wallet.PrivateKey) - return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) - return qsTr("BIP 39 seed-phrase", "description of type") - if (typedData === Wallet.PartialMnemonicWithTypo) - return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Wallet.MissingLexicon) - return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this - return "" - } - Layout.columnSpan: 2 - } - - Flowee.Label { - text: qsTr("Name") + ":" - Layout.columnSpan: 2 - } - Flowee.TextField { - id: accountName - Layout.columnSpan: 2 - Layout.fillWidth: true - } - - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") - checked: true - visible: importAccount.isPrivateKey - Layout.columnSpan: 2 - } - Flowee.Label { - text: qsTr("Alternate phrase") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - Layout.columnSpan: 2 - } - Flowee.Label { - Layout.columnSpan: 2 - text: qsTr("Oldest Transaction") + ":" - } - Flow { - spacing: 10 - QQC2.ComboBox { - id: month - model: { - let locale = Qt.locale(); - var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { - list.push(locale.monthName(i)); - } - return list; - } - } - QQC2.ComboBox { - id: year - model: { - var list = []; - let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { - list.push(i); - } - return list; - } - currentIndex: 9; - } - } - Flowee.Label { - text: qsTr("Derivation") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - id: derivationPath - Layout.columnSpan: 2 - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most BCH wallets are created with - visible: !importAccount.isPrivateKey - color: Pay.checkDerivation(text) ? palette.text : "red" - } - } - } - } } } diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml new file mode 100644 index 0000000..517a6df --- /dev/null +++ b/guis/mobile/StartupScreen.qml @@ -0,0 +1,163 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + id: root + headerText: qsTr("Welcome!") + headerButtonVisible: true + headerButtonText: qsTr("Continue") + onHeaderButtonClicked: { + if (!portfolio.current.isUserOwned) { + var mainView = thePile.get(0); + mainView.currentIndex = 2 // go to the receive-tab + } + + thePile.pop(); + } + + + Item { // data store for this page. + Connections { + // connect to wallet and when its no longe user owned, close this window. + target: portfolio.current + function onIsUserOwnedChanged() { + // transaction seen arriving, this startup screen has fulfilled its purpose. + thePile.pop(); + } + } + } + + Flickable { + anchors.fill: parent + contentWidth: parent.width + contentHeight: column.height + clip: true + Column { + id: column + width: parent.width + spacing: 10 + + Row { + spacing: 20 + x: (root.width - width) / 2 + + Rectangle { + id: logo + width: 100 + height: 100 + radius: 50 + color: mainWindow.floweeBlue + Item { + // clip the logo only, ignore the text part + width: 71 + height: 71 + clip: true + x: 18 + y: 22 + Image { + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: height / 77 * 449 + height: 71 + } + } + } + Image { + source: "qrc:/bch.svg" + y: 10 + width: 87 + height: 87 + smooth: true + } + } + Flowee.Label { + text: qsTr("Moving the world towards a Bitcoin Cash economy") + wrapMode: Text.WordWrap + font.italic: true + horizontalAlignment: Text.AlignHCenter + width: root.width * 0.8 + x: (root.width - width) / 2 + } + Item { width: 1; height: 20 } // spacer + + Rectangle { + id: startImportButton + radius: 20 + height:buttonText.height + 20 + width: buttonText.width + 40 + color: "#178b3a" + x: (root.width - width) / 2 + + Flowee.Label { + id: buttonText + color: "white" + text: qsTr("I already have a wallet") + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: thePile.push("./ImportWalletPage.qml"); + } + } + + Item { width: 1; height: 10 } // spacer + + Row { + spacing: 15 + x: (root.width - width) / 2 + Rectangle { + width: 50 + height: 1 + color: root.palette.button + anchors.verticalCenter: parent.verticalCenter + } + Flowee.Label { + text: qsTr("OR") + } + Rectangle { + width: 50 + height: 1 + color: root.palette.button + anchors.verticalCenter: parent.verticalCenter + } + } + + Item { width: 1; height: 10 } // spacer + + Flowee.Label { + id: instructions + text: qsTr("I want to send funds to my new wallet") + ":" + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + width: root.width * 0.8 + x: (root.width - width) / 2 + } + + Flowee.QRWidget { + id: bla + request: portfolio.current.createPaymentRequest(root) + x: (root.width - width) / 2 + } + + Item { width: 1; height: 40 } // spacer + } + } +} diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 26f6444..122a474 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -34,8 +34,11 @@ ApplicationWindow { onIsLoadingChanged: { // only load our UI when the p2p layer is loaded and all // variables are available. - if (!isLoading) + if (!isLoading) { thePile.replace("./MainView.qml"); + if (portfolio.accounts.length === 0) + thePile.push("./StartupScreen.qml"); + } } Component.onCompleted: updateFontSize(); function updateFontSize() { -- 2.54.0 From 51b5fddc1d98384972e5a4b53fa977ef4339fddb Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Feb 2023 18:26:58 +0100 Subject: [PATCH 0297/1428] Fix sizing This works around the weirdness that we can inherit from Page, while the children are actually added to a separate item and that caused confusion when the sizes of those two were not equal. This is now fixed, at least for the width. --- guis/mobile/Page.qml | 10 +++++----- guis/mobile/StartupScreen.qml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 79a721e..8429fda 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -23,8 +23,8 @@ import "../Flowee" as Flowee QQC2.Control { id: root - width: parent == null ? 10 : parent.width - height: parent == null ? 10 : parent.height + width: parent == null ? 10 : parent.width - 20 + height: parent == null ? 10 : parent.height - header.height default property alias content: child.children property alias headerText: headerLabel.text @@ -55,7 +55,7 @@ QQC2.Control { Rectangle { id: header - width: parent.width + width: parent.width + 20 height: 50 color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue @@ -117,10 +117,10 @@ QQC2.Control { anchors.fill: parent QQC2.Control { id: child - width: root.width - 20 x: 10 y: header.height + 10 - height: root.height - y + width: parent.width + height: parent.height } } Keys.onPressed: (event)=> { diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 517a6df..119362f 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -89,7 +89,7 @@ Page { } } Flowee.Label { - text: qsTr("Moving the world towards a Bitcoin Cash economy") + text: qsTr("Moving the world towards a Bitcoin\u00a0Cash economy") wrapMode: Text.WordWrap font.italic: true horizontalAlignment: Text.AlignHCenter -- 2.54.0 From c0243210831343bcbd486dfe5e1d49a4f619fd37 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 14:56:18 +0100 Subject: [PATCH 0298/1428] Revise colors, especially dark-theme This gives the dark-theme a make-over with better colors, more colors coming from the palette and this makes the conceptual usage between the mobile and desktop clients of colors to be in-line. --- guis/ControlColors.js | 73 +++++++++++-------- guis/Flowee/CheckBox.qml | 14 ++-- guis/Flowee/GroupBox.qml | 20 ++++-- guis/Flowee/HamburgerMenu.qml | 4 +- guis/desktop/AccountListItem.qml | 2 +- guis/desktop/WalletTransaction.qml | 16 +---- guis/desktop/main.qml | 10 +-- guis/mobile/AccountHistory.qml | 109 +++++++++++++++-------------- guis/mobile/MainViewBase.qml | 9 ++- guis/mobile/MenuOverlay.qml | 4 +- guis/mobile/Page.qml | 34 ++++----- guis/mobile/PopupOverlay.qml | 2 +- 12 files changed, 157 insertions(+), 140 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index c6c792a..c91ce3d 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,44 +24,55 @@ function applySkin(item) { } function applyDarkSkin(item) { - item.palette.alternateBase = "#292c30"; - item.palette.base = "#232629"; - item.palette.brightText = "#fcfcfc" - item.palette.button = "#4d4d4d" - item.palette.buttonText = "#fcfcfc" - item.palette.dark = "#bdc3c7" - item.palette.highlight = "#76bfc7" - item.palette.highlightedText = "#090909" - item.palette.light = "#202326" - item.palette.link = "#2980b9" - item.palette.linkVisited = "#7f8c8d" - item.palette.mid = "#6b6b6b" - item.palette.midlight = "#41464c" - item.palette.shadow = "#111111" - item.palette.text = "#b2b2b2" - item.palette.toolTipBase = "#31363b" - item.palette.toolTipText = "#eff0f1" - item.palette.window = "#232629" - item.palette.windowText = "#fcfcfc" + item.palette.window = "#222222" // background of all things. + item.palette.base = "#111111"; // background of text fields, popup-menus + item.palette.alternateBase = "#1d1d1d"; // background of even rows in lists + + item.palette.light = "#181818" // background of groupbox, odd rows, pages etc + item.palette.dark = "#bdc3c7" // contrast to the previous one + item.palette.mid = "#4d4d4d" // borders for groupbox, sliders, text-area + + item.palette.windowText = "#fcfcfc" // Most text (labels etc) + item.palette.text = "#fcfcfc" // multiline textarea text color. + item.palette.brightText = "#9f9f9f" // popup menu text, less strong text + + item.palette.button = "#4d4d4d" // buttons and groupbox borders and slider borders + item.palette.buttonText = "#fcfcfc" // just text for buttons + + item.palette.highlight = "#76bfc7" // mouseover on popup, slider thumb, outline of active textfield, selected-text background + item.palette.highlightedText = "#090909"// selected text + + // Desktop is the only one that used tooltips + item.palette.toolTipBase = "#629c7b" // tooltip background + item.palette.toolTipText = "#000000" // tooltip text and outline + item.palette.shadow = "#90e4b5" // tooltip shadow + + // shade of Flowee-green. + item.palette.midlight = "#4f7d63" + + // Not currently used + item.palette.link = "#2980b9" // unused + item.palette.linkVisited = "#7f8c8d" // unused } function applyLightSkin(item) { - item.palette.alternateBase = "#d8dae0"; + item.palette.window = "#e0dfde"; item.palette.base = "#e8e7e6"; + item.palette.alternateBase = "#e2e1e0"; + item.palette.light = "#dddcdb"; + item.palette.dark = "#353637"; + item.palette.mid = "#bdbdbd"; + item.palette.windowText = "#26282a"; + item.palette.text = "#353637"; item.palette.brightText = "#ffffff"; item.palette.button = "#bfbebd"; item.palette.buttonText = "#26282a"; - item.palette.dark = "#353637"; item.palette.highlight = "#0066ff"; item.palette.highlightedText = "#f9f9f9"; - item.palette.light = "#f6f6f6"; - item.palette.link = "#45a7d7"; - item.palette.linkVisited = "#7f8c8d"; - item.palette.mid = "#bdbdbd"; - item.palette.midlight = "#e4e4e4"; - item.palette.shadow = "#28282a"; - item.palette.text = "#353637"; item.palette.toolTipBase = "#ffffff"; item.palette.toolTipText = "#000000"; - item.palette.window = "#e0dfde"; - item.palette.windowText = "#26282a"; + item.palette.shadow = "#28282a"; + item.palette.midlight = "#9ec7af" + + item.palette.link = "#45a7d7"; + item.palette.linkVisited = "#7f8c8d"; } diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index f1ea8d2..812159e 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,8 +39,12 @@ T.CheckBox { Rectangle { anchors.fill: parent radius: parent.height / 3 - color: control.sliderOnIndicator && control.enabled && control.checked ? (Pay.useDarkSkin ? "#4f7d63" : "#9ec7af") : mainWindow.palette.window - border.color: control.activeFocus ? mainWindow.palette.highlight : mainWindow.palette.button + color: { + if (control.sliderOnIndicator && control.enabled && control.checked) + return control.palette.midlight + return control.palette.window + } + border.color: control.activeFocus ? control.palette.highlight : control.palette.button border.width: 0.8 Behavior on color { ColorAnimation {}} @@ -55,8 +59,8 @@ T.CheckBox { if (!control.enabled) return "darkgray" if (control.checked && Pay.useDarkSkin) - return mainWindow.palette.windowText; - return mainWindow.palette.highlight + return control.palette.windowText; + return control.palette.highlight } Behavior on x { NumberAnimation {}} Behavior on color { ColorAnimation {}} diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index 9850666..2377c49 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,8 +46,8 @@ QQC2.Control { width: parent.width y: titleArea.visible ? titleLabel.height / 2 : arrowPoint.height / 2 height: root.effectiveCollapsed ? 1 : parent.height - y; - color: "#00000000" - border.color: titleLabel.palette.button + color: root.palette.light + border.color: root.palette.mid border.width: 1.3 radius: 3 } @@ -60,14 +60,20 @@ QQC2.Control { cursorShape: Qt.PointingHandCursor } - Rectangle { + Item { id: titleArea visible: titleLabel.text !== "" - color: titleLabel.palette.window width: titleLabel.width + 6 + (summaryLabel.visible ? summaryLabel.width + 6 : 0) height: titleLabel.height anchors.left: arrowPoint.right - Text { + Rectangle { + // erase the groupbox outline behind the text + width: parent.width + color: titleLabel.palette.window + height: 2 + y: parent.height / 2 - 1 + } + Label { id: titleLabel x: 3 color: root.palette.windowText @@ -84,7 +90,7 @@ QQC2.Control { } } - Text { + Label { id: summaryLabel anchors.left: titleLabel.right anchors.leftMargin: 20 diff --git a/guis/Flowee/HamburgerMenu.qml b/guis/Flowee/HamburgerMenu.qml index 7a12bbc..c0a0139 100644 --- a/guis/Flowee/HamburgerMenu.qml +++ b/guis/Flowee/HamburgerMenu.qml @@ -22,6 +22,7 @@ Item { implicitWidth: root.wide ? 12 : 4 implicitHeight: 16 property bool wide: false + property color color: mainWindow.palette.windowText Column { spacing: 3 @@ -29,7 +30,8 @@ Item { Repeater { model: 3 delegate: Rectangle { - color: "white" + id: rect + color: root.color width: root.wide ? 12 : 4 height: 3 radius: 2 diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index a32f335..5836cfe 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -35,7 +35,7 @@ Item { id: background property bool hover: false radius: 7 - color: selected && !Pay.useDarkSkin ? "white" : "#00000000" // transparant + color: selected ? mainWindow.palette.light : mainWindow.palette.window border.width: 1.5 border.color: { if (portfolio.current === account) diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index c5bae44..b78a6b0 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ import QtQuick import QtQuick.Controls import "../Flowee" as Flowee -Item { +Rectangle { id: txRoot height: { var rc = mainLabel.height + 10 + date.height @@ -28,6 +28,7 @@ Item { return rc; } width: mainLabel.width + bitcoinAmountLabel.width + 30 + color: (index % 2) == 0 ? mainLabel.palette.light : mainLabel.palette.alternateBase property bool isRejected: model.height === -2 // -2 is the magic block-height indicating 'rejected' @@ -147,17 +148,6 @@ Item { anchors.right: parent.right } - // visual separator - Rectangle { - width: parent.width / 100 * 80 - height: 1 - color: "#99999B" - opacity: 0.5 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - } - MouseArea { anchors.fill: parent onClicked: detailsPane.source = (detailsPane.source == "") ? "./WalletTransactionDetails.qml" : "" diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index aef7d37..e8be6a8 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -182,10 +182,10 @@ ApplicationWindow { Rectangle { width: parent.width height: warn.height + unarchiveButton.height + 26 - color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" + color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" // yellow visible: !isLoading && portfolio.current.isArchived radius: 7 - Text { + Flowee.Label { id: warn y: 6 x: 6 @@ -212,7 +212,7 @@ ApplicationWindow { id: needsDecryptPane width: parent.width height: decryptText.height + decryptPwd.height + decryptButton.height + 36 - color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" + color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" // yellow visible: !isLoading && portfolio.current.needsPinToOpen && !portfolio.current.isDecrypted radius: 7 @@ -299,11 +299,11 @@ ApplicationWindow { width: label.width + 12 height: label.height + 12 radius: 5 - color: label.palette.dark + color: label.palette.light Label { id: label anchors.centerIn: parent - color: palette.light + color: palette.dark text: isLoading || activityView.model === null ? "" : activityView.model.dateForItem(thumb.position); } } diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 7ce7d6a..ce9b33d 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -28,6 +28,7 @@ ListView { property string title: qsTr("Home") Rectangle { + id: backToTopButton width: 60 height: 60 anchors.right: parent.right @@ -66,60 +67,64 @@ ListView { To avoid a mess of two scroll-areas (of Flickables) we simply make the top part into a header of the listview. */ - header: Column { - id: column - width: root.width - 20 - x: 10 - y: 10 - z: 10 // make sure the wallet Selector can cover the historical items + header: Rectangle { + color: root.palette.window + width: root.width + height: column.height + Column { + id: column + width: root.width - 20 + x: 10 + y: 10 - Flowee.BitcoinAmountLabel { - opacity: Pay.hideBalance ? 0.2 : 1 - fontPixelSize: 34 - value: { - if (Pay.hideBalance) - return 88888888; - return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + Flowee.BitcoinAmountLabel { + opacity: Pay.hideBalance ? 0.2 : 1 + fontPixelSize: 34 + value: { + if (Pay.hideBalance) + return 88888888; + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + } + colorize: false } - colorize: false + + AccountSyncState { + account: portfolio.current + width: parent.width + } + + Row { + width: parent.width + height: 60 + Flowee.ImageButton { + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: parent.width / 3 + onClicked: thePile.push("PayWithQR.qml") + iconSize: 40 + height: dummyButton.height + } + IconButton { + id: dummyButton + width: parent.width / 3 + text: qsTr("Scheduled") + } + Flowee.ImageButton { + width: parent.width / 3 + height: dummyButton.height + iconSize: 50 + source: "qrc:/receive.svg" + onClicked: switchToTab(2) // receive tab + } + } + + /* TODO + "Is archive" / "Unrchive"" + + Is Encryopted / Decrypt + */ + + Item { width: 10; height: 15 } // spacer } - - AccountSyncState { - account: portfolio.current - width: parent.width - } - - Row { - width: parent.width - height: 60 - Flowee.ImageButton { - source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - width: parent.width / 3 - onClicked: thePile.push("PayWithQR.qml") - iconSize: 40 - height: dummyButton.height - } - IconButton { - id: dummyButton - width: parent.width / 3 - text: qsTr("Scheduled") - } - Flowee.ImageButton { - width: parent.width / 3 - height: dummyButton.height - iconSize: 50 - source: "qrc:/receive.svg" - onClicked: switchToTab(2) // receive tab - } - } - - /* TODO - "Is archive" / "Unrchive"" - - Is Encryopted / Decrypt - */ - - Item { width: 10; height: 15 } // spacer } model: portfolio.current.transactions @@ -169,7 +174,7 @@ ListView { y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; radius: 20 - color: root.palette.base + color: root.palette.light border.width: 1 border.color: root.palette.highlight } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4bc00cf..4b9b574 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +25,10 @@ QQC2.Control { width: parent.width height: parent.height + background: Rectangle { + color: root.palette.light + } + // This trick means any child items are actually added to the 'stack' item's children. default property alias content: stack.children property int currentIndex: 0 @@ -60,11 +64,12 @@ QQC2.Control { id: header width: parent.width height: 50 - color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue + color: Pay.useDarkSkin ? root.palette.window : mainWindow.floweeBlue Flowee.HamburgerMenu { id: menuButton anchors.verticalCenter: parent.verticalCenter + color: "white" wide: true x: 8 } diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 776988b..982dd2c 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -40,7 +40,7 @@ Item { Rectangle { id: menuArea - color: mainWindow.palette.base + color: mainWindow.palette.window width: 300 height: parent.height x: root.open ? 0 : 0 - width -3 @@ -60,7 +60,7 @@ Item { h = h+ Math.max(currentAccountName.height, 12) + 10 return h; } - color: Qt.lighter(mainWindow.palette.base) + color: Qt.lighter(mainWindow.palette.window) property bool openAccounts: false clip: true diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 8429fda..fd400c6 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -23,10 +23,18 @@ import "../Flowee" as Flowee QQC2.Control { id: root - width: parent == null ? 10 : parent.width - 20 - height: parent == null ? 10 : parent.height - header.height + width: parent == null ? 10 : parent.width + height: parent == null ? 10 : parent.height - default property alias content: child.children + background: Rectangle { + color: root.palette.light + } + + leftPadding: 10 + rightPadding: 10 + topPadding: header.height + + default property alias content: focusScope.children property alias headerText: headerLabel.text property alias headerButtonVisible: headerButton.visible property alias headerButtonText: headerButton.text @@ -55,9 +63,9 @@ QQC2.Control { Rectangle { id: header - width: parent.width + 20 + width: parent.width // + 20 height: 50 - color: Pay.useDarkSkin ? root.palette.base : mainWindow.floweeBlue + color: Pay.useDarkSkin ? root.palette.window : mainWindow.floweeBlue Image { id: backButton @@ -106,22 +114,8 @@ QQC2.Control { } } - Rectangle { - width: parent.width - y: header.height - height: parent.height - y - color: root.palette.base - } - FocusScope { + contentItem: FocusScope { id: focusScope - anchors.fill: parent - QQC2.Control { - id: child - x: 10 - y: header.height + 10 - width: parent.width - height: parent.height - } } Keys.onPressed: (event)=> { if (event.key === Qt.Key_Back) { diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 1b0cd23..c03f799 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -57,7 +57,7 @@ FocusScope { root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } background: Rectangle { - color: mainWindow.palette.base + color: mainWindow.palette.light border.color: mainWindow.palette.highlight border.width: 1 radius: 5 -- 2.54.0 From a04b529cb816ad565984204fa35129857098433f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 15:03:27 +0100 Subject: [PATCH 0299/1428] Fixes in color/fontsize --- guis/ControlColors.js | 4 ++-- guis/mobile/TextButton.qml | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index c91ce3d..77f9e71 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -62,8 +62,8 @@ function applyLightSkin(item) { item.palette.dark = "#353637"; item.palette.mid = "#bdbdbd"; item.palette.windowText = "#26282a"; - item.palette.text = "#353637"; - item.palette.brightText = "#ffffff"; + item.palette.text = "#26282a"; + item.palette.brightText = "#555658"; item.palette.button = "#bfbebd"; item.palette.buttonText = "#26282a"; item.palette.highlight = "#0066ff"; diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 073a07b..cd95d2b 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,9 +43,11 @@ Item { id: smallLabel anchors.top: label.bottom anchors.topMargin: 6 - font.pointSize: mainWindow.font.poinSize * 0.7 + font.pixelSize: label.font.pixelSize * 0.8 font.bold: false - color: Qt.darker(palette.windowText, 1.5) + color: palette.brightText + width: parent.width + wrapMode: Text.WordWrap } MouseArea { anchors.fill: parent -- 2.54.0 From 6bb3d901eb0db8bdb0df7fa7794e6e6a82241fd4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 15:30:59 +0100 Subject: [PATCH 0300/1428] Adjust max width --- guis/mobile/PopupOverlay.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index c03f799..8803b2c 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -33,8 +33,8 @@ FocusScope { */ function open(sourceComponent, target) { thePopup.palette = mainWindow.palette - thePopup.width = target.width - thePopup.x = (width - target.width) / 2 + thePopup.width = Math.min(root.width - 18, target.width) + thePopup.x = (width - thePopup.width) / 2 thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); loader.sourceComponent = sourceComponent; // last, as it starts the loading return loader.item; -- 2.54.0 From e0056fd8c597bf571e3b423193fd79c6cc5d6ce6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 15:31:18 +0100 Subject: [PATCH 0301/1428] Use the highlight color more properly --- guis/ControlColors.js | 2 +- guis/mobile/AccountHistory.qml | 4 ++-- guis/mobile/AccountSelector.qml | 16 +++++++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index 77f9e71..8c77233 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -71,7 +71,7 @@ function applyLightSkin(item) { item.palette.toolTipBase = "#ffffff"; item.palette.toolTipText = "#000000"; item.palette.shadow = "#28282a"; - item.palette.midlight = "#9ec7af" + item.palette.midlight = "#5c9e67" item.palette.link = "#45a7d7"; item.palette.linkVisited = "#7f8c8d"; diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index ce9b33d..25e946f 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -176,7 +176,7 @@ ListView { radius: 20 color: root.palette.light border.width: 1 - border.color: root.palette.highlight + border.color: root.palette.midlight } Item { @@ -280,7 +280,7 @@ ListView { height: 1 width: parent.width - 16 x: 8 - color: root.palette.highlight + color: root.palette.midlight } MouseArea { diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml index 611edd0..e771fc5 100644 --- a/guis/mobile/AccountSelector.qml +++ b/guis/mobile/AccountSelector.qml @@ -34,8 +34,8 @@ QQC2.Popup { } background: Rectangle { - color: root.palette.base - border.color: root.palette.highlight + color: root.palette.light + border.color: root.palette.midlight border.width: 1 radius: 5 } @@ -56,10 +56,16 @@ QQC2.Popup { width: columnLayout.width height: accountName.height + lastActive.height + 6 * 3 Rectangle { - color: root.palette.button - radius: 5 - anchors.fill: parent + id: selectedItemIndicator visible: modelData === root.selectedAccount + anchors.fill: parent + color: root.palette.alternateBase + + Rectangle { + height: parent.height + width: 3 + color: root.palette.highlight + } } Flowee.Label { id: accountName -- 2.54.0 From 9cd6202da46da37dd1d5bd6267fbb0900c295ba9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 17:31:03 +0100 Subject: [PATCH 0302/1428] Consistency in colors --- guis/mobile/AccountHistory.qml | 6 +++--- guis/mobile/PopupOverlay.qml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 25e946f..6bf6a89 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -95,7 +95,7 @@ ListView { Row { width: parent.width - height: 60 + height: dummyButton.height Flowee.ImageButton { source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); width: parent.width / 3 @@ -138,7 +138,7 @@ ListView { height: label.height + 15 width: root.width Rectangle { - color: root.palette.window + color: root.palette.light anchors.fill: parent } Flowee.Label { @@ -174,7 +174,7 @@ ListView { y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; radius: 20 - color: root.palette.light + color: root.palette.alternateBase border.width: 1 border.color: root.palette.midlight } diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 8803b2c..e2da87f 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -58,7 +58,7 @@ FocusScope { } background: Rectangle { color: mainWindow.palette.light - border.color: mainWindow.palette.highlight + border.color: mainWindow.palette.midlight border.width: 1 radius: 5 } -- 2.54.0 From 6832870ba246cea997749f1da2b9f6c7a5781f8e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 17:49:16 +0100 Subject: [PATCH 0303/1428] Use the width from the parent instead of root. This centers it better again and avoid future issues by using the local sizes instead of some parent. --- guis/mobile/StartupScreen.qml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 119362f..7abcd2a 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -57,7 +57,7 @@ Page { Row { spacing: 20 - x: (root.width - width) / 2 + x: (column.width - width) / 2 Rectangle { id: logo @@ -93,8 +93,8 @@ Page { wrapMode: Text.WordWrap font.italic: true horizontalAlignment: Text.AlignHCenter - width: root.width * 0.8 - x: (root.width - width) / 2 + width: column.width * 0.8 + x: column.width / 10 } Item { width: 1; height: 20 } // spacer @@ -104,7 +104,7 @@ Page { height:buttonText.height + 20 width: buttonText.width + 40 color: "#178b3a" - x: (root.width - width) / 2 + x: (column.width - width) / 2 Flowee.Label { id: buttonText @@ -118,11 +118,11 @@ Page { } } - Item { width: 1; height: 10 } // spacer + Item { width: 1; height: 5 } // spacer Row { spacing: 15 - x: (root.width - width) / 2 + x: (column.width - width) / 2 Rectangle { width: 50 height: 1 @@ -140,21 +140,21 @@ Page { } } - Item { width: 1; height: 10 } // spacer + Item { width: 1; height: 5 } // spacer Flowee.Label { id: instructions text: qsTr("I want to send funds to my new wallet") + ":" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter - width: root.width * 0.8 - x: (root.width - width) / 2 + width: column.width * 0.8 + x: column.width / 10 } Flowee.QRWidget { id: bla request: portfolio.current.createPaymentRequest(root) - x: (root.width - width) / 2 + x: (column.width - width) / 2 } Item { width: 1; height: 40 } // spacer -- 2.54.0 From 2809685ecb6e26c29db8642d93aa2b9262affe19 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 18:44:53 +0100 Subject: [PATCH 0304/1428] UX improvcements of highlights. Make selected list-item and selectd-tab consistent in coloring and layout. This improves contrast dramatically and should make things easier to understand. --- guis/mobile/AccountSelector.qml | 15 ++++++++------- guis/mobile/MainViewBase.qml | 25 ++++++++++++++++++++----- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml index e771fc5..f185f4f 100644 --- a/guis/mobile/AccountSelector.qml +++ b/guis/mobile/AccountSelector.qml @@ -59,13 +59,14 @@ QQC2.Popup { id: selectedItemIndicator visible: modelData === root.selectedAccount anchors.fill: parent - color: root.palette.alternateBase - - Rectangle { - height: parent.height - width: 3 - color: root.palette.highlight - } + color: root.palette.highlight + opacity: 0.15 + } + Rectangle { + height: parent.height + width: 3 + color: root.palette.highlight + visible: selectedItemIndicator.visible } Flowee.Label { id: accountName diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4b9b574..f661af0 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -139,19 +139,34 @@ QQC2.Control { width: root.width anchors.top: header.bottom; anchors.bottom: tabbar.top } + + Rectangle { + anchors.fill: tabbar + color: root.palette.window + } + Row { id: tabbar anchors.bottom: parent.bottom Repeater { model: stack.children.length - delegate: Rectangle { + delegate: Item { height: 55 width: root.width / stack.children.length; - color: { - modelData === root.currentIndex - ? root.palette.button - : root.palette.base + + Rectangle { + x: 5 + height: 4 + width: parent.width - 10 + color: mainWindow.palette.highlight + visible: modelData === root.currentIndex + } + Rectangle { + anchors.fill: parent + color: mainWindow.palette.highlight + visible: modelData === root.currentIndex + opacity: 0.15 } Image { source: stack.children[modelData].icon -- 2.54.0 From 26075e3dc2d6dd3c8b03d0845bfa2fb2b68c03b5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 14 Feb 2023 18:58:40 +0100 Subject: [PATCH 0305/1428] Fix colors in this screen too --- guis/mobile/PayWithQR.qml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 6e426c8..a42f5eb 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -142,12 +142,24 @@ Page { anchors.top: userComment.bottom anchors.topMargin: 10 border.width: 1 - border.color: root.palette.highlight - color: root.palette.base + border.color: root.palette.midlight + color: root.palette.light width: inputs.width + 20 height: 40 radius: 15 + Rectangle { + color: root.palette.highlight + opacity: 0.3 + radius: 6 + width: 35 + height: parent.height - 4 + y: 2 + x: root.fiatFollowsSats ? 5 : 45 + + Behavior on x { NumberAnimation { } } + } + Row { id: inputs x: 10 @@ -184,7 +196,7 @@ Page { width: 1 y: inputs.y * -1 height: parent.height - color: root.palette.text + color: root.palette.dark } Flowee.HamburgerMenu { @@ -202,7 +214,6 @@ Page { sourceComponent = languageMenuComponent } onLoaded: item.open(); - } Component { @@ -231,18 +242,6 @@ Page { } Item { width: 5; height: 1 } // spacer } - - Rectangle { - color: root.palette.text - opacity: 0.3 - radius: 6 - width: 35 - height: parent.height - 4 - y: 2 - x: root.fiatFollowsSats ? 5 : 45 - - Behavior on x { NumberAnimation { } } - } } Flowee.Label { @@ -260,7 +259,8 @@ Page { id: walletNameBackground anchors.top: currentWalletLabel.visible ? currentWalletLabel.top : currentWalletValue.top anchors.topMargin: -5 - width: parent.width + width: parent.width + 20 + x: -10 anchors.bottom: currentWalletValue.bottom anchors.bottomMargin: -5 color: root.palette.alternateBase @@ -339,7 +339,7 @@ Page { // make dim when not enabled. color: { if (!enabled) - return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 1.8) : Qt.lighter(palette.buttonText, 2); + return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 2) : Qt.lighter(palette.buttonText, 2); return palette.windowText; } } -- 2.54.0 From 155647d7ac59cc29a9c171a1e5f671e379726488 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 17 Feb 2023 19:22:24 +0100 Subject: [PATCH 0306/1428] Add docs and make input selector work again Document various of the Payment properties better and make the input selector use the multi-detail getter for price. --- guis/desktop/SendTransactionPane.qml | 2 +- src/Payment.cpp | 4 ++-- src/Payment.h | 12 +++++++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 96db1a4..9e415a3 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -502,7 +502,7 @@ Item { } Flowee.BitcoinAmountLabel { id: neededAmountLabel - value: payment.paymentAmount + value: payment.effectiveBchAmount Layout.fillWidth: true colorize: false } diff --git a/src/Payment.cpp b/src/Payment.cpp index 8dfbc27..a437628 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -673,8 +673,8 @@ int Payment::effectiveFiatAmount() const for (auto i = m_paymentDetails.crbegin(); i != m_paymentDetails.crend(); ++i) { auto *detail = *i; if (detail->isInputs()) - inputSelector = detail->toInputs(); - if (!detail->isOutput()) + inputSelector = detail->toInputs(); // find the one input selector if there is one + if (!detail->isOutput()) // only care about outputs from here continue; auto *out = detail->toOutput(); if (out->maxAllowed() && out->maxSelected()) { diff --git a/src/Payment.h b/src/Payment.h index a9fce61..7063f5c 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -36,13 +36,16 @@ class Payment : public QObject { Q_OBJECT Q_PROPERTY(int feePerByte READ feePerByte WRITE setFeePerByte NOTIFY feePerByteChanged) + /// The single-output amount of funds being sent by this Payment. Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) + /// The single-output amount of funds being sent by this Payment. Q_PROPERTY(double paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) + /// The single-output address we will send to Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTargetAddress READ formattedTargetAddress NOTIFY targetAddressChanged) Q_PROPERTY(bool preferSchnorr READ preferSchnorr WRITE setPreferSchnorr NOTIFY preferSchnorrChanged); - /// Input is valid, tx can be prepared + /// If input is valid, tx can be prepared. \see prepare() Q_PROPERTY(bool isValid READ validate NOTIFY validChanged); Q_PROPERTY(QList details READ paymentDetails NOTIFY paymentDetailsChanged); Q_PROPERTY(BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) @@ -53,14 +56,17 @@ class Payment : public QObject Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged); Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) + /// The payment-wide amount of funds being sent by this Payment. + Q_PROPERTY(int effectiveFiatAmount READ effectiveFiatAmount NOTIFY amountChanged) + /// The payment-wide amount of funds being sent by this Payment. + Q_PROPERTY(double effectiveBchAmount READ effectiveBchAmount NOTIFY amountChanged) + // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared Q_PROPERTY(bool txPrepared READ txPrepared NOTIFY txPreparedChanged); Q_PROPERTY(QString txid READ txid NOTIFY txCreated) Q_PROPERTY(int assignedFee READ assignedFee NOTIFY txCreated) Q_PROPERTY(int txSize READ txSize NOTIFY txCreated) - Q_PROPERTY(int effectiveFiatAmount READ effectiveFiatAmount NOTIFY txCreated) - Q_PROPERTY(double effectiveBchAmount READ effectiveBchAmount NOTIFY txCreated) /// If Prepare failed, this is set. Q_PROPERTY(QString error READ error NOTIFY errorChanged); -- 2.54.0 From a8264538c0383d6ed7959dfef21858864a908166 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 18 Feb 2023 16:25:33 +0100 Subject: [PATCH 0307/1428] Payment::reset() should not delete the exchange rate to reset the payment removes all user set properties, giving you a clean payment object. Since the exchange rate is both not user-set and also not something that is expected to change between one payment and the next, it is now no longer cleared on reset(). --- src/Payment.cpp | 1 - src/Payment.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index a437628..76649fd 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -351,7 +351,6 @@ void Payment::broadcast() void Payment::reset() { - m_fiatPrice = 50000; // just have it initialized m_fee = 1; m_txPrepared = false; m_txBroadcastStarted = false; diff --git a/src/Payment.h b/src/Payment.h index 7063f5c..79a52fc 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -249,7 +249,7 @@ private: Tx m_tx; int m_fee; // in sats per byte int m_assignedFee; - int m_fiatPrice; // price for one whole BCH + int m_fiatPrice = 50000; // price for one whole BCH std::shared_ptr m_infoObject; short m_sentPeerCount; short m_rejectedPeerCount; -- 2.54.0 From aa0a505f8fd0864c559704e37eba286020721e3b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 19 Feb 2023 18:42:33 +0100 Subject: [PATCH 0308/1428] Add price details popup Show the current price, historical price-differences and a button to change the currency. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 9 ++++ guis/mobile/PopupOverlay.qml | 4 +- guis/mobile/PriceDetails.qml | 85 ++++++++++++++++++++++++++++++++++ src/PriceDataProvider.cpp | 10 +++- src/PriceDataProvider.h | 5 ++ 6 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 guis/mobile/PriceDetails.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c123b64..7b398c5 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -50,5 +50,6 @@ mobile/CurrencySelector.qml mobile/StartupScreen.qml mobile/ImportWalletPage.qml + mobile/PriceDetails.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 6bf6a89..b2b8bd1 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -86,6 +86,15 @@ ListView { return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed } colorize: false + + MouseArea { + anchors.fill: parent + onClicked: popupOverlay.open(priceDetails, parent) + } + Component { + id: priceDetails + PriceDetails { } + } } AccountSyncState { diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index e2da87f..cf3635c 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -28,12 +28,12 @@ FocusScope { /** * @param sourceComponent is a Component we set on the loader. - * @param target is the visual item we position next to. + * @param target is the visual item we position next to (vertically). * @returns the item instance of the sourceComponent template */ function open(sourceComponent, target) { thePopup.palette = mainWindow.palette - thePopup.width = Math.min(root.width - 18, target.width) + thePopup.width = root.width - 18 thePopup.x = (width - thePopup.width) / 2 thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); loader.sourceComponent = sourceComponent; // last, as it starts the loading diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml new file mode 100644 index 0000000..df74e98 --- /dev/null +++ b/guis/mobile/PriceDetails.qml @@ -0,0 +1,85 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: root + implicitHeight: mainPrice.height + changes.height + 10 + + property int currentPrice: Fiat.price + function changeComparedTo(daysAgo) { + var oldPrice = Fiat.historicalPrice(daysAgo); + var percentage = oldPrice / root.currentPrice * 100 - 100; + var sign = ""; + if (percentage < 0) + sign = "↓"; + else if (percentage > 0) + sign = "↑"; + return sign + Math.abs(percentage.toFixed(2))+ "%" + } + + Rectangle { + color: "red" + anchors.right: parent.right + opacity: 0.3 + width: 20 + height: 20 + MouseArea { + anchors.fill: parent + onClicked: { + thePile.push("./CurrencySelector.qml") + popupOverlay.close(); + } + } + } + + Flowee.Label { + id: mainPrice + text: qsTr("1 BCH is: %1").arg(Fiat.formattedPrice(100000000, root.currentPrice)) + } + + Flow { + id: changes + anchors.top: mainPrice.bottom + anchors.topMargin: 10 + width: parent.width + spacing: 4 + Flowee.Label { + text: qsTr("7d") + ":"; // 7 days + } + Flowee.Label { + text: root.changeComparedTo(7); + color: text.substring(0, 1) === '↓' ? "red" : "green" + } + Flowee.Label { + text: " " + qsTr("1m") + ":"; // 30 days + } + Flowee.Label { + text: root.changeComparedTo(30); + color: text.substring(0, 1) === '↓' ? "red" : "green" + } + Flowee.Label { + text: " " + qsTr("3m") + ":"; // 90 days + } + Flowee.Label { + text: root.changeComparedTo(90); + color: text.substring(0, 1) === '↓' ? "red" : "green" + } + } +} diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index d1ee33f..dcc247b 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -166,6 +166,14 @@ int PriceDataProvider::historicalPrice(const QDateTime ×tamp) const return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch()); } +int PriceDataProvider::historicalPrice(int days) const +{ + if (days < 0 || days > 3000) + throw std::runtime_error("Invalid number of days ago"); + QDateTime now = QDateTime::currentDateTimeUtc(); + return historicalPrice(now.addDays(days * -1)); +} + QString PriceDataProvider::priceToStringSimple(int cents) const { auto value = QString::number(cents); diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index a516a8c..12774a4 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -68,6 +68,11 @@ public: */ Q_INVOKABLE int historicalPrice(const QDateTime ×tamp) const; + /** + * Return the price a certain amount of days in the past + */ + Q_INVOKABLE int historicalPrice(int days) const; + /// return a string with the given price and needed decimal separator. /// Please note that the currency indicators are not included, unlike in formattedPrice() /// \see currencySymbolPrefix() -- 2.54.0 From 28cbd5829496d15527a2e1f36bbd408ce2a572b6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 19 Feb 2023 18:53:53 +0100 Subject: [PATCH 0309/1428] Replace placeholder button with image --- guis/mobile.qrc | 2 ++ guis/mobile/PriceDetails.qml | 11 ++++++----- guis/mobile/images/settingsIcon-light.svg | 4 ++++ guis/mobile/images/settingsIcon.svg | 4 ++++ 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 guis/mobile/images/settingsIcon-light.svg create mode 100644 guis/mobile/images/settingsIcon.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 7b398c5..2c8c9eb 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -19,6 +19,8 @@ mobile/images/qr-code-light.svg mobile/images/backspace.svg mobile/images/backspace-light.svg + mobile/images/settingsIcon-light.svg + mobile/images/settingsIcon.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index df74e98..0fbfc1e 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -17,6 +17,7 @@ */ import QtQuick import "../Flowee" as Flowee +import Flowee.org.pay; Item { id: root @@ -34,12 +35,12 @@ Item { return sign + Math.abs(percentage.toFixed(2))+ "%" } - Rectangle { - color: "red" + Image { anchors.right: parent.right - opacity: 0.3 - width: 20 - height: 20 + width: 16 + height: 16 + smooth: true + source: "qrc:/settingsIcon" + (Pay.useDarkSkin ? "-light" : "") + ".svg" MouseArea { anchors.fill: parent onClicked: { diff --git a/guis/mobile/images/settingsIcon-light.svg b/guis/mobile/images/settingsIcon-light.svg new file mode 100644 index 0000000..23da2e5 --- /dev/null +++ b/guis/mobile/images/settingsIcon-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/settingsIcon.svg b/guis/mobile/images/settingsIcon.svg new file mode 100644 index 0000000..815df31 --- /dev/null +++ b/guis/mobile/images/settingsIcon.svg @@ -0,0 +1,4 @@ + + + + -- 2.54.0 From 64071e8ca81ff3ed8e6439672792478458c5fe8d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 10:11:26 +0100 Subject: [PATCH 0310/1428] i18n: Disambiguation added. --- guis/mobile/PriceDetails.qml | 8 ++++---- guis/mobile/TxInfoSmall.qml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 0fbfc1e..886a2fc 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -52,7 +52,7 @@ Item { Flowee.Label { id: mainPrice - text: qsTr("1 BCH is: %1").arg(Fiat.formattedPrice(100000000, root.currentPrice)) + text: qsTr("1 BCH is: %1", "Price of a whole bitcoin cash"). arg(Fiat.formattedPrice(100000000, root.currentPrice)) } Flow { @@ -62,21 +62,21 @@ Item { width: parent.width spacing: 4 Flowee.Label { - text: qsTr("7d") + ":"; // 7 days + text: qsTr("7d", "7 days") + ":"; // 7 days } Flowee.Label { text: root.changeComparedTo(7); color: text.substring(0, 1) === '↓' ? "red" : "green" } Flowee.Label { - text: " " + qsTr("1m") + ":"; // 30 days + text: " " + qsTr("1m", "1 month") + ":"; // 30 days } Flowee.Label { text: root.changeComparedTo(30); color: text.substring(0, 1) === '↓' ? "red" : "green" } Flowee.Label { - text: " " + qsTr("3m") + ":"; // 90 days + text: " " + qsTr("3m", "3 months") + ":"; // 90 days } Flowee.Label { text: root.changeComparedTo(90); diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 35b5f72..2949151 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -64,7 +64,7 @@ GridLayout { var rc = Pay.formatDateTime(model.date); var confirmations = Pay.headerChainHeight - root.minedHeight + 1; if (confirmations > 0 && confirmations < 100) - rc += " (" + qsTr("%1 blocks ago", confirmations).arg(confirmations) + ")"; + rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; return rc; } } -- 2.54.0 From 44131aa4df46c40101f6a46a0ebd0aa7f1f2c3b1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 10:52:58 +0100 Subject: [PATCH 0311/1428] Remove no longer used 'mock' method. --- src/PriceDataProvider.cpp | 5 ----- src/PriceDataProvider.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index dcc247b..471c6e0 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -49,11 +49,6 @@ void PriceDataProvider::start() fetch(); } -void PriceDataProvider::mock(int price) -{ - m_currentPrice.price = price; -} - void PriceDataProvider::setCurrency(const QLocale &countryLocale) { auto newCurrency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 12774a4..8b6277c 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -36,7 +36,6 @@ public: explicit PriceDataProvider(const QString &countryCode = QString(), QObject *parent = nullptr); void start(); - void mock(int price); QString currencyName() const { return m_currency; -- 2.54.0 From 5e2976e8039eeb9a49fc1c98ac8f8b5252a69987 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 10:54:21 +0100 Subject: [PATCH 0312/1428] When we load historical price, emit that change So the QML that was already loaded can use it. --- src/PriceDataProvider.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 471c6e0..be1bb65 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -267,6 +267,8 @@ void PriceDataProvider::loadPriceHistory(const QString &basedir) m_priceHistory.get(), [=](int price) { m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); }); + + emit priceChanged(lastKnownPrice); } QString PriceDataProvider::currencySymbolPrefix() const -- 2.54.0 From b828a69332173154ba7c9e349393676d05f716ef Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 11:29:39 +0100 Subject: [PATCH 0313/1428] Add BCH-unit choice to settings --- guis/mobile/GuiSettings.qml | 73 ++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 9370d3c..de63f0e 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -26,6 +26,7 @@ Page { ColumnLayout { width: parent.width + spacing: 6 Flowee.Label { text: qsTr("Font sizing") } @@ -45,7 +46,7 @@ Page { width: parent.width - 5 x: 2.5 height: 3 - color: Pay.fontScaling == target ? root.palette.highlight : root.palette.button + color: Pay.fontScaling === target ? root.palette.highlight : root.palette.button } Flowee.Label { @@ -62,5 +63,75 @@ Page { } } } + + Item { width: 1; height: 10; } // spacer + + Item { + width: parent.width + implicitHeight: Math.max(unitLabel.height, unitSelector.height) + Flowee.Label { + id: unitLabel + text: qsTr("Unit") + ":" + anchors.baseline: unitSelector.baseline + } + Flowee.ComboBox { + id: unitSelector + anchors.left: unitLabel.right + anchors.leftMargin: 6 + width: 160 + model: { + var answer = []; + for (let i = 0; i < 5; ++i) { + answer[i] = Pay.nameOfUnit(i); + } + return answer; + } + currentIndex: Pay.unit + onCurrentIndexChanged: Pay.unit = currentIndex + } + } + + Rectangle { + Layout.alignment: Qt.AlignHCenter + color: "#00000000" + radius: 6 + border.color: root.palette.button + border.width: 0.8 + + implicitHeight: units.height + 10 + implicitWidth: units.width + 10 + + GridLayout { + id: units + columns: 3 + x: 5; y: 5 + rowSpacing: 0 + Flowee.Label { + text: { + var answer = "1"; + for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { + answer += "0"; + } + return answer + " " + Pay.unitName; + } + Layout.alignment: Qt.AlignRight + } + Flowee.Label { text: "=" } + Flowee.Label { text: "1 Bitcoin Cash" } + + Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} + Flowee.Label { text: "="; visible: Pay.isMainChain} + Flowee.Label { + text: { + var amount = 1; + for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { + amount = amount * 10; + } + return Fiat.formattedPrice(amount, Fiat.price); + } + visible: Pay.isMainChain + } + } + } } } -- 2.54.0 From e78bf974c88efcc2d1606d35a8b19d4a44093117 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 12:15:49 +0100 Subject: [PATCH 0314/1428] Add currency selection to menu. --- guis/mobile/GuiSettings.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index de63f0e..3a636df 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -133,5 +133,11 @@ Page { } } } + + TextButton { + text: qsTr("Select Currency", "the word for euro/dollar/etc") + showPageIcon: true + onClicked: thePile.push("./CurrencySelector.qml") + } } } -- 2.54.0 From 5e2e4c1bc4039fef891c4fe5ecfe474c9cf3b65e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 16:20:43 +0100 Subject: [PATCH 0315/1428] Fix historical pricing data-state. --- src/PriceDataProvider.cpp | 3 +-- src/PriceHistoryDataProvider.cpp | 15 ++++++++------- src/PriceHistoryDataProvider.h | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index be1bb65..bd08eab 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -263,12 +263,11 @@ void PriceDataProvider::loadPriceHistory(const QString &basedir) if (lastKnownPrice == 0) lastKnownPrice = 10000; // if we never fetched, set to 100,- m_currentPrice.price = lastKnownPrice; + emit priceChanged(lastKnownPrice); connect (this, &PriceDataProvider::priceChanged, m_priceHistory.get(), [=](int price) { m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); }); - - emit priceChanged(lastKnownPrice); } QString PriceDataProvider::currencySymbolPrefix() const diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index 3bfa144..fd72d8c 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -60,6 +60,7 @@ PriceHistoryDataProvider::PriceHistoryDataProvider(const QString &basedir, const auto &pool = Streaming::pool(static_cast(fileSize)); input.read(pool.begin(), fileSize); data->valueBlob = pool.commit(fileSize); + data->hasBlob = true; } } } @@ -147,11 +148,6 @@ QString PriceHistoryDataProvider::currencyName() const return m_currency; } -void PriceHistoryDataProvider::setCurrency(const QString &newCurrency) -{ - m_currency = newCurrency; -} - namespace { struct Day { Day() { reset(); } @@ -233,6 +229,7 @@ void PriceHistoryDataProvider::processLog() auto count = blobData.write(data->valueBlob.begin(), data->valueBlob.size()); assert(count == data->valueBlob.size()); data->logValues.clear(); + data->hasBlob = true; data->log->open(QIODevice::WriteOnly | QIODevice::Truncate); } @@ -270,8 +267,10 @@ bool PriceHistoryDataProvider::allowLogCompression() const void PriceHistoryDataProvider::initialPopulate() { - if (m_currencies.empty()) { + auto cur = currencyData(m_currency, FetchOnly); + if (cur && !cur->hasBlob) { logCritical() << "populate!"; + cur->hasBlob = true; // avoid starting fetcher again InitialHistoryFetcher *f = new InitialHistoryFetcher(this); connect (f, &InitialHistoryFetcher::success, f, [=](const QString ¤cy) { // load this file into a currency object. @@ -313,7 +312,9 @@ void InitialHistoryFetcher::fetch(const QString &path, const QString ¤cy) req.setRawHeader("User-Agent", useragent.toLatin1()); auto reply = m_network.get(req); connect (reply, &QNetworkReply::finished, reply, [=]() { - if (reply->error() == QNetworkReply::NoError) { + // either everything went fine, or the server doesn't have our file. + // in both cases we will not retry and we create a file. + if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::ContentNotFoundError ) { QFile out(path + '/' + currency); if (out.open(QIODevice::WriteOnly)) { out.write(reply->readAll()); diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index 7c0de80..c043133 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -60,6 +60,7 @@ private: struct Currency { // a single currency and historical things we know about it. QString id; ///< the language ID (EUR/USD etc) + bool hasBlob = false; Streaming::ConstBuffer valueBlob; // a raw list of time/value pairs. /// a append-only list of time/value pairs (as stored in the log file) -- 2.54.0 From ee5d99696366b1c13d05559ca9bd6b4b8ca7b09f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 16:21:08 +0100 Subject: [PATCH 0316/1428] Tweak GUI; show current currency. --- guis/mobile/GuiSettings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 3a636df..4149517 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -135,7 +135,7 @@ Page { } TextButton { - text: qsTr("Select Currency", "the word for euro/dollar/etc") + text: qsTr("Change Currency (%1)").arg(Fiat.currencyName) showPageIcon: true onClicked: thePile.push("./CurrencySelector.qml") } -- 2.54.0 From 3ad8cba5a792013e0499849e2d79b9521e1a14a1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 16:53:23 +0100 Subject: [PATCH 0317/1428] Re-do the price details QML For less copy/paste code and more declarative design. --- guis/mobile/PriceDetails.qml | 79 ++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 886a2fc..8b26394 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -24,16 +24,6 @@ Item { implicitHeight: mainPrice.height + changes.height + 10 property int currentPrice: Fiat.price - function changeComparedTo(daysAgo) { - var oldPrice = Fiat.historicalPrice(daysAgo); - var percentage = oldPrice / root.currentPrice * 100 - 100; - var sign = ""; - if (percentage < 0) - sign = "↓"; - else if (percentage > 0) - sign = "↑"; - return sign + Math.abs(percentage.toFixed(2))+ "%" - } Image { anchors.right: parent.right @@ -55,32 +45,59 @@ Item { text: qsTr("1 BCH is: %1", "Price of a whole bitcoin cash"). arg(Fiat.formattedPrice(100000000, root.currentPrice)) } + Component { + id: historyLabel + Item { + property int days: 0 + height: buddy.height + width: buddy.width + 3 + main.width + + Flowee.Label { + id: buddy + text: title + ":" + } + Flowee.Label { + id: main + anchors.left: buddy.right + anchors.leftMargin: 3 + property double percentage: { + var oldPrice = Fiat.historicalPrice(daysAgo); + return (root.currentPrice - oldPrice) / oldPrice * 100; + } + text: { + var sign = ""; + if (percentage < 0) + sign = "↓"; + else if (percentage > 0) + sign = "↑"; + return sign + Math.abs(percentage.toFixed(1))+ "%" + } + + color: { + if (percentage > 0) + return Pay.useDarkSkin ? mainWindow.floweeGreen : "#31993d"; + return Pay.useDarkSkin ? "#ff5050" : "#c33d3d"; + } + } + } + } + Flow { id: changes anchors.top: mainPrice.bottom anchors.topMargin: 10 width: parent.width - spacing: 4 - Flowee.Label { - text: qsTr("7d", "7 days") + ":"; // 7 days - } - Flowee.Label { - text: root.changeComparedTo(7); - color: text.substring(0, 1) === '↓' ? "red" : "green" - } - Flowee.Label { - text: " " + qsTr("1m", "1 month") + ":"; // 30 days - } - Flowee.Label { - text: root.changeComparedTo(30); - color: text.substring(0, 1) === '↓' ? "red" : "green" - } - Flowee.Label { - text: " " + qsTr("3m", "3 months") + ":"; // 90 days - } - Flowee.Label { - text: root.changeComparedTo(90); - color: text.substring(0, 1) === '↓' ? "red" : "green" + spacing: 10 + Repeater { + model: ListModel { + ListElement { title: qsTr("7d", "7 days"); // 7 days + daysAgo: 7 } + ListElement { title: qsTr("1m", "1 month"); // 30 days + daysAgo: 30 } + ListElement { title: qsTr("3m", "3 months"); // 90 days + daysAgo: 90 } + } + delegate: historyLabel } } } -- 2.54.0 From 02638b45bdea0b249161d2791e40efac771d4e8d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 18:05:22 +0100 Subject: [PATCH 0318/1428] Fix alignment Just because we have two labels with the same font doesn't mean they are the same hight, especially when using special characters. --- guis/mobile/PriceDetails.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 8b26394..9e28aef 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -54,6 +54,7 @@ Item { Flowee.Label { id: buddy + anchors.baseline: main.baseline text: title + ":" } Flowee.Label { -- 2.54.0 From a00f21e4e2c576d8f8cc7640ff5bdc33232542b6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 20 Feb 2023 18:10:35 +0100 Subject: [PATCH 0319/1428] Move calculation of fees to c++ Add a property 'fees' to the TransactionInfo class and use it. This also changes the GUI behavior of the 'sent' field a little. In the GUI we now show the amount actually meant to arrive on the other address(es) instead of the amount our wallet became more empty. This makes a lot of sense if you look at your transaction and compare to an invoice, now the 'sent' field will match that invoice and it will match what the receiver actually received. --- guis/desktop/WalletTransactionDetails.qml | 13 ++----------- guis/mobile/TxInfoSmall.qml | 2 +- src/TransactionInfo.cpp | 16 +++++++++++++++- src/TransactionInfo.h | 8 +++++++- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/guis/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml index 9d5dc14..d928c80 100644 --- a/guis/desktop/WalletTransactionDetails.qml +++ b/guis/desktop/WalletTransactionDetails.qml @@ -64,7 +64,7 @@ GridLayout { } Flowee.BitcoinAmountLabel { visible: paymentTypeLabel.visible - value: model.fundsOut - model.fundsIn + value: model.fundsOut - model.fundsIn + infoObject.fees fiatTimestamp: root.minedDate } Label { @@ -74,16 +74,7 @@ GridLayout { } Flowee.BitcoinAmountLabel { visible: feesLabel.visible - value: { - if (!infoObject.createdByUs) - return 0; - var amount = model.fundsIn; - var outputs = infoObject.outputs - for (var i in outputs) { - amount -= outputs[i].value - } - return amount - } + value: infoObject.fees; fiatTimestamp: root.minedDate colorize: false } diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 2949151..d6b660a 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -92,7 +92,7 @@ GridLayout { Flowee.BitcoinAmountLabel { Layout.fillWidth: true visible: paymentTypeLabel.visible - value: model.fundsOut - model.fundsIn + value: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) fiatTimestamp: model.date showFiat: false // might not fit } diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index c78becd..bbeb064 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,20 @@ int TransactionInfo::txSize() const return m_txSize; } +double TransactionInfo::fees() const +{ + qint64 fees = 0; + if (m_createdByUs) { + for (const auto i : m_inputs) { + fees += i->value(); + } + for (const auto o : m_outputs) { + fees -= o->value(); + } + } + return static_cast(fees); +} + QList TransactionInfo::outputs() const { QList answer; diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index c567f63..88a1bdd 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -84,6 +84,11 @@ class TransactionInfo : public QObject { Q_OBJECT Q_PROPERTY(int size READ txSize CONSTANT) + /** + * Return the amount of fees paid for this transaction. + * This returns zero if we didn't create the transaction. + */ + Q_PROPERTY(double fees READ fees CONSTANT) Q_PROPERTY(QList inputs READ inputs CONSTANT) Q_PROPERTY(QList outputs READ outputs CONSTANT) Q_PROPERTY(QString userComment READ userComment CONSTANT) @@ -95,6 +100,7 @@ public: explicit TransactionInfo(QObject *parent = nullptr); int txSize() const; + double fees() const; QList outputs() const; QList inputs() const; const QString &userComment() const; -- 2.54.0 From 15da1acc19813c376560f3cad8c745ef87cc5f1d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 15:50:47 +0100 Subject: [PATCH 0320/1428] Make i18n target include all the cpp sources --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7332f3..39184f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,7 +92,7 @@ if(NOT ANDROID) add_custom_target(i18n COMMAND lupdate guis/desktop.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts - COMMAND lupdate ${PAY_SOURCES} guis/widgets.qrc + COMMAND lupdate src guis/widgets.qrc -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts COMMAND lupdate guis/mobile.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -- 2.54.0 From f7c1d100eeea1b03d899aaf643628a3af1a60c8e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 15:51:40 +0100 Subject: [PATCH 0321/1428] Update crowdin and do my translations --- translations/floweepay-common_nl.ts | 270 ++++++++-- translations/floweepay-desktop_nl.ts | 727 +++++++++++-------------- translations/floweepay-mobile_nl.ts | 773 ++++++++++++++++++++++++++- 3 files changed, 1306 insertions(+), 464 deletions(-) diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 7d4a1b6..35d96bb 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,68 +4,137 @@ AccountInfo - - - Up to date - Is volledig bijgewerkt + + Wallet: Up to date + Portemonnee: gesynchroniseerd - - %1 weeks behind + + Behind: %1 weeks, %2 days + counter on weeks - Een week achter - %1 weken achter + Achter %1 week, %2 dagen + Achter %1 weken, %2 dagen - - %1 days behind + + Behind: %1 days - Een dag achter - %1 dagen achter + Achter %1 dag + Achter %1 dagen - + + Up to date + Is volledig bijgewerkt + + + Updating - Bijwerken + Aan het bijwerken - - %1 hours behind + + Still %1 hours behind - Een uur achter - %1 uren achter + Nog één uur + Nog %1 uren achter + + AccountTypeLabel + + + This wallet is a single-address wallet. + Deze portemonnee heeft één enkel adres. + + + + This wallet is based on a HD seed-phrase + Dit is een portemonnee gebaseerd op een HD-herstelzin + + + + This wallet is a simple multiple-address wallet. + Dit is een eenvoudige portemonnee met diverse adressen. + + + + BroadcastFeedback + + + Sending Payment + Betaling wordt verzonden + + + + Payment Sent + Betaling Verzonden + + + + Transaction rejected by network + Transactie afgewezen door het netwerk + + + + Add a personal note + Voeg een persoonlijke notitie toe + + + + Copied TXID to clipboard + Gekopieerd TXID naar klipbord + + + + Opening Website + Open Website + + + + Close + Sluiten + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + Munt is gefuseerd voor verhoogde anonimiteit + + FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -74,13 +143,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -90,37 +159,74 @@ - NotificationManager + LabelWithClipboard - - BCH block mined %1 - BCH blok gedolven %1 + + Copy + Kopieer + + + + MenuModel + + + Settings + Instellingen - + + Wallet Information + Portemonnee Informatie + + + + Network Details + Netwerk Details + + + + About + Over Ons + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash-blok gemijnd. Hoogte: %1 + + + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) blok gemijnd: %1 + + + Mute Negeren - - New Transaction + + New Transactions + dialog-title Nieuwe transactie Nieuwe transacties - + %1 new transactions across %2 wallets found (%3) %1 nieuwe transacties in %2 portemonnees gevonden (%3) - + A payment of %1 has been sent Een betaling van %1 is verzonden - + %1 new transactions found (%2) %1 nieuwe transactie gevonden (%2) @@ -131,31 +237,39 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. + + QRWidget + + + Copied to clipboard + Naar klembord gekopieerd + + Wallet - - + + Change #%1 Wisselmunt #%1 @@ -163,12 +277,12 @@ WalletCoinsModel - + Unconfirmed Onbevestigd - + %1 hours age, like: hours old @@ -177,36 +291,88 @@ - + %1 days age, like: days old - een dag + %1 dag %1 dagen - + %1 weeks age, like: weeks old - één week + %1 week %1 weken - + %1 months age, like: months old - één maand + %1 maand %1 maanden - + Change #%1 Wisselmunt #%1 + + WalletHistoryModel + + + Today + Vandaag + + + + Yesterday + Gisteren + + + + Earlier this week + Eerder deze week + + + + This week + Deze week + + + + Earlier this month + Eerder deze maand + + + + WalletSecretsView + + + + Copy Address + Kopieer adres + + + + Copy Private Key + Kopieer privésleutel + + + + Coins: %1 / %2 + Munten: %1 / %2 + + + + Signed with Schnorr signatures in the past + In het verleden ondertekend met Schnorr + + diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 6b64202..abd2e1b 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -2,209 +2,155 @@ - AccountDetails + AccountConfigMenu - - Wallet Details - Portemonnee Details - - - - Name - Naam - - - - Encryption - Encryptie - - - - Password - Wachtwoord - - - - Open - Open - - - - Invalid PIN - Ongeldige PIN - - - - Sync Status - Synchronisatie status - - - - This wallet is a single-address wallet. - Deze portemonnee heeft één enkel adres. - - - - This wallet is based on a HD seed-phrase - Dit is een portemonnee gebaseerd op een HD-herstelzin - - - - This wallet is a simple multiple-address wallet. - Dit is een eenvoudige portemonnee met diverse adressen. - - - - Address List - Adreslijst - - - - Change Addresses - Wisselgeldadres - - - - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - - - - Used Addresses - Gebruikte adressen - - - - Switches between unused and used Bitcoin addresses - Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - - - - Coins: %1 / %2 - Munten: %1 / %2 - - - - Signed with Schnorr signatures in the past - In het verleden ondertekend met Schnorr - - - - Copy Address - Kopieer adres - - - - Copy Private Key - Kopieer privésleutel - - - - Backup details - Back-up details - - - - Seed-phrase - Herstelzin - - - - Derivation - Derivatie - - - - - Copy - Kopieer - - - - Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - - - - <b>Important</b>: Never share your seed-phrase with others! - <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - - - - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. - - - - AccountListItem - - - Last Transaction - Laatste Transactie - - - + Details Details - + Unarchive - Dearchiveren + Uit archief halen - + Archive Wallet Portemonnee Archiveren - + Make Primary Maak primair - + ★ Primary ★ Primair - + Protect With Pin... Bescherm met Pin... - + Open Open encrypted wallet Open - + Close Close encrypted wallet Sluiten - CashFusionIcon + AccountDetails - - Coin has been fused for increased anonymity - Munt is gefuseerd voor verhoogde anonimiteit + + Wallet Details + Portemonnee Details + + + + Name + Naam + + + + Sync Status + Synchronisatie status + + + + Encryption + Encryptie + + + + Password + Wachtwoord + + + + Open + Open + + + + Invalid PIN + Ongeldige PIN + + + + Address List + Adreslijst + + + + Change Addresses + Wisselgeldadres + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. + + + + Used Addresses + Gebruikte adressen + + + + Switches between unused and used Bitcoin addresses + Schakelt tussen ongebruikt en gebruikte Bitcoin adressen + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. + + + + Backup details + Back-up details + + + + Seed-phrase + Herstelzin + + + + Derivation + Derivatie + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - LabelWithClipboard + AccountListItem - - Copy Address - Kopieer adres + + Last Transaction + Laatste Transactie NetView - + Peers (%1) Peer (%1) @@ -212,48 +158,48 @@ - + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - + initializing connection verbinding initialiseren - + Verifying peer Peer verifiëren - + Assigning... Toewijzen... - + Peer for wallet: %1 Peer voor portemonnee: %1 - + Peer for initial wallet Peer voor initiële portemonnee - + Close Sluiten @@ -261,32 +207,32 @@ NewAccountCreateBasicAccount - + This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden - + Name Naam - + Go Start - + Advanced Options Geavanceerde Opties - + Force Single Address Forceer één adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. @@ -296,27 +242,27 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountCreateHDAccount - + This creates a new empty wallet with smart creation of addresses from a single seed-phrase Dit maakt een nieuwe lege portemonnee die slim adressen maakt van één enkele herstelzin - + Name Naam - + Go Start - + Advanced Options Geavanceerde Opties - + Derivation Derivatie @@ -324,79 +270,79 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - + Secret The seed-phrase or private key Geheim - + Example: %1 placeholder text Voorbeeld: %1 - + Name Naam - + Private key description of type Privé-sleutel - + BIP 39 seed-phrase description of type BIP 39 herstelzin - + Unrecognized word Word from the seed-phrases lexicon Niet herkend woord - + Import wallet Portemonnee importeren - + Advanced Options Geavanceerde Opties - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Alternate phrase Alternatieve zin - + Start Height Beginhoogte - + Derivation Derivatie @@ -404,83 +350,83 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. NewAccountPane - + New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee - + Basic Eenvoudig - + Private keys based Property of a wallet Privé sleutels gebaseerd - + Difficult to backup Context: wallet type Back-up moeilijk - + Great for brief usage Context: wallet type Ideaal voor kort gebruik - + HD wallet HD wallet - + Seed-phrase based Context: wallet type Herstelzin gebaseerd - + Easy to backup Context: wallet type Back-upt makkelijk - + Most compatible The most compatible wallet type Meest compatibel - + Import Importeer - + Imports seed-phrase Importeert herstelzin - + Imports private key Importeert Privésleutel - + Basic wallet with private keys Basis portemonnee met privésleutels - + Seed-phrase wallet Herstelzin portemonnee - + Import of an existing wallet Importeer een bestaande portemonnee @@ -488,17 +434,17 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. PaymentTweakingPanel - + Add Detail Details toevoegen - + Coin Selector Muntselectie - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. De standaard selectie van munten die een transactie betalen kunt u veranderen, u kunt een eigen 'Muntselectie' selecteren waar de munten van deze portemonnee beschikbaar zijn. @@ -506,73 +452,68 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. ReceiveTransactionPane - + Share your QR code or copy address to receive Deel uw QR code of kopieer het adres waarop u Bitcoin Cash kan ontvangen - - Copied to clipboard - Gekopieerd naar klembord - - - + Checking Controleren - + Transaction high risk Transactie met hoog risico - + Payment Seen Betaling gezien - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (dubbel uitgegeven bewijs ontvangen) - + Description Omschrijving - + Amount Bedrag - + Remember payment request Onthoud - + Clear Wissen - + Done Klaar - + Delete Verwijder @@ -580,177 +521,146 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. SendTransactionPane - - Destination - Bestemming - - - - Max available - The maximum balance available - Max. beschikbaar - - - - %1 to %2 - summary text to pay X-euro to address M - %1 aan %2 - - - - Enter Bitcoin Cash Address - Voer Bitcoin Cash adres in - - - - Amount - Bedrag - - - - Max - Max - - - - Prepare - Bereid voor - - - + Add Destination Voeg bestemming toe - + + Prepare + Bereid voor + + + Transaction Details Transactiedetails - + Not prepared yet Nog niet voorbereid - + Fee Transactiekosten - + Transaction size Transactie grootte - + %1 bytes %1 bytes - + Fee per byte Transactiekosten per byte - + %1 sat/byte fee %1 sat/byte - + Send Verstuur - + Cancel Afbreken - - Transaction rejected by network - Transactie afgewezen door het netwerk - - - - Payment Sent - Betaling Verzonden - - - - + Copy transaction-ID Kopieer transactie-ID - + Confirm delete Verwijderen bevestigen - + Do you really want to delete this detail? Wilt u dit detail echt wissen? - + Enter your PIN Voer uw PIN in - - Your payment can be found by its identifyer: %1 - Uw betaling kan worden gevonden onder ID: %1 + + Destination + Bestemming - - Copy - Kopieer + + Max available + The maximum balance available + Max. beschikbaar - - Internet - Op Internet + + %1 to %2 + summary text to pay X-euro to address M + %1 aan %2 - - Comment - Opmerking + + Enter Bitcoin Cash Address + Voer Bitcoin Cash adres in - - Close - Sluiten + + Copy Address + Kopieer adres - + self payment to self deze - + + Amount + Bedrag + + + + Max + Max + + + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -759,53 +669,53 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt @@ -813,42 +723,47 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. SettingsPane - + Settings Instellingen - + Unit Eenheid - + Show Block Notifications Toon blok notificaties - + When a new block is mined, Flowee Pay shows a desktop notification Wanneer een nieuw blok is gedolven, toont Flowee Pay een desktop notificatie - + Night Mode Nachtmodus - + Version Versie - + Library Version Bibliotheek versie - + + Synchronization + Synchronisatie + + + Network Status Netwerk Status @@ -856,124 +771,124 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletEncryption - + Protect your wallet with a password Beveilig uw portomonee met een wachtwoord - + Pin to Pay - Pin to Pay + PIN bij betalen - + Protect your funds pin to pay Bescherm uw geld - + Fully open, except for sending funds pin to pay Volledig open, behalve voor het verzenden van geld - + Keeps in sync pin to pay Synchroniseert - + Pin to Open - Pin to Open + PIN bij openen - + Protect your entire wallet pin to open Bescherm uw gehele portemonnee - + Balance and history protected pin to open Saldo en geschiedenis beveiligd - + Requires Pin to view, sync or pay pin to open Vereist Pin om te bekijken, synchroniseren of betalen - + Make "%1" wallet require a pin to pay Verplicht een Pin voor betaling op portemonnee "%1" - + Make "%1" wallet require a pin to open Verplicht een Pin om portemonnee "%1" te openen - + Wallet already has pin to pay applied Portomonee heeft al Pin to Pay - - + + Wallet already has pin to open applied Portomonee heeft al Pin to Open - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Uw portemonnee zal gedeeltelijk versleuteld worden en betalen zal onmogelijk worden zonder een wachtwoord. Als u geen backup heeft van deze portemonnee, maak er dan eerst een. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Uw volledige portemonnee wordt versleuteld, bij openen heeft u een wachtwoord nodig. Als u geen backup heeft van deze portemonnee, maak er eerst een. - + Password Wachtwoord - + Wallet Portemonnee - + Encrypt Versleutelen - + Invalid password to open this wallet Ongeldig wachtwoord om deze portemonnee te openen - + Close Sluiten - + Repeat password Herhaal wachtwoord - + Please confirm the password by entering it again Bevestig het wachtwoord door het opnieuw in te voeren - + Typed passwords do not match Wachtwoorden komen niet overeen @@ -981,17 +896,17 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletEncryptionStatus - + Pin to Pay - Pin to Pay + PIN bij betalen - + Pin to Open - Pin to Open + PIN bij openen - + (Opened) Wallet is decrypted (Geopend) @@ -1000,37 +915,37 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletTransaction - + Miner Reward Miner Beloning - + Cash Fusion Cash Fusion - + Received Ontvangen - + Moved Zelf-betaling - + Sent Verzonden - + rejected geweigerd - + unconfirmed onbevestigd @@ -1038,27 +953,27 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletTransactionDetails - + Copy transaction-ID Kopieer transactie-ID - + Status Status - + rejected geweigerd - + unconfirmed onbevestigd - + %1 confirmations (mined in block %2) %1 bevestiging (gedolven in blok %2) @@ -1066,22 +981,22 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Copy block height Blokhoogte kopiëren - + Fees Kosten - + Size Grootte - + %1 bytes %1 byte @@ -1089,18 +1004,18 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Inputs In - - + + Copy Address Kopieer adres - + Outputs Uitgaand @@ -1108,105 +1023,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - - Total balance - Totale saldo + + Offline + Offline - + Balance Saldo - - Show Wallet Details - Toon portemonnee details - - - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - - Offline - Offline - - - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1215,7 +1120,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 9e73950..a05e0d5 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -1,4 +1,775 @@ - + + + About + + + About + Over Ons + + + + Help translate this app + Help deze app te vertalen + + + + License + Licentie + + + + + Credits + Met dank aan + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander en bijdragers + + + + Project Home + Startpagina project + + + + With git repository and issues tracker + Met git data en takenlijst + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Start + + + + Scheduled + Gepland + + + + Miner Reward + Mijnwerker Beloning + + + + Cash Fusion + Cash Fusion + + + + Received + Ontvangen + + + + Moved + Zelf-betaling + + + + Sent + Verzonden + + + + Sending + Verzenden + + + + Seen + Gezien + + + + Rejected + Geweigerd + + + + AccountPageListItem + + + Sync Status + Synchronisatie status + + + + Primary Wallet + Primaire portemonnee + + + + Backup information + Back-up Informatie + + + + Backup Details + Back-up gegevens + + + + Wallet seed-phrase + Herstelzin opslaan + + + + Derivation Path + Derivatie pad + + + + Starting Height + height refers to block-height + Beginhoogte + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! + + + + Wallet keys + Sleutels van portemonnee + + + + Change Addresses + Wisselgeldadres + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. + + + + Used Addresses + Gebruikte adressen + + + + Switches between unused and used Bitcoin addresses + Schakelt tussen ongebruikt en gebruikte Bitcoin adressen + + + + Addresses and Keys + Adressen en sleutels + + + + AccountSelector + + + Your Wallets + Uw portemonnees + + + + last active + laatst actief + + + + AccountSyncState + + + Status: Offline + Status: offline + + + + AccountsList + + + Wallet Information + Portemonnee Informatie + + + + CurrencySelector + + + Select Currency + Selecteer valuta + + + + GuiSettings + + + Display Settings + Scherminstellingen + + + + Font sizing + Lettertypegrootte + + + + Unit + Eenheid + + + + Change Currency (%1) + Verander valuta (%1) + + + + ImportWalletPage + + + Import Wallet + Portemonnee importeren + + + + Create + Creëer + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. + + + + Secret + The seed-phrase or private key + Geheim + + + + Private key + description of type + Privé-sleutel + + + + BIP 39 seed-phrase + description of type + BIP 39 herstelzin + + + + Unrecognized word + Word from the seed-phrases lexicon + Niet herkend woord + + + + Name + Naam + + + + Force Single Address + Forceer één adres + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. +Wisselgeld gaat weer naar de geïmporteerde sleutel. + + + + Alternate phrase + Alternatieve zin + + + + Oldest Transaction + Oudste transactie + + + + Derivation + Derivatie + + + + MenuOverlay + + + Add Wallet + Portemonnee toevoegen + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adres + + + + Start-height: %1 + Beginhoogte: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + verbinding initialiseren + + + + Verifying peer + Peer verifiëren + + + + Peer for wallet + Peer voor portemonnee + + + + Peer for wallet: %1 + Peer voor portemonnee: %1 + + + + NewAccount + + + New Bitcoin Cash Wallet + Nieuwe Bitcoin Cash Portemonnee + + + + Next + Volgende + + + + Create a New Wallet + Maak een nieuwe portemonnee + + + + Basic + Eenvoudig + + + + Private keys based + Property of a wallet + Privé sleutels gebaseerd + + + + Difficult to backup + Context: wallet type + Back-up moeilijk + + + + Great for brief usage + Context: wallet type + Ideaal voor kort gebruik + + + + HD wallet + HD portemonnee + + + + Seed-phrase based + Context: wallet type + Herstelzin gebaseerd + + + + Easy to backup + Context: wallet type + Back-upt makkelijk + + + + Most compatible + The most compatible wallet type + Meest compatibel + + + + Import Existing Wallet + Importeer bestaande portemonnee + + + + Import + Importeer + + + + Imports seed-phrase + Importeert herstelzin + + + + Imports private key + Importeert Privésleutel + + + + New Wallet + Nieuwe Portemonnee + + + + + Create + Creëer + + + + This creates a new empty wallet with simple multi-address capability + Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden + + + + + Name + Naam + + + + Force Single Address + Forceer één adres + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. +Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + + + + Derivation + Derivatie + + + + New HD-Wallet + Nieuwe Portemonnee + + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + Dit maakt een nieuwe lege portemonnee die slim adressen maakt van één enkele herstelzin + + + + PayWithQR + + + Approve Payment + Betaling goedkeuren + + + + Send All + all money in wallet + Alles verzenden + + + + Payment description + Omschrijving betaling + + + + All Currencies + Alle valuta's + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + ReceiveTab + + + Receive + Ontvangen + + + + Share this QR to receive + Betaler moet deze QR lezen + + + + Checking + Verifiëren + + + + Transaction high risk + Transactie met hoog risico + + + + Payment Seen + Betaling gezien + + + + Payment Accepted + Betaling geaccepteerd + + + + Payment Settled + Betaling Afgewikkeld + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) + + + + Description + Omschrijving + + + + Clear + Wissen + + + + Done + Klaar + + + + SendTransactionsTab + + + Send + Versturen + + + + Scan a QR code + Scan QR-code + + + + SlideToApprove + + + SLIDE TO SEND + SWIPE VOOR VERZENDEN + + + + StartupScreen + + + Welcome! + Welkom! + + + + Continue + Doorgaan + + + + Moving the world towards a Bitcoin Cash economy + We lopen het pad naar een Bitcoin Cash economie + + + + I already have a wallet + Ik heb al een portemonnee + + + + OR + OF + + + + I want to send funds to my new wallet + Ik wil geld naar mijn nieuwe portemonnee sturen + + + + TransactionDetails + + + Transaction Details + Transactiedetails + + + + Rejected + Afgewezen + + + + Unconfirmed + Onbevestigd + + + + Mined + Gedolven + + + + Coinbase + Coinbase + + + + CashFusion transaction + CashFusion transactie + + + + Size + Grootte + + + + %1 bytes + + %1 bytes + %1 bytes + + + + + TxInfoSmall + + + Transaction is rejected + Transactie geweigerd + + + + Processing + In behandeling + + + + Mined + Gedolven + + + + %1 blocks ago + Confirmations + + %1 blok geleden + %1 blokken geleden + + + + + Miner Reward + Mijnwerker Beloning + + + + Cash Fusion + Cash Fusion + + + + Received + Ontvangen + + + + Moved + Zelf-betaling + + + + Sent + Verzonden + + + + Value then + Waarde toen + + + + Value now + Waarde nu + + + + Transaction Details + Transactiedetails + + -- 2.54.0 From fadf247838f67abb8377a48098ac5659aa95ae3d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 15:52:16 +0100 Subject: [PATCH 0322/1428] Add context to translation --- src/NotificationManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 7b46d03..54e7eb3 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -175,7 +175,7 @@ void NotificationManager::walletUpdated() args << QVariant("Flowee Pay"); // app-name args << QVariant(m_newFundsNotificationId); // replaces-id args << QString(); // app_icon (not needed since we say which desktop file we are) - args << tr("New Transaction", "", txCount); + args << tr("New Transactions", "dialog-title", txCount); const auto gained = deposited - spent; auto pricesOracle = FloweePay::instance()->prices(); -- 2.54.0 From a8ecf888d61a83c63ab447f0066f51c5ca65669e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 15:52:33 +0100 Subject: [PATCH 0323/1428] fix i18n string --- guis/mobile/PayWithQR.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index a42f5eb..048248c 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -123,7 +123,7 @@ Page { Flowee.Label { id: commentLabel - text: qsTr("Payment description" + ":") + text: qsTr("Payment description") + ":" visible: userComment.text !== "" y: 100 } -- 2.54.0 From 018a27ceb04dd41e6124ae3dc12a867e9bbb0432 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 16:40:46 +0100 Subject: [PATCH 0324/1428] cleanup; use local palette In Qt5 the palette was introduced in the Control object (part of Qt- Quick-Controls-2). In Qt6 this property was moved to the superclass 'Item'. This means that we no longer need to refer to a control when using a palette, every single thing in QtQuick is an Item, afterall. --- guis/Flowee/CardTypeSelector.qml | 4 ++-- guis/Flowee/CheckBox.qml | 12 ++++++------ guis/Flowee/HamburgerMenu.qml | 2 +- guis/Flowee/ImageButton.qml | 4 ++-- guis/Flowee/Label.qml | 2 +- guis/Flowee/LabelWithCursor.qml | 2 +- guis/Flowee/MultilineTextField.qml | 8 ++++---- guis/Flowee/ScrollThumb.qml | 4 ++-- guis/Flowee/TextField.qml | 4 ++-- guis/Flowee/WalletSecretsView.qml | 4 ++-- guis/desktop/AccountListItem.qml | 2 +- guis/desktop/NetView.qml | 2 +- guis/desktop/NewAccountPane.qml | 2 +- guis/desktop/PaymentTweakingPanel.qml | 10 +++++----- guis/desktop/ReceiveTransactionPane.qml | 4 ++-- guis/desktop/SendTransactionPane.qml | 12 ++++++------ guis/desktop/SettingsPane.qml | 2 +- guis/desktop/WalletTransaction.qml | 2 +- guis/desktop/main.qml | 12 ++++++------ guis/mobile/AccountHistory.qml | 10 +++++----- guis/mobile/AccountSelector.qml | 8 ++++---- guis/mobile/CurrencySelector.qml | 2 +- guis/mobile/GuiSettings.qml | 4 ++-- guis/mobile/MainViewBase.qml | 10 +++++----- guis/mobile/MenuOverlay.qml | 8 ++++---- guis/mobile/NetView.qml | 2 +- guis/mobile/Page.qml | 4 ++-- guis/mobile/PayWithQR.qml | 10 +++++----- guis/mobile/PopupOverlay.qml | 4 ++-- guis/mobile/QRScannerOverlay.qml | 2 +- guis/mobile/ReceiveTab.qml | 4 ++-- guis/mobile/SlideToApprove.qml | 8 ++++---- guis/mobile/StartupScreen.qml | 4 ++-- guis/mobile/TxInfoSmall.qml | 2 +- guis/mobile/VisualSeparator.qml | 2 +- 35 files changed, 89 insertions(+), 89 deletions(-) diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index 45eaf87..bb2d522 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -29,13 +29,13 @@ Item { Rectangle { color: { - var base = name.palette.base + var base = palette.base if (Pay.useDarkSkin) return parent.selected ? Qt.lighter(base, 1.4) : Qt.darker(base, 0.9) return parent.selected ? Qt.darker(base, 1.04) : Qt.darker(base, 1.1) } border.width: parent.selected ? 5 : 0.8 - border.color: parent.selected ? name.palette.mid : name.palette.alternateBase + border.color: parent.selected ? palette.mid : palette.alternateBase width: parent.width height: parent.selected ? parent.height : parent.height - 50 radius: 10 diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 812159e..58b68af 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -41,10 +41,10 @@ T.CheckBox { radius: parent.height / 3 color: { if (control.sliderOnIndicator && control.enabled && control.checked) - return control.palette.midlight - return control.palette.window + return palette.midlight + return palette.window } - border.color: control.activeFocus ? control.palette.highlight : control.palette.button + border.color: control.activeFocus ? palette.highlight : palette.button border.width: 0.8 Behavior on color { ColorAnimation {}} @@ -59,8 +59,8 @@ T.CheckBox { if (!control.enabled) return "darkgray" if (control.checked && Pay.useDarkSkin) - return control.palette.windowText; - return control.palette.highlight + return palette.windowText; + return palette.highlight } Behavior on x { NumberAnimation {}} Behavior on color { ColorAnimation {}} @@ -85,7 +85,7 @@ T.CheckBox { anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 16 radius: width - color: q.palette.windowText + color: palette.windowText Label { id: q text: "?" diff --git a/guis/Flowee/HamburgerMenu.qml b/guis/Flowee/HamburgerMenu.qml index c0a0139..d14076c 100644 --- a/guis/Flowee/HamburgerMenu.qml +++ b/guis/Flowee/HamburgerMenu.qml @@ -22,7 +22,7 @@ Item { implicitWidth: root.wide ? 12 : 4 implicitHeight: 16 property bool wide: false - property color color: mainWindow.palette.windowText + property color color: palette.windowText Column { spacing: 3 diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index 3fbbf40..d953ea7 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -56,9 +56,9 @@ QQC2.Control { Rectangle { id: feedbackPopup radius: 6 - color: mainWindow.palette.window + color: palette.window border.width: 2 - border.color: mainWindow.palette.highlight + border.color: palette.highlight visible: false width: comment.width + 12 height: comment.height + 12 diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml index fb33067..9e8e968 100644 --- a/guis/Flowee/Label.qml +++ b/guis/Flowee/Label.qml @@ -20,5 +20,5 @@ import QtQuick.Controls as QQC2 QQC2.Label { // With Qt6.4 on Android, this extra line is needed to // get the label to follow the app-color-style - color: mainWindow.palette.windowText + color: palette.windowText } diff --git a/guis/Flowee/LabelWithCursor.qml b/guis/Flowee/LabelWithCursor.qml index f28f227..638774f 100644 --- a/guis/Flowee/LabelWithCursor.qml +++ b/guis/Flowee/LabelWithCursor.qml @@ -67,7 +67,7 @@ Item { return cutPoint <= parent.stringLength return cutPoint < parent.stringLength } - color: cursorVisible ? begin.palette.windowText : "#00000000" + color: cursorVisible ? palette.windowText : "#00000000" property bool cursorVisible: true Timer { id: blinkingCursor diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 32e5893..35dd7c2 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -75,9 +75,9 @@ QQC2.Control { width: parent.width - 10 height: parent.height - 10 activeFocusOnTab: true - color: showingPlaceholder ? Qt.darker(root.palette.text, Pay.useDarkSkin ? 1.6 : 0.65) : root.palette.text - selectedTextColor: root.palette.highlightedText - selectionColor: root.palette.highlight + color: showingPlaceholder ? Qt.darker(palette.text, Pay.useDarkSkin ? 1.6 : 0.65) : palette.text + selectedTextColor: palette.highlightedText + selectionColor: palette.highlight selectByMouse: true wrapMode: TextEdit.Wrap @@ -111,7 +111,7 @@ QQC2.Control { background: Rectangle { color: "#00000000" - border.color: textEdit.activeFocus ? root.palette.highlight : root.palette.mid + border.color: textEdit.activeFocus ? palette.highlight : palette.mid border.width: 0.8 } } diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index 7154b0f..1c6f493 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -71,14 +71,14 @@ QQC2.ScrollBar { Repeater { model: 3 delegate: Rectangle { - color: root.palette.light + color: palette.light width: column.width height: 2 radius: 1 } } } - color: thumbInput.engaged ? root.palette.highlight : root.palette.dark + color: thumbInput.engaged ? palette.highlight : palette.dark Timer { running: thumbRect.open && !thumbRect.moving diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index aab529d..c31cc36 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -34,12 +34,12 @@ QQC2.TextField { radius: 3 color: { if (root.enabled) - return root.palette.base; + return palette.base; return "#00000000"; } border.color: { if (root.enabled) - return root.activeFocus ? root.palette.highlight : root.palette.button + return root.activeFocus ? palette.highlight : palette.button return "transparant"; } border.width: 0.8 diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 132f160..d052952 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -26,7 +26,7 @@ ListView { model: root.account.secrets delegate: Rectangle { id: delegateRoot - color: (index % 2) == 0 ? root.palette.base : root.palette.alternateBase + color: (index % 2) == 0 ? palette.base : palette.alternateBase width: ListView.view.width height: addressLabel.height + 6 + amountLabel.height + 6 + (lineCount === 3 ? coinCountLabel.height + 6: 0) + 12 @@ -73,7 +73,7 @@ ListView { Repeater { // hamburger model: 3 delegate: Rectangle { - color: root.palette.windowText + color: palette.windowText width: 12 height: 3 radius: 2 diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 5836cfe..3190062 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -35,7 +35,7 @@ Item { id: background property bool hover: false radius: 7 - color: selected ? mainWindow.palette.light : mainWindow.palette.window + color: selected ? palette.light : palette.window border.width: 1.5 border.color: { if (portfolio.current === account) diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index cb2547b..36d0f81 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -50,7 +50,7 @@ ApplicationWindow { delegate: Rectangle { width: listView.width height: peerPane.height + 12 - color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base + color: index % 2 === 0 ? palette.button : palette.base ColumnLayout { id: peerPane width: listView.width - 20 diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index c4ca381..aeae48f 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -34,7 +34,7 @@ FocusScope { } } Rectangle { - color: mainWindow.palette.window + color: palette.window anchors.fill: contentArea anchors.margins: -10 // have an inset MouseArea { anchors.fill: parent } diff --git a/guis/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml index 6133409..682dcf5 100644 --- a/guis/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -53,7 +53,7 @@ Item { radius: width / 2 width: priv.collapsed ? 70 : priv.width * 2.9 height: priv.collapsed ? 70 : priv.height * 2.9 - color: priv.collapsed ? mainWindow.palette.windowText : mainWindow.palette.window + color: priv.collapsed ? palette.windowText : palette.window Behavior on color { ColorAnimation { duration: 400 } } Behavior on width { NumberAnimation { duration: 400 } } @@ -88,9 +88,9 @@ Item { anchors.topMargin: 10 x: 10 - color: helpText.palette.light + color: palette.light border.width: 2 - border.color: helpText.palette.shadow + border.color: palette.shadow Flowee.Button { id: inputSelectorButton @@ -141,7 +141,7 @@ Item { // the two lines that make up the "+" icon Rectangle { opacity: priv.collapsed ? 1 : 0 - color: mainWindow.palette.window + color: palette.window width: 17 height: 3 x: 6 @@ -149,7 +149,7 @@ Item { } Rectangle { opacity: priv.collapsed ? 1 : 0 - color: mainWindow.palette.window + color: palette.window width: 3 height: 17 y: 6 diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index e64b2f8..0f25351 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -72,7 +72,7 @@ Pane { color: { var state = qr.request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) - return receivePane.palette.base + return palette.base if (state === PaymentRequest.DoubleSpentSeen) return "#640e0f" // red return "#3e8b4e" // in all other cases: green @@ -81,7 +81,7 @@ Pane { } GradientStop { position: 0.1 - color: receivePane.palette.base + color: palette.base } } opacity: qr.request.state === PaymentRequest.Unpaid ? 0: 1 diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 9e415a3..a95ff87 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -34,7 +34,7 @@ Item { } Rectangle { // background anchors.fill: parent - color: mainWindow.palette.window + color: palette.window } Flickable { id: contentArea @@ -79,8 +79,8 @@ Item { width: 32 height: 32 visible: modelData.collapsable && !modelData.collapsed - color: mouseArea.containsMouse ? mainWindow.palette.button : mainWindow.palette.window - border.color: mainWindow.palette.button + color: mouseArea.containsMouse ? palette.button : palette.window + border.color: palette.button Image { source: "qrc:/edit-delete.svg" @@ -317,7 +317,7 @@ Item { if (!activeFocus && text !== "" && !addressOk) color = Pay.useDarkSkin ? "#ff6568" : "red" else - color = mainWindow.palette.windowText + color = palette.windowText } } Label { @@ -412,7 +412,7 @@ Item { Rectangle { anchors.fill: warningColumn anchors.margins: -7 - color: warning.palette.window + color: palette.window border.width: 2 border.color: "red" radius: 10 @@ -543,7 +543,7 @@ Item { delegate: Rectangle { width: ListView.view.width - 5 height: mainText.height + ageLabel.height + 12 - color: index %2 == 0 ? mainText.palette.alternateBase : mainText.palette.base + color: index %2 == 0 ? palette.alternateBase : palette.base Rectangle { id: lockedRect diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index b3c43a4..edfed75 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -51,7 +51,7 @@ Pane { Rectangle { color: "#00000000" radius: 6 - border.color: mainWindow.palette.button + border.color: palette.button border.width: 0.8 implicitHeight: units.height + 10 diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index b78a6b0..948fd70 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -28,7 +28,7 @@ Rectangle { return rc; } width: mainLabel.width + bitcoinAmountLabel.width + 30 - color: (index % 2) == 0 ? mainLabel.palette.light : mainLabel.palette.alternateBase + color: (index % 2) == 0 ? palette.light : palette.alternateBase property bool isRejected: model.height === -2 // -2 is the magic block-height indicating 'rejected' diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index e8be6a8..f002104 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -172,7 +172,7 @@ ApplicationWindow { Rectangle { anchors.fill: parent anchors.margins: -10 - color: mainWindow.palette.light + color: palette.light radius: 10 } Column { @@ -299,7 +299,7 @@ ApplicationWindow { width: label.width + 12 height: label.height + 12 radius: 5 - color: label.palette.light + color: palette.light Label { id: label anchors.centerIn: parent @@ -465,7 +465,7 @@ ApplicationWindow { } colorize: false showFiat: false - color: mainWindow.palette.windowText + color: palette.windowText fontPixelSize: { if (leftColumn.width < 240) // max width is 252 return leftColumn.width / 7 @@ -526,7 +526,7 @@ ApplicationWindow { Rectangle { id: priceCover // this covers the prices while the wallet is encrypted. - color: mainWindow.palette.window + color: palette.window opacity: 0.8 anchors.fill: parent anchors.topMargin: 2 @@ -567,7 +567,7 @@ ApplicationWindow { Timer { id: animTimer interval: 305 - onTriggered: fiatValue.color = fiatValue.palette.windowText + onTriggered: fiatValue.color = palette.windowText } } @@ -704,7 +704,7 @@ ApplicationWindow { Rectangle { id: splashScreen - color: mainWindow.palette.window + color: palette.window anchors.fill: parent Label { text: qsTr("Preparing...") diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index b2b8bd1..0a64a30 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -68,7 +68,7 @@ ListView { header of the listview. */ header: Rectangle { - color: root.palette.window + color: palette.window width: root.width height: column.height Column { @@ -147,7 +147,7 @@ ListView { height: label.height + 15 width: root.width Rectangle { - color: root.palette.light + color: palette.light anchors.fill: parent } Flowee.Label { @@ -183,9 +183,9 @@ ListView { y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; radius: 20 - color: root.palette.alternateBase + color: palette.alternateBase border.width: 1 - border.color: root.palette.midlight + border.color: palette.midlight } Item { @@ -289,7 +289,7 @@ ListView { height: 1 width: parent.width - 16 x: 8 - color: root.palette.midlight + color: palette.midlight } MouseArea { diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelector.qml index f185f4f..5010223 100644 --- a/guis/mobile/AccountSelector.qml +++ b/guis/mobile/AccountSelector.qml @@ -34,8 +34,8 @@ QQC2.Popup { } background: Rectangle { - color: root.palette.light - border.color: root.palette.midlight + color: palette.light + border.color: palette.midlight border.width: 1 radius: 5 } @@ -59,13 +59,13 @@ QQC2.Popup { id: selectedItemIndicator visible: modelData === root.selectedAccount anchors.fill: parent - color: root.palette.highlight + color: palette.highlight opacity: 0.15 } Rectangle { height: parent.height width: 3 - color: root.palette.highlight + color: palette.highlight visible: selectedItemIndicator.visible } Flowee.Label { diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index f2dbfb3..ee778c9 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -75,7 +75,7 @@ Page { delegate: Rectangle { width: ListView.view.width height: label.height + 20 - color: (index % 2) == 0 ? root.palette.base : root.palette.alternateBase + color: (index % 2) == 0 ? palette.base : palette.alternateBase Flowee.Label { id: iso diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 4149517..cbf4ab0 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -46,7 +46,7 @@ Page { width: parent.width - 5 x: 2.5 height: 3 - color: Pay.fontScaling === target ? root.palette.highlight : root.palette.button + color: Pay.fontScaling === target ? palette.highlight : palette.button } Flowee.Label { @@ -95,7 +95,7 @@ Page { Layout.alignment: Qt.AlignHCenter color: "#00000000" radius: 6 - border.color: root.palette.button + border.color: palette.button border.width: 0.8 implicitHeight: units.height + 10 diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index f661af0..d954133 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -26,7 +26,7 @@ QQC2.Control { height: parent.height background: Rectangle { - color: root.palette.light + color: palette.light } // This trick means any child items are actually added to the 'stack' item's children. @@ -64,7 +64,7 @@ QQC2.Control { id: header width: parent.width height: 50 - color: Pay.useDarkSkin ? root.palette.window : mainWindow.floweeBlue + color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue Flowee.HamburgerMenu { id: menuButton @@ -142,7 +142,7 @@ QQC2.Control { Rectangle { anchors.fill: tabbar - color: root.palette.window + color: palette.window } Row { @@ -159,12 +159,12 @@ QQC2.Control { x: 5 height: 4 width: parent.width - 10 - color: mainWindow.palette.highlight + color: palette.highlight visible: modelData === root.currentIndex } Rectangle { anchors.fill: parent - color: mainWindow.palette.highlight + color: palette.highlight visible: modelData === root.currentIndex opacity: 0.15 } diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 982dd2c..c3d0cbf 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -40,7 +40,7 @@ Item { Rectangle { id: menuArea - color: mainWindow.palette.window + color: palette.window width: 300 height: parent.height x: root.open ? 0 : 0 - width -3 @@ -60,7 +60,7 @@ Item { h = h+ Math.max(currentAccountName.height, 12) + 10 return h; } - color: Qt.lighter(mainWindow.palette.window) + color: Qt.lighter(palette.window) property bool openAccounts: false clip: true @@ -255,7 +255,7 @@ Item { width: 13 height: 2 x: 2 - color: mainWindow.palette.mid + color: palette.mid anchors.verticalCenter: parent.verticalCenter } Rectangle { @@ -264,7 +264,7 @@ Item { height: 13 anchors.horizontalCenter: horizontalBar.horizontalCenter anchors.verticalCenter: parent.verticalCenter - color: mainWindow.palette.mid + color: palette.mid } TextButton { id: addWalletButton diff --git a/guis/mobile/NetView.qml b/guis/mobile/NetView.qml index bebf028..62429e0 100644 --- a/guis/mobile/NetView.qml +++ b/guis/mobile/NetView.qml @@ -39,7 +39,7 @@ Page { delegate: Rectangle { width: listView.width height: peerPane.height + 12 - color: index % 2 === 0 ? secondRow.palette.button : secondRow.palette.base + color: index % 2 === 0 ? palette.button : palette.base opacity: modelData.headersReceived ? 1 : 0.5 ColumnLayout { id: peerPane diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index fd400c6..323d491 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -27,7 +27,7 @@ QQC2.Control { height: parent == null ? 10 : parent.height background: Rectangle { - color: root.palette.light + color: palette.light } leftPadding: 10 @@ -65,7 +65,7 @@ QQC2.Control { id: header width: parent.width // + 20 height: 50 - color: Pay.useDarkSkin ? root.palette.window : mainWindow.floweeBlue + color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue Image { id: backButton diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 048248c..1afe647 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -142,14 +142,14 @@ Page { anchors.top: userComment.bottom anchors.topMargin: 10 border.width: 1 - border.color: root.palette.midlight - color: root.palette.light + border.color: palette.midlight + color: palette.light width: inputs.width + 20 height: 40 radius: 15 Rectangle { - color: root.palette.highlight + color: palette.highlight opacity: 0.3 radius: 6 width: 35 @@ -196,7 +196,7 @@ Page { width: 1 y: inputs.y * -1 height: parent.height - color: root.palette.dark + color: palette.dark } Flowee.HamburgerMenu { @@ -263,7 +263,7 @@ Page { x: -10 anchors.bottom: currentWalletValue.bottom anchors.bottomMargin: -5 - color: root.palette.alternateBase + color: palette.alternateBase MouseArea { anchors.fill: parent diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index cf3635c..a4ac5ca 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -57,8 +57,8 @@ FocusScope { root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } background: Rectangle { - color: mainWindow.palette.light - border.color: mainWindow.palette.midlight + color: palette.light + border.color: palette.midlight border.width: 1 radius: 5 } diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index e6cffa8..6da076d 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -36,7 +36,7 @@ FocusScope { Rectangle { id: background anchors.fill: parent - color: mainWindow.palette.window + color: palette.window } // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 2848dc2..def991a 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -90,7 +90,7 @@ FocusScope { return "red" var state = qr.request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) - return qr.palette.base + return palette.base if (state === PaymentRequest.DoubleSpentSeen) return "#640e0f" // red return "#3e8b4e" // in all other cases: green @@ -99,7 +99,7 @@ FocusScope { } GradientStop { position: 0.1 - color: receiveTab.palette.base + color: palette.base } } opacity: qr.request == null ? 0 : (qr.request.state === PaymentRequest.Unpaid ? 0: 1) diff --git a/guis/mobile/SlideToApprove.qml b/guis/mobile/SlideToApprove.qml index 965118b..b6909fc 100644 --- a/guis/mobile/SlideToApprove.qml +++ b/guis/mobile/SlideToApprove.qml @@ -11,20 +11,20 @@ Item { width: parent.height height: width radius: width / 2 - color: root.palette.window + color: palette.window } Rectangle { x: parent.width - width - 30 width: parent.height height: width radius: width / 2 - color: root.palette.window + color: palette.window } Rectangle { x: 30 + parent.height / 2 width: parent.width - 30 - 30 - parent.height height: parent.height - color: root.palette.window + color: palette.window } Flowee.Label { id: textLabel @@ -42,7 +42,7 @@ Item { color: { if (root.enabled) return Pay.useDarkSkin ? mainWindow.floweeGreen : mainWindow.floweeBlue; - return Qt.darker(textLabel.palette.button, 1.2); + return Qt.darker(palette.button, 1.2); } property bool finished: false onXChanged: { diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 7abcd2a..8ef8a2e 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -126,7 +126,7 @@ Page { Rectangle { width: 50 height: 1 - color: root.palette.button + color: palette.button anchors.verticalCenter: parent.verticalCenter } Flowee.Label { @@ -135,7 +135,7 @@ Page { Rectangle { width: 50 height: 1 - color: root.palette.button + color: palette.button anchors.verticalCenter: parent.verticalCenter } } diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index d6b660a..0a58a68 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -47,7 +47,7 @@ GridLayout { // Transaction is rejected by network return Pay.useDarkSkin ? "#ec2327" : "#b41214"; } - return mainWindow.palette.windowText + return palette.windowText } } diff --git a/guis/mobile/VisualSeparator.qml b/guis/mobile/VisualSeparator.qml index 84eea5d..6e323a1 100644 --- a/guis/mobile/VisualSeparator.qml +++ b/guis/mobile/VisualSeparator.qml @@ -24,6 +24,6 @@ Item { width: parent.width * 0.8 height: 1.3 anchors.centerIn: parent - color: mainWindow.palette.mid + color: palette.mid } } -- 2.54.0 From 0fd3465c7d6c1f5a3f43fdd6fab538dbca99f36c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Feb 2023 20:31:58 +0100 Subject: [PATCH 0325/1428] new version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7f6deaa..81eab0e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.01.0"); + qapp.setApplicationVersion("2023.02.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From ce9f8ce9eb4a9ee26028db586f84ecec7857306e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 11:03:25 +0100 Subject: [PATCH 0326/1428] Set a minimum width for the money editors. --- guis/Flowee/BitcoinValueField.qml | 5 +++-- guis/Flowee/FiatValueField.qml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/BitcoinValueField.qml b/guis/Flowee/BitcoinValueField.qml index 51944ee..dc360da 100644 --- a/guis/Flowee/BitcoinValueField.qml +++ b/guis/Flowee/BitcoinValueField.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,10 +28,11 @@ MoneyValueField { baselineOffset: beginOfValue.baselineOffset implicitHeight: beginOfValue.implicitHeight - implicitWidth: row.width + implicitWidth: Math.max(row.width, 70) RowLayout { id: row + anchors.right: parent.right spacing: 4 height: parent.height property string amountString: { diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index 0762f06..d218483 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -27,11 +27,12 @@ MoneyValueField { property alias color: fiat.color baselineOffset: fiat.baselineOffset implicitHeight: fiat.implicitHeight - implicitWidth: row.width + implicitWidth: Math.max(row.width, 70) maxFractionalDigits: Fiat.displayCents ? 2 : 0 RowLayout { id: row + anchors.right: parent.right height: parent.height spacing: 0 property string amountString: Fiat.priceToStringSimple(root.value) -- 2.54.0 From 63033ab153acb67a731ff0ff74dcc76d5fb3cabc Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 15:05:29 +0100 Subject: [PATCH 0327/1428] Forward the user owned property being changed. This is useful to see if a default wallet got an incoming transaction. --- src/AccountInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 84b1a5c..de0d205 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -42,6 +42,7 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) }, Qt::QueuedConnection); connect(wallet, SIGNAL(paymentRequestsChanged()), this, SIGNAL(paymentRequestsChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged()), Qt::QueuedConnection); + connect(wallet, SIGNAL(userOwnedChanged()), this, SIGNAL(userOwnedChanged()), Qt::QueuedConnection); connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); } -- 2.54.0 From 457edc20b4c96492b1d50f4b953a375e981a8db3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 15:28:06 +0100 Subject: [PATCH 0328/1428] Simplify the payment and amounts getters This merges the paymentAmount and effectiveBchAmount methods because they did 99% the same. Same with the paymentAmountFiat and effectiveFiatAmount. This removes the 'effective*' set of properties. --- guis/Flowee/BroadcastFeedback.qml | 4 ++-- guis/desktop/SendTransactionPane.qml | 2 +- src/Payment.cpp | 27 ++++++++++++--------------- src/Payment.h | 17 ++++++----------- 4 files changed, 21 insertions(+), 29 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index cfb1aea..b010197 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -149,7 +149,7 @@ QQC2.Control { anchors.topMargin: 10 color: "black" font.pixelSize: 38 - text: Fiat.formattedPrice(payment.effectiveFiatAmount) + text: Fiat.formattedPrice(payment.paymentAmountFiat) visible: Fiat.price !== 0 } BitcoinAmountLabel { @@ -158,7 +158,7 @@ QQC2.Control { anchors.top: fiatAmount.bottom anchors.topMargin: 20 fontPixelSize: 28 - value: payment.effectiveBchAmount + value: payment.paymentAmount colorize: false showFiat: false } diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index a95ff87..612f46f 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -502,7 +502,7 @@ Item { } Flowee.BitcoinAmountLabel { id: neededAmountLabel - value: payment.effectiveBchAmount + value: payment.paymentAmount Layout.fillWidth: true colorize: false } diff --git a/src/Payment.cpp b/src/Payment.cpp index 76649fd..fe17c12 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -70,22 +70,12 @@ void Payment::setPaymentAmount(double amount) doAutoPrepare(); } -double Payment::paymentAmount() const -{ - return soleOut()->paymentAmount(); -} - -void Payment::setPaymentAmountFiat(double amount) +void Payment::setPaymentAmountFiat(int amount) { soleOut()->setFiatAmount(amount); doAutoPrepare(); } -double Payment::paymentAmountFiat() const -{ - return soleOut()->fiatAmount(); -} - void Payment::setTargetAddress(const QString &address_) { QString address = address_.trimmed(); @@ -362,6 +352,7 @@ void Payment::reset() m_sentPeerCount = 0; m_rejectedPeerCount = 0; m_userComment.clear(); + m_wallet = nullptr; for (auto d : m_paymentDetails) { d->deleteLater(); @@ -665,7 +656,7 @@ bool Payment::txPrepared() const return m_txPrepared; } -int Payment::effectiveFiatAmount() const +int Payment::paymentAmountFiat() const { int amount = 0; PaymentDetailInputs *inputSelector = nullptr; @@ -684,7 +675,10 @@ int Payment::effectiveFiatAmount() const totalBch = inputSelector->selectedValue(); } else { // then the total amount is actually trivial, it is all that is available in the wallet. - totalBch = m_wallet->balanceConfirmed() + m_wallet->balanceUnconfirmed(); + auto wallet = m_wallet; // use the one we prepare()d from if available + if (wallet == nullptr) + wallet = m_account->wallet(); + totalBch = wallet->balanceConfirmed() + wallet->balanceUnconfirmed(); } return (totalBch * m_fiatPrice / 10000000 + 5) / 10; @@ -694,7 +688,7 @@ int Payment::effectiveFiatAmount() const return amount; } -double Payment::effectiveBchAmount() const +double Payment::paymentAmount() const { int64_t amount = 0; PaymentDetailInputs *inputSelector = nullptr; @@ -709,7 +703,10 @@ double Payment::effectiveBchAmount() const if (inputSelector) return inputSelector->selectedValue(); // then the total amount is actually trivial, it is all that is available in the wallet. - return m_wallet->balanceConfirmed() + m_wallet->balanceUnconfirmed(); + auto wallet = m_wallet; // use the one we prepare()d from if available + if (wallet == nullptr) + wallet = m_account->wallet(); + return wallet->balanceConfirmed() + wallet->balanceUnconfirmed(); } amount += out->paymentAmount(); } diff --git a/src/Payment.h b/src/Payment.h index 79a52fc..9530db6 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -36,10 +36,10 @@ class Payment : public QObject { Q_OBJECT Q_PROPERTY(int feePerByte READ feePerByte WRITE setFeePerByte NOTIFY feePerByteChanged) - /// The single-output amount of funds being sent by this Payment. + /// The payment-wide amount of funds being sent by this Payment. Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) - /// The single-output amount of funds being sent by this Payment. - Q_PROPERTY(double paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) + /// The payment-wide amount of funds being sent by this Payment. + Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) /// The single-output address we will send to Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted @@ -56,11 +56,6 @@ class Payment : public QObject Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged); Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) - /// The payment-wide amount of funds being sent by this Payment. - Q_PROPERTY(int effectiveFiatAmount READ effectiveFiatAmount NOTIFY amountChanged) - /// The payment-wide amount of funds being sent by this Payment. - Q_PROPERTY(double effectiveBchAmount READ effectiveBchAmount NOTIFY amountChanged) - // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared Q_PROPERTY(bool txPrepared READ txPrepared NOTIFY txPreparedChanged); @@ -123,8 +118,8 @@ public: */ double paymentAmount() const; - void setPaymentAmountFiat(double amount); - double paymentAmountFiat() const; + void setPaymentAmountFiat(int amount); + int paymentAmountFiat() const; /** * Sets the address to pay to. @@ -253,7 +248,7 @@ private: std::shared_ptr m_infoObject; short m_sentPeerCount; short m_rejectedPeerCount; - Wallet *m_wallet; + Wallet *m_wallet = nullptr; QString m_error; QString m_userComment; }; -- 2.54.0 From 424ddbd9fb2fe87e568bdc25185535c4361a0dfd Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 15:48:18 +0100 Subject: [PATCH 0329/1428] New version for Android --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b3c417e..afa59d8 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + -- 2.54.0 From c93dbbeb2ef3a8c8769e7970ca21bd7279ebd7d2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 20:53:15 +0100 Subject: [PATCH 0330/1428] Update amounts in UI --- src/PaymentDetailOutput.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index fdfd003..6a1b91d 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -289,6 +289,10 @@ void PaymentDetailOutput::setMaxAllowed(bool max) m_maxAllowed = max; emit maxAllowedChanged(); - if (max == false && m_paymentAmount == -1) + if (max == false && m_paymentAmount == -1) { setPaymentAmount(0); + } else { + emit paymentAmountChanged(); + emit fiatAmountChanged(); + } } -- 2.54.0 From 7e4d884cec25b961e83abdda1b45336e33071dca Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:05:39 +0100 Subject: [PATCH 0331/1428] Fix link, make defaults load again. --- guis/desktop.qrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop.qrc b/guis/desktop.qrc index cb4327a..018f75d 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -19,7 +19,7 @@ desktop/images/edit-delete.svg desktop/images/lock-light.svg desktop/images/lock-dark.svg - desktop/defaults.ini + desktop/defaults.ini desktop/ConfigItem.qml ControlColors.js desktop/main.qml -- 2.54.0 From c10510b1398b986851bb05b8d49fe05384014655 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:19:14 +0100 Subject: [PATCH 0332/1428] Behave correctly on accounts lists The GUI confused 'userowned' and having a list of wallets. Basically we can just trust the backend 'accounts' list, making the GUI eaier to understand. --- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/PayWithQR.qml | 2 +- guis/mobile/main.qml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index dbd9331..03e86a8 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -57,7 +57,7 @@ QQC2.Control { } TextButton { - visible: root.account.isUserOwned + visible: portfolio.accounts.length > 1 Layout.fillWidth: true text: qsTr("Primary Wallet") onClicked: if (!root.account.isArchived) root.account.isPrimaryAccount = !root.account.isPrimaryAccount diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index d954133..5dc7061 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -101,7 +101,7 @@ QQC2.Control { QQC2.Label { id: currentWalletName - visible: portfolio.current.isUserOwned || portfolio.accounts.length >= 1 + visible: portfolio.accounts.length >= 1 text: portfolio.current.name color: "#fcfcfc" clip: true diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 1afe647..6b55199 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -295,7 +295,7 @@ Page { Flowee.Label { id: currentWalletLabel text: payment.account.name - visible: payment.account.isUserOwned + visible: portfolio.accounts.length > 1 x: 10 width: parent.width - 10 anchors.bottom: currentWalletValue.top diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 122a474..7ccaff5 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -36,7 +36,7 @@ ApplicationWindow { // variables are available. if (!isLoading) { thePile.replace("./MainView.qml"); - if (portfolio.accounts.length === 0) + if (!portfolio.current.isUserOwned) thePile.push("./StartupScreen.qml"); } } -- 2.54.0 From 641ec5c969ffeebc504f00d4b4b450a38c8f16be Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:25:46 +0100 Subject: [PATCH 0333/1428] Fix sizing. The page is no longer using a layout manager. --- guis/mobile/About.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 142db6a..b4c8ae4 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -80,9 +80,8 @@ Nederland
" textFormat: Text.MarkdownText - Layout.fillWidth: true - Layout.fillHeight: true wrapMode: Text.WordWrap + width: parent.width } } } -- 2.54.0 From e153e35d32bd765659367d7ad14a75b8457fa8e7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:27:31 +0100 Subject: [PATCH 0334/1428] Avoid closing the app on pressing back too often --- guis/mobile/main.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 7ccaff5..f929312 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -60,6 +60,14 @@ ApplicationWindow { initialItem: "./Loading.qml"; onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); enabled: !menuOverlay.open + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + // We eat this and have nothing happen. + // This is useful to avoid the app closing on pressing 'back' one time too many on Android + event.accepted = true; + } + } } MenuOverlay { id: menuOverlay -- 2.54.0 From 7309ba978e9fcc7b91db53fe6c6f9ff3b3de7f07 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:45:33 +0100 Subject: [PATCH 0335/1428] Make tabbar look the same as others This synchronizes the look of the tabbar with the look of the main screen tabbar and account selection popup. --- guis/mobile/AccountsList.qml | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 7fe3c33..e0e107f 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,19 +45,30 @@ Page { orientation: Qt.Horizontal width: parent.width anchors.top: parent.top - height: 50 + height: portfolio.accounts.length > 1 ? 50 : 0 + clip: true boundsBehavior: Flickable.StopAtBounds currentIndex: indexOfCurrentAccount(); - delegate: Rectangle { - color: { - if (index === tabBar.currentIndex) - return "#1a1a1a" - return "#303030" - } + delegate: Item { width: Math.max(tabName.width, 120) height: tabName.height + 20 + Rectangle { + x: 5 + height: 4 + width: parent.width - 10 + color: palette.highlight + visible: index === tabBar.currentIndex + anchors.bottom: parent.bottom + } + Rectangle { + anchors.fill: parent + color: palette.highlight + visible: index === tabBar.currentIndex + opacity: 0.15 + } + Text { id: tabName color: index === tabBar.currentIndex ? "white" : "#c2c2c2" -- 2.54.0 From e7f28405460461d3d007bcf7a116449044224e15 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:50:13 +0100 Subject: [PATCH 0336/1428] Improve feedback a little Make sure it doesn't overlap the QR at all. Allow the user to press it a second time to make the text go away again. --- guis/Flowee/QRWidget.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 1ee0717..2649af5 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -35,7 +35,8 @@ Image { anchors.fill: parent onClicked: { Pay.copyToClipboard(root.request.qr) - clipboardFeedback.opacity = 1 + // invert the feedback so a second tap removes the feedback again. + clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 } } @@ -43,11 +44,11 @@ Image { id: clipboardFeedback opacity: 0 width: feedbackText.width + 20 - height: feedbackText.height + 20 + height: feedbackText.height + 14 radius: 10 color: Pay.useDarkSkin ? "#333" : "#ddd" anchors.bottom: parent.bottom - anchors.bottomMargin: -10 + anchors.bottomMargin: -8 anchors.horizontalCenter: parent.horizontalCenter Label { @@ -60,9 +61,9 @@ Image { Behavior on opacity { OpacityAnimator {} } - /// after 10 seconds, remove feedback. + /// after 8 seconds, remove feedback. Timer { - interval: 10000 + interval: 8000 running: clipboardFeedback.opacity >= 1 onTriggered: clipboardFeedback.opacity = 0 } -- 2.54.0 From fa34cb06d916670c540367a99fd5453e68aa6586 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 21:54:52 +0100 Subject: [PATCH 0337/1428] Remove placeholder button for now. --- guis/mobile/AccountHistory.qml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 0a64a30..b3f78c6 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -103,23 +103,20 @@ ListView { } Row { - width: parent.width - height: dummyButton.height + height: startScan.height + x: (parent.width - width) / 2 + spacing: 16 Flowee.ImageButton { + id: startScan source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - width: parent.width / 3 + width: 60 onClicked: thePile.push("PayWithQR.qml") iconSize: 40 - height: dummyButton.height - } - IconButton { - id: dummyButton - width: parent.width / 3 - text: qsTr("Scheduled") + height: 60 } Flowee.ImageButton { - width: parent.width / 3 - height: dummyButton.height + width: 60 + height: startScan.height iconSize: 50 source: "qrc:/receive.svg" onClicked: switchToTab(2) // receive tab -- 2.54.0 From 46f3bc6ae0fb257e882fb3e6823934e25ea48d40 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 22:18:11 +0100 Subject: [PATCH 0338/1428] Tweaks to the slider Make sure that the text doesn't get covered by the thumb Make the thumb slightly transparant. --- guis/mobile/SlideToApprove.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/guis/mobile/SlideToApprove.qml b/guis/mobile/SlideToApprove.qml index b6909fc..96d9752 100644 --- a/guis/mobile/SlideToApprove.qml +++ b/guis/mobile/SlideToApprove.qml @@ -29,7 +29,13 @@ Item { Flowee.Label { id: textLabel text: qsTr("SLIDE TO SEND") - anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 30 + 50 // because the circle covers that. + anchors.right: parent.right + anchors.rightMargin: 80 + fontSizeMode: Text.HorizontalFit // account for long translations + horizontalAlignment: Text.AlignHCenter } Rectangle { @@ -39,6 +45,7 @@ Item { radius: width / 2 x: 35 y: 2.5 + opacity: 0.7 color: { if (root.enabled) return Pay.useDarkSkin ? mainWindow.floweeGreen : mainWindow.floweeBlue; -- 2.54.0 From b2d5aea59002e0ea86aee96b0bb4ba5c67f51e09 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 22:23:03 +0100 Subject: [PATCH 0339/1428] Don't show historical price if we don't have any --- guis/mobile/PriceDetails.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 9e28aef..3951622 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -51,6 +51,7 @@ Item { property int days: 0 height: buddy.height width: buddy.width + 3 + main.width + visible: main.percentage > 0 Flowee.Label { id: buddy -- 2.54.0 From 9487852033b9f76e6df2b7bb9bf182af9c06bb49 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Feb 2023 23:34:15 +0100 Subject: [PATCH 0340/1428] slighly shorter string --- translations/floweepay-mobile_nl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index a05e0d5..7f87c84 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -727,8 +727,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt %1 blocks ago Confirmations - %1 blok geleden - %1 blokken geleden + %1 blok terug + %1 blokken terug
-- 2.54.0 From e664b033e4aa413be9d3936d0fe3c1a42416ff62 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 24 Feb 2023 18:27:17 +0100 Subject: [PATCH 0341/1428] Move shutdown of app singleton to be earlier The downside of having the application singleton be the owner of the wallets is that the destructor won't get called until after main() has been completed. And that means that globals deletion order becomes an issue, which is messy and should be avoided. This change splits out the saving and deletion of wallets & p2p layer, making use of the Qt application object signal to delete wallets just before the QtGuiApplication object destructs. Which is _inside_ of main(). --- src/FloweePay.cpp | 16 +++++++++++----- src/FloweePay.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 19bcf26..de9c435 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -86,9 +86,6 @@ FloweePay::FloweePay() // make it move to the proper thread. connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() { - p2pNet()->shutdown(); - }); #ifdef TARGET_OS_Android // on Android, an app is either full screen (active) or inactive and not visible. // It is expected we save state as we move to inactive state in order to make the app @@ -173,17 +170,25 @@ FloweePay::FloweePay() // the singleshot below. QTimer::singleShot(1000, this, SLOT(saveData())); }, Qt::QueuedConnection); + + connect (QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() { + shutdown(); + }); } -FloweePay::~FloweePay() +// shutting down is done outside of the destructor. +// This is so we have a predictable ordering of shutdown whereas on +// a global destructor the ordering is not locally controlled. +void FloweePay::shutdown() { + p2pNet()->shutdown(); saveData(); auto *dl = m_downloadManager.get(); if (dl) // p2pNet follows lazy initialization. dl->removeHeaderListener(this); for (auto wallet : m_wallets) { - if (dl) { + if (dl) { // p2pNet follows lazy initialization. dl->removeDataListener(wallet); dl->removeHeaderListener(wallet); dl->connectionManager().removePrivacySegment(wallet->segment()); @@ -195,6 +200,7 @@ FloweePay::~FloweePay() } } qDeleteAll(m_wallets); + m_wallets.clear(); } // static diff --git a/src/FloweePay.h b/src/FloweePay.h index 24b234d..281b05f 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -76,7 +76,6 @@ public: }; FloweePay(); - ~FloweePay(); /** * Select the chain that the FloweePay singleton will be created for. @@ -287,6 +286,7 @@ private slots: private: void init(); + void shutdown(); void saveAll(); // create wallet and add to list. Please consider calling walletsChanged() after Wallet *createWallet(const QString &name); -- 2.54.0 From b13f7d353e649268b243d87e0ca32e99bc8f1ae2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 24 Feb 2023 19:45:12 +0100 Subject: [PATCH 0342/1428] Make brand new wallets behave better on initial sync We now skip building (and sending) of the bloom filter until we have finished the initial blockheader sync. (only relevant for newly created wallets). Additionally we update the WalletInfo with our inital blockheight as soon as we know about it. --- src/Wallet.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 310e542..97cfb48 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -935,11 +935,24 @@ int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript) const void Wallet::rebuildBloom() { - auto lock = m_segment->clearFilter(); int unusedToInclude = 20; int hdUnusedToInclude = 10; int changeUnusedToInclude = 30; + // on wallet creation we may not have yet synced the entire header chain, + // in that case the secrets are given a height that is in seconds, in order + // to find the proper blockheight matching it later. + // As such, the presence of such initialHeights indicates the p2p net is not + // yet at the tip of the headerchain, which makes sending out a bloom filter irrelevant. + for (const auto &i : m_walletSecrets) { + if (i.second.initialHeight >= 10000000) { + logDebug() << " not building bloom, still have date based private keys"; + return; + } + } + + auto lock = m_segment->clearFilter(); + // ^ notice that the 'lock' we get back will broadcast the new filter when it goes out of scope. std::set secretsUsed; for (const auto &i : m_walletTransactions) { const auto &wtx = i.second; @@ -962,12 +975,7 @@ void Wallet::rebuildBloom() logDebug() << "Rebuilding bloom filter. UTXO-size:" << secretsWithBalance.size(); for (auto &i : m_walletSecrets) { const auto &secret = i.second; - if (secret.initialHeight >= 10000000) { - // is a timestamp, which means that we are waiting for the - // headerSyncComplete() to be called and this key is fresh - // and so searching in the history is pointless. - continue; - } + assert(secret.initialHeight < 10000000); // see similar check at start of this method if (secret.reserved) { // whitelisted this one } @@ -1294,8 +1302,15 @@ void Wallet::headerSyncComplete() changedOne = true; } } - if (changedOne) + if (changedOne) { + // broadcast to peers our bloom filter, which would have skipped all + // time-based blocks before. rebuildBloom(); + + // make the wallet iniital sync also show something sane. + if (m_segment) + m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); + } } void Wallet::broadcastUnconfirmed() -- 2.54.0 From 45768afe631613533ec98f12bbc6edf1cf191198 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 24 Feb 2023 23:44:10 +0100 Subject: [PATCH 0343/1428] Tweak the popup menu - Don't show Details again for already showing details. - Show encryption menu even if we are in the details pane (because why not) --- guis/desktop/AccountConfigMenu.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 7e5d944..0147510 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -58,7 +58,9 @@ ConfigItem { onAboutToOpen: { var items = []; var onMainView = (accountOverlay.state === "showTransactions") - if (onMainView || accountOverlay.state === "accountDetails") + if (onMainView + || (accountOverlay.state === "accountDetails" + && portfolio.current != root.account)) items.push(detailsAction); var encrypted = root.account.needsPinToOpen; var decrypted = root.account.isDecrypted; @@ -70,7 +72,7 @@ ConfigItem { var isArchived = root.account.isArchived; if (!singleAccountSetup && !isArchived) items.push(primaryAction); - if (onMainView && !encrypted) + if (!encrypted) items.push(encryptAction); if (!singleAccountSetup) items.push(archiveAction); -- 2.54.0 From 8742108fd1ff73943dc3252c8b7bb1e9871f43d2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 24 Feb 2023 23:48:26 +0100 Subject: [PATCH 0344/1428] Use bool instead of list-length --- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/PayWithQR.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 03e86a8..d48f126 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -57,7 +57,7 @@ QQC2.Control { } TextButton { - visible: portfolio.accounts.length > 1 + visible: !portfolio.singleAccountSetup Layout.fillWidth: true text: qsTr("Primary Wallet") onClicked: if (!root.account.isArchived) root.account.isPrimaryAccount = !root.account.isPrimaryAccount diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 5dc7061..ebd9bed 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -101,7 +101,7 @@ QQC2.Control { QQC2.Label { id: currentWalletName - visible: portfolio.accounts.length >= 1 + visible: !portfolio.singleAccountSetup text: portfolio.current.name color: "#fcfcfc" clip: true diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 6b55199..fff4815 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -295,7 +295,7 @@ Page { Flowee.Label { id: currentWalletLabel text: payment.account.name - visible: portfolio.accounts.length > 1 + visible: !portfolio.singleAccountSetup x: 10 width: parent.width - 10 anchors.bottom: currentWalletValue.top -- 2.54.0 From 8d5a4ffc73c6ad12f2fe371fd9090cb5d2222c47 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 25 Feb 2023 14:45:36 +0100 Subject: [PATCH 0345/1428] Rename Android package to have 'test' in it. Since the default option to 'sign' is a self-signed certificate which is insecure (its in git, absolutely not private), it makes sense to be consistent and make the default create a test package. Make it easier on people self-compiling. --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index afa59d8..74cda35 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + -- 2.54.0 From fa225772e84e66ac8109fa14afe7cf867a64c93c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 25 Feb 2023 19:04:47 +0100 Subject: [PATCH 0346/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 74cda35..bb68b34 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/main.cpp b/src/main.cpp index 81eab0e..958b513 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.02.0"); + qapp.setApplicationVersion("2023.02.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From e1165bf66c511502ed24af08e73cc3ebb9f181da Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Mar 2023 18:02:12 +0100 Subject: [PATCH 0347/1428] Update translations from crowdin --- translations/floweepay-common_en.ts | 288 ++++++++-- translations/floweepay-common_nl.ts | 10 +- translations/floweepay-desktop_en.ts | 717 +++++++++++-------------- translations/floweepay-mobile_en.ts | 775 +++++++++++++++++++++++++++ 4 files changed, 1323 insertions(+), 467 deletions(-) create mode 100644 translations/floweepay-mobile_en.ts diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index dd2f31f..34b8558 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -4,68 +4,137 @@ AccountInfo - - - Up to date - Up to date + + Wallet: Up to date + Wallet: Up to date - - %1 weeks behind - - A week behind - %1 weeks behind + + Behind: %1 weeks, %2 days + counter on weeks + + Behind: %1 week, %2 days + Behind: %1 weeks, %2 days - - %1 days behind - - A day behind - %1 days behind + + Behind: %1 days + + Behind: %1 day + Behind: %1 days - + + Up to date + Up to date + + + Updating Updating - - %1 hours behind - - An hour behind - %1 hours behind + + Still %1 hours behind + + Still an hour behind + Still %1 hours behind + + AccountTypeLabel + + + This wallet is a single-address wallet. + This wallet is a single-address wallet. + + + + This wallet is based on a HD seed-phrase + This wallet is based on a HD seed-phrase + + + + This wallet is a simple multiple-address wallet. + This wallet is a simple multiple-address wallet. + + + + BroadcastFeedback + + + Sending Payment + Sending Payment + + + + Payment Sent + Payment Sent + + + + Transaction rejected by network + Transaction rejected by network + + + + Add a personal note + Add a personal note + + + + Copied TXID to clipboard + Copied TXID to clipboard + + + + Opening Website + Opening Website + + + + Close + Close + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + Coin has been fused for increased anonymity + + FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -74,13 +143,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -90,35 +159,77 @@ - NotificationManager + LabelWithClipboard - - BCH block mined %1 - BCH block mined %1 + + Copy + Copy + + + + MenuModel + + + Settings + Settings - + + Wallet Information + Wallet Information + + + + Network Details + Network Details + + + + About + About + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash block mined. Height: %1 + + + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) block mined: %1 + + + Mute Mute - - New Transaction - - New Transaction + + New Transactions + dialog-title + + New Transactions New Transactions - + %1 new transactions across %2 wallets found (%3) %1 new transactions across %2 wallets found (%3) + + + A payment of %1 has been sent + A payment of %1 has been sent + - + %1 new transactions found (%2) - %1 new transaction found (%2) + %1 new transactions found (%2) %1 new transactions found (%2) @@ -126,26 +237,39 @@ Payment - + + Invalid PIN + Invalid PIN + + + Not enough funds selected for fees Not enough funds selected for fees - + Not enough funds in wallet to make payment! Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. + + QRWidget + + + Copied to clipboard + Copied to clipboard + + Wallet - - + + Change #%1 Change #%1 @@ -153,50 +277,102 @@ WalletCoinsModel - + Unconfirmed Unconfirmed - + %1 hours age, like: hours old - one hour + an hour %1 hours - + %1 days age, like: days old - one day + %1 day %1 days - + %1 weeks age, like: weeks old - one week + %1 week %1 weeks - + %1 months age, like: months old - one month + %1 month %1 months - + Change #%1 Change #%1 + + WalletHistoryModel + + + Today + Today + + + + Yesterday + Yesterday + + + + Earlier this week + Earlier this week + + + + This week + This week + + + + Earlier this month + Earlier this month + + + + WalletSecretsView + + + + Copy Address + Copy Address + + + + Copy Private Key + Copy Private Key + + + + Coins: %1 / %2 + Coins: %1 / %2 + + + + Signed with Schnorr signatures in the past + Signed with Schnorr signatures in the past + + diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 35d96bb..de39ab6 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -13,16 +13,16 @@ Behind: %1 weeks, %2 days counter on weeks - Achter %1 week, %2 dagen - Achter %1 weken, %2 dagen + %1 week, %2 dagen te gaan + %1 weken, %2 dagen te gaan Behind: %1 days - Achter %1 dag - Achter %1 dagen + %1 dag oude data + %1 dagen oude info @@ -40,7 +40,7 @@ Still %1 hours behind Nog één uur - Nog %1 uren achter + Nog %1 uur
diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 0bc6860..66eb32b 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -2,209 +2,155 @@ - AccountDetails + AccountConfigMenu - - Wallet Details - Wallet Details - - - - Name - Name - - - - Encryption - Encryption - - - - Password - Password - - - - Open - Open - - - - Invalid PIN - Invalid PIN - - - - Sync Status - Sync Status - - - - This wallet is a single-address wallet. - This wallet is a single-address wallet. - - - - This wallet is based on a HD seed-phrase - This wallet is based on a HD seed-phrase - - - - This wallet is a simple multiple-address wallet. - This wallet is a simple multiple-address wallet. - - - - Address List - Address List - - - - Change Addresses - Change Addresses - - - - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - - - - Used Addresses - Used Addresses - - - - Switches between unused and used Bitcoin addresses - Switches between unused and used Bitcoin addresses - - - - Coins: %1 / %2 - Coins: %1 / %2 - - - - Signed with Schnorr signatures in the past - Signed with Schnorr signatures in the past - - - - Copy Address - Copy Address - - - - Copy Private Key - Copy Private Key - - - - Backup details - Backup details - - - - Seed-phrase - Seed-phrase - - - - Derivation - Derivation - - - - - Copy - Copy - - - - Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - - - - <b>Important</b>: Never share your seed-phrase with others! - <b>Important</b>: Never share your seed-phrase with others! - - - - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - - - - AccountListItem - - - Last Transaction - Last Transaction - - - + Details Details - + Unarchive Unarchive - + Archive Wallet Archive Wallet - + Make Primary Make Primary - + ★ Primary ★ Primary - + Protect With Pin... Protect With Pin... - + Open Open encrypted wallet Open - + Close Close encrypted wallet Close - CashFusionIcon + AccountDetails - - Coin has been fused for increased anonymity - Coin has been fused for increased anonymity + + Wallet Details + Wallet Details + + + + Name + Name + + + + Sync Status + Sync Status + + + + Encryption + Encryption + + + + Password + Password + + + + Open + Open + + + + Invalid PIN + Invalid PIN + + + + Address List + Address List + + + + Change Addresses + Change Addresses + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + + + + Used Addresses + Used Addresses + + + + Switches between unused and used Bitcoin addresses + Switches between unused and used Bitcoin addresses + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + + + + Backup details + Backup details + + + + Seed-phrase + Seed-phrase + + + + Derivation + Derivation + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Important</b>: Never share your seed-phrase with others! - LabelWithClipboard + AccountListItem - - Copy Address - Copy Address + + Last Transaction + Last Transaction NetView - + Peers (%1) One peer @@ -212,48 +158,48 @@ - + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - + initializing connection initializing connection - + Verifying peer Verifying peer - + Assigning... Assigning... - + Peer for wallet: %1 Peer for wallet: %1 - + Peer for initial wallet Peer for initial wallet - + Close Close @@ -261,32 +207,32 @@ NewAccountCreateBasicAccount - + This creates a new empty wallet with simple multi-address capability This creates a new empty wallet with simple multi-address capability - + Name Name - + Go Go - + Advanced Options Advanced Options - + Force Single Address Force Single Address - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. @@ -296,27 +242,27 @@ This ensures only one private key will need to be backed up NewAccountCreateHDAccount - + This creates a new empty wallet with smart creation of addresses from a single seed-phrase This creates a new empty wallet with smart creation of addresses from a single seed-phrase - + Name Name - + Go Go - + Advanced Options Advanced Options - + Derivation Derivation @@ -324,79 +270,79 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - + Secret The seed-phrase or private key Secret - + Example: %1 placeholder text Example: %1 - + Name Name - + Private key description of type Private key - + BIP 39 seed-phrase description of type BIP 39 seed-phrase - + Unrecognized word Word from the seed-phrases lexicon Unrecognized word - + Import wallet Import wallet - + Advanced Options Advanced Options - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - + Alternate phrase Alternate phrase - + Start Height Start Height - + Derivation Derivation @@ -404,83 +350,83 @@ Change will come back to the imported key. NewAccountPane - + New Bitcoin Cash Wallet New Bitcoin Cash Wallet - + Basic Basic - + Private keys based Property of a wallet Private keys based - + Difficult to backup Context: wallet type Difficult to backup - + Great for brief usage Context: wallet type Great for brief usage - + HD wallet HD wallet - + Seed-phrase based Context: wallet type Seed-phrase based - + Easy to backup Context: wallet type Easy to backup - + Most compatible The most compatible wallet type Most compatible - + Import Import - + Imports seed-phrase Imports seed-phrase - + Imports private key Imports private key - + Basic wallet with private keys Basic wallet with private keys - + Seed-phrase wallet Seed-phrase wallet - + Import of an existing wallet Import of an existing wallet @@ -488,17 +434,17 @@ Change will come back to the imported key. PaymentTweakingPanel - + Add Detail Add Detail - + Coin Selector Coin Selector - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. @@ -506,73 +452,68 @@ Change will come back to the imported key. ReceiveTransactionPane - + Share your QR code or copy address to receive Share your QR code or copy address to receive - - Copied to clipboard - Copied to clipboard - - - + Checking Checking - + Transaction high risk Transaction high risk - + Payment Seen Payment Seen - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Description Description - + Amount Amount - + Remember payment request Remember - + Clear Clear - + Done Done - + Delete Delete @@ -580,177 +521,146 @@ Change will come back to the imported key. SendTransactionPane - - Destination - Destination - - - - Max available - The maximum balance available - Max available - - - - %1 to %2 - summary text to pay X-euro to address M - %1 to %2 - - - - Enter Bitcoin Cash Address - Enter Bitcoin Cash Address - - - - Amount - Amount - - - - Max - Max - - - - Prepare - Prepare - - - + Add Destination Add Destination - + + Prepare + Prepare + + + Transaction Details Transaction Details - + Not prepared yet Not prepared yet - + Fee Fee - + Transaction size Transaction size - + %1 bytes %1 bytes - + Fee per byte Fee per byte - + %1 sat/byte fee %1 sat/byte - + Send Send - + Cancel Cancel - - Transaction rejected by network - Transaction rejected by network - - - - Payment Sent - Payment Sent - - - - + Copy transaction-ID Copy transaction-ID - + Confirm delete Confirm delete - + Do you really want to delete this detail? Do you really want to delete this detail? - + Enter your PIN Enter your PIN - - Your payment can be found by its identifyer: %1 - Your payment can be found by its identifyer: %1 + + Destination + Destination - - Copy - Copy + + Max available + The maximum balance available + Max available - - Internet - Internet + + %1 to %2 + summary text to pay X-euro to address M + %1 to %2 - - Comment - Comment + + Enter Bitcoin Cash Address + Enter Bitcoin Cash Address - - Close - Close + + Copy Address + Copy Address - + self payment to self self - + + Amount + Amount + + + + Max + Max + + + Warning Warning - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -759,53 +669,53 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin @@ -813,42 +723,47 @@ Change will come back to the imported key. SettingsPane - + Settings Settings - + Unit Unit - + Show Block Notifications Show Block Notifications - + When a new block is mined, Flowee Pay shows a desktop notification When a new block is mined, Flowee Pay shows a desktop notification - + Night Mode Night Mode - + Version Version - + Library Version Library Version - + + Synchronization + Synchronization + + + Network Status Network Status @@ -856,124 +771,124 @@ Change will come back to the imported key. WalletEncryption - + Protect your wallet with a password Protect your wallet with a password - + Pin to Pay Pin to Pay - + Protect your funds pin to pay Protect your funds - + Fully open, except for sending funds pin to pay Fully open, except for sending funds - + Keeps in sync pin to pay Keeps in sync - + Pin to Open Pin to Open - + Protect your entire wallet pin to open Protect your entire wallet - + Balance and history protected pin to open Balance and history protected - + Requires Pin to view, sync or pay pin to open Requires Pin to view, sync or pay - + Make "%1" wallet require a pin to pay Make "%1" wallet require a pin to pay - + Make "%1" wallet require a pin to open Make "%1" wallet require a pin to open - + Wallet already has pin to pay applied Wallet already has pin to pay applied - - + + Wallet already has pin to open applied Wallet already has pin to open applied - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. - + Password Password - + Wallet Wallet - + Encrypt Encrypt - + Invalid password to open this wallet Invalid password to open this wallet - + Close Close - + Repeat password Repeat password - + Please confirm the password by entering it again Please confirm the password by entering it again - + Typed passwords do not match Typed passwords do not match @@ -981,17 +896,17 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Pay Pin to Pay - + Pin to Open Pin to Open - + (Opened) Wallet is decrypted (Opened) @@ -1000,37 +915,37 @@ Change will come back to the imported key. WalletTransaction - + Miner Reward Miner Reward - + Cash Fusion Cash Fusion - + Received Received - + Moved Moved - + Sent Sent - + rejected rejected - + unconfirmed unconfirmed @@ -1038,27 +953,27 @@ Change will come back to the imported key. WalletTransactionDetails - + Copy transaction-ID Copy transaction-ID - + Status Status - + rejected rejected - + unconfirmed unconfirmed - + %1 confirmations (mined in block %2) %1 confirmation (mined in block %2) @@ -1066,22 +981,22 @@ Change will come back to the imported key. - + Copy block height Copy block height - + Fees Fees - + Size Size - + %1 bytes %1 bytes @@ -1089,18 +1004,18 @@ Change will come back to the imported key. - + Inputs Inputs - - + + Copy Address Copy Address - + Outputs Outputs @@ -1108,105 +1023,95 @@ Change will come back to the imported key. main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - - Total balance - Total balance + + Offline + Offline - + Balance Balance - - Show Wallet Details - Show Wallet Details - - - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - - Offline - Offline - - - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1215,7 +1120,7 @@ Change will come back to the imported key. - + Preparing... Preparing... diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts new file mode 100644 index 0000000..c669a2d --- /dev/null +++ b/translations/floweepay-mobile_en.ts @@ -0,0 +1,775 @@ + + + + + About + + + About + About + + + + Help translate this app + Help translate this app + + + + License + License + + + + + Credits + Credits + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander and contributors + + + + Project Home + Project Home + + + + With git repository and issues tracker + With git repository and issues tracker + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Home + + + + Scheduled + Scheduled + + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + Sending + Sending + + + + Seen + Seen + + + + Rejected + Rejected + + + + AccountPageListItem + + + Sync Status + Sync Status + + + + Primary Wallet + Primary Wallet + + + + Backup information + Backup information + + + + Backup Details + Backup Details + + + + Wallet seed-phrase + Wallet seed-phrase + + + + Derivation Path + Derivation Path + + + + Starting Height + height refers to block-height + Starting Height + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Important</b>: Never share your seed-phrase with others! + + + + Wallet keys + Wallet keys + + + + Change Addresses + Change Addresses + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + + + + Used Addresses + Used Addresses + + + + Switches between unused and used Bitcoin addresses + Switches between unused and used Bitcoin addresses + + + + Addresses and Keys + Addresses and Keys + + + + AccountSelector + + + Your Wallets + Your Wallets + + + + last active + last active + + + + AccountSyncState + + + Status: Offline + Status: Offline + + + + AccountsList + + + Wallet Information + Wallet Information + + + + CurrencySelector + + + Select Currency + Select Currency + + + + GuiSettings + + + Display Settings + Display Settings + + + + Font sizing + Font sizing + + + + Unit + Unit + + + + Change Currency (%1) + Change Currency (%1) + + + + ImportWalletPage + + + Import Wallet + Import Wallet + + + + Create + Create + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + + + + Secret + The seed-phrase or private key + Secret + + + + Private key + description of type + Private key + + + + BIP 39 seed-phrase + description of type + BIP 39 seed-phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unrecognized word + + + + Name + Name + + + + Force Single Address + Force Single Address + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + + + + Alternate phrase + Alternate phrase + + + + Oldest Transaction + Oldest Transaction + + + + Derivation + Derivation + + + + MenuOverlay + + + Add Wallet + Add Wallet + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Address + + + + Start-height: %1 + Start-height: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + initializing connection + + + + Verifying peer + Verifying peer + + + + Peer for wallet + Peer for wallet + + + + Peer for wallet: %1 + Peer for wallet: %1 + + + + NewAccount + + + New Bitcoin Cash Wallet + New Bitcoin Cash Wallet + + + + Next + Next + + + + Create a New Wallet + Create a New Wallet + + + + Basic + Basic + + + + Private keys based + Property of a wallet + Private keys based + + + + Difficult to backup + Context: wallet type + Difficult to backup + + + + Great for brief usage + Context: wallet type + Great for brief usage + + + + HD wallet + HD wallet + + + + Seed-phrase based + Context: wallet type + Seed-phrase based + + + + Easy to backup + Context: wallet type + Easy to backup + + + + Most compatible + The most compatible wallet type + Most compatible + + + + Import Existing Wallet + Import Existing Wallet + + + + Import + Import + + + + Imports seed-phrase + Imports seed-phrase + + + + Imports private key + Imports private key + + + + New Wallet + New Wallet + + + + + Create + Create + + + + This creates a new empty wallet with simple multi-address capability + This creates a new empty wallet with simple multi-address capability + + + + + Name + Name + + + + Force Single Address + Force Single Address + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + + + + Derivation + Derivation + + + + New HD-Wallet + New HD-Wallet + + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + + + + PayWithQR + + + Approve Payment + Approve Payment + + + + Send All + all money in wallet + Send All + + + + Payment description + Payment description + + + + All Currencies + All Currencies + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + ReceiveTab + + + Receive + Receive + + + + Share this QR to receive + Share this QR to receive + + + + Checking + Checking + + + + Transaction high risk + Transaction high risk + + + + Payment Seen + Payment Seen + + + + Payment Accepted + Payment Accepted + + + + Payment Settled + Payment Settled + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + + Description + Description + + + + Clear + Clear + + + + Done + Done + + + + SendTransactionsTab + + + Send + Send + + + + Scan a QR code + Scan a QR code + + + + SlideToApprove + + + SLIDE TO SEND + SLIDE TO SEND + + + + StartupScreen + + + Welcome! + Welcome! + + + + Continue + Continue + + + + Moving the world towards a Bitcoin Cash economy + Moving the world towards a Bitcoin Cash economy + + + + I already have a wallet + I already have a wallet + + + + OR + OR + + + + I want to send funds to my new wallet + I want to send funds to my new wallet + + + + TransactionDetails + + + Transaction Details + Transaction Details + + + + Rejected + Rejected + + + + Unconfirmed + Unconfirmed + + + + Mined + Mined + + + + Coinbase + Coinbase + + + + CashFusion transaction + CashFusion transaction + + + + Size + Size + + + + %1 bytes + + %1 bytes + %1 bytes + + + + + TxInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 block ago + %1 blocks ago + + + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + Value then + Value then + + + + Value now + Value now + + + + Transaction Details + Transaction Details + + + -- 2.54.0 From 11c2847055f507ae49bd2c08b7e21a5d24fd3efe Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Mar 2023 18:04:01 +0100 Subject: [PATCH 0348/1428] Include mobile_en translations as well. English "translations" are just there to allow us to have a different form for singular statements: "1 hour left". --- CMakeLists.txt | 2 ++ translations/mobile-i18n.qrc | 1 + 2 files changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 39184f4..2952108 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,8 @@ if(NOT ANDROID) translations/floweepay-common_en.ts translations/floweepay-common_nl.ts translations/floweepay-common_pl.ts + + translations/floweepay-mobile_en.ts translations/floweepay-mobile_nl.ts translations/floweepay-mobile_pl.ts ) diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index b8d97b4..900e5e4 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -3,6 +3,7 @@ floweepay-common_en.qm floweepay-common_nl.qm floweepay-common_pl.qm + floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm -- 2.54.0 From 784ccfbf388bd7210467b2f6c753a011413e6ed5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Mar 2023 22:44:17 +0100 Subject: [PATCH 0349/1428] Allow historical date to be accurate The backend has the historical prices in its database, we ask for it by timestamp the by default it returns the nearest value. Typically within 24 hours. But if the database doesn't have the data, or its very inaccurate, this may casue confusion. So we now have a new API call that returns zero instead of an inaccurate historical price. Allowing us to prefer not showing anything over plain false data. --- guis/mobile/PriceDetails.qml | 8 +++--- src/PriceDataProvider.cpp | 11 +++++--- src/PriceDataProvider.h | 4 +-- src/PriceHistoryDataProvider.cpp | 43 +++++++++++++++++++++++++++----- src/PriceHistoryDataProvider.h | 18 ++++++++----- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 3951622..4b751f3 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -51,7 +51,7 @@ Item { property int days: 0 height: buddy.height width: buddy.width + 3 + main.width - visible: main.percentage > 0 + visible: main.priceThen > 0 Flowee.Label { id: buddy @@ -62,10 +62,8 @@ Item { id: main anchors.left: buddy.right anchors.leftMargin: 3 - property double percentage: { - var oldPrice = Fiat.historicalPrice(daysAgo); - return (root.currentPrice - oldPrice) / oldPrice * 100; - } + property int priceThen: Fiat.historicalPriceAccurate(daysAgo); + property double percentage: (root.currentPrice - priceThen) / priceThen * 100; text: { var sign = ""; if (percentage < 0) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index bd08eab..2f7a452 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -158,15 +158,20 @@ int PriceDataProvider::historicalPrice(const QDateTime ×tamp) const if (m_priceHistory.get() == nullptr) return m_currentPrice.price; - return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch()); + return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch(), + PriceHistoryDataProvider::Nearest); } -int PriceDataProvider::historicalPrice(int days) const +int PriceDataProvider::historicalPriceAccurate(int days) const { if (days < 0 || days > 3000) throw std::runtime_error("Invalid number of days ago"); QDateTime now = QDateTime::currentDateTimeUtc(); - return historicalPrice(now.addDays(days * -1)); + if (m_priceHistory.get() == nullptr) + return 0; + + return m_priceHistory->historicalPrice(now.addDays(days * -1).toSecsSinceEpoch(), + PriceHistoryDataProvider::Accurate); } QString PriceDataProvider::priceToStringSimple(int cents) const diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 8b6277c..1c697df 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,7 +70,7 @@ public: /** * Return the price a certain amount of days in the past */ - Q_INVOKABLE int historicalPrice(int days) const; + Q_INVOKABLE int historicalPriceAccurate(int days) const; /// return a string with the given price and needed decimal separator. /// Please note that the currency indicators are not included, unlike in formattedPrice() diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index fd72d8c..e84880e 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -88,7 +88,7 @@ void PriceHistoryDataProvider::addPrice(const QString ¤cy, uint32_t timest } } -int PriceHistoryDataProvider::historicalPrice(uint32_t timestamp) const +int PriceHistoryDataProvider::historicalPrice(const uint32_t timestamp, HistoricalPriceAccuracy hpa) const { const Currency *data = currencyData(m_currency); int answer = 0; @@ -96,15 +96,30 @@ int PriceHistoryDataProvider::historicalPrice(uint32_t timestamp) const return answer; uint32_t prevTimestamp = 0; + /* + * Historical price is downloaded from the feed and appended to the LOG file. + * + * We collect the LOG when it gets too big and copy it into a 'blob' after some time, + * aiming to have one value per day instead of one value every 5 minutes. + * + * the 'log' values are always oldest to newest. + * the 'blob' values are always older than the log ones, and also oldest to newest. + */ + // the log is per definition about newer values than the blob. if (!data->logValues.empty()) { prevTimestamp = data->logValues.front().first; answer = data->logValues.front().second; + // oldest(but newer than blob) to newest value for (auto i = data->logValues.begin(); i != data->logValues.end(); ++i) { if (i->first >= timestamp) { const int diff1 = timestamp - prevTimestamp; const int diff2 = i->first - timestamp; + + // we pick the value that is nearest the timestamp searched for. + // this is either the one just before, or the one directly after + // the log entry. if (diff1 > diff2) // closest one is 'i' answer = i->second; break; @@ -112,9 +127,19 @@ int PriceHistoryDataProvider::historicalPrice(uint32_t timestamp) const answer = i->second; prevTimestamp = i->first; } - if (timestamp >= prevTimestamp) + // special case, if the requested time is newer than + // all log entries. + if (timestamp >= prevTimestamp) { + // if we want accuracy, reject the request if our values are more than + // 12 hours old. + if (hpa == Accurate && timestamp - prevTimestamp > 3600 * 12) + return 0; return answer; + } } + // the 'answer' variable is now set to the first entry of the log (or zero if there was no log). + + // if we have a blob, check if we can improve our answer // with a closer timestamp if (!data->valueBlob.isEmpty()) { @@ -128,6 +153,8 @@ int PriceHistoryDataProvider::historicalPrice(uint32_t timestamp) const if (time >= timestamp) { const int diff1 = timestamp - prevTimestamp; const int diff2 = time - timestamp; + // same logic as above with the log, + // use the recorded one that is closest in time. if (diff1 > diff2) // closest one is 'i' answer = value; break; @@ -135,10 +162,14 @@ int PriceHistoryDataProvider::historicalPrice(uint32_t timestamp) const answer = value; prevTimestamp = time; } - // if (timestamp >= prevTimestamp) - return answer; + + // if the timestamp requested is older than any we know. + // And the user wants an accurate value, then we don't just return the + // oldest one we have but we return an empty one. + if (timestamp < prevTimestamp + && hpa == Accurate && prevTimestamp - timestamp > 3600 * 48) + return 0; } - // TODO maybe check if the value is between the blob and the log return answer; } diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index c043133..aaf2c74 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,15 +18,15 @@ #ifndef PRICEHISTORYDATAPROVIDER_H #define PRICEHISTORYDATAPROVIDER_H -#include -#include -#include #include - #include +#include + #include +class QFile; + class PriceHistoryDataProvider : public QObject { Q_OBJECT @@ -35,7 +35,13 @@ public: void addPrice(const QString ¤cy, uint32_t timestamp, int price); - int historicalPrice(uint32_t timestamp) const; + /// Used in historicalPrice() + enum HistoricalPriceAccuracy { + Accurate, ///< Return zero if we don't have historical data of that day + Nearest ///< Return the nearest known price. + }; + + int historicalPrice(uint32_t timestamp, HistoricalPriceAccuracy = Nearest) const; QString currencyName() const; void setCurrency(const QString &newCurrency); -- 2.54.0 From 0659c99b37221169fedd24275a3c05af921b2e7f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Mar 2023 22:53:56 +0100 Subject: [PATCH 0350/1428] Make 'price then' use accurate prices. If we don't have price info near the mining date, don't show an inaccurate version. --- guis/mobile/TxInfoSmall.qml | 4 +++- src/PriceDataProvider.cpp | 10 +++++++++- src/PriceDataProvider.h | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 0a58a68..221b555 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -109,6 +109,8 @@ GridLayout { let diff = model.fundsOut - model.fundsIn; if (diff < 0 && diff > -1000) // then the diff is likely just fees. return false; + if (valueThenLabel.fiatPrice === 0) + return false; return true; } text: qsTr("Value then") + ":" @@ -117,7 +119,7 @@ GridLayout { Layout.fillWidth: true id: valueThenLabel visible: priceAtMining.visible - property int fiatPrice: visible ? Fiat.historicalPrice(model.date) : 0; + property int fiatPrice: Fiat.historicalPriceAccurate(model.date) text: Fiat.formattedPrice(Math.abs(model.fundsOut - model.fundsIn), fiatPrice) } Flowee.Label { diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 2f7a452..fab08ad 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -159,7 +159,15 @@ int PriceDataProvider::historicalPrice(const QDateTime ×tamp) const return m_currentPrice.price; return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch(), - PriceHistoryDataProvider::Nearest); + PriceHistoryDataProvider::Nearest); +} + +int PriceDataProvider::historicalPriceAccurate(const QDateTime ×tamp) const +{ + if (m_priceHistory.get() == nullptr) + return 0; + return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch(), + PriceHistoryDataProvider::Accurate); } int PriceDataProvider::historicalPriceAccurate(int days) const diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 1c697df..25cfcd2 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -68,7 +68,12 @@ public: Q_INVOKABLE int historicalPrice(const QDateTime ×tamp) const; /** - * Return the price a certain amount of days in the past + * Return the price at a certain time in the past, or 0 if none are on record. + */ + Q_INVOKABLE int historicalPriceAccurate(const QDateTime ×tamp) const; + + /** + * Return the price a certain amount of days in the past, or 0 if none are on record. */ Q_INVOKABLE int historicalPriceAccurate(int days) const; -- 2.54.0 From 1c209a16bcf1e585e8a96d0d76ad87c07971e294 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 10 Mar 2023 22:16:37 +0100 Subject: [PATCH 0351/1428] Start new screen: build transaction. --- guis/mobile.qrc | 1 + guis/mobile/PayToOthers.qml | 236 ++++++++++++++++++++++++++++ guis/mobile/SendTransactionsTab.qml | 5 + 3 files changed, 242 insertions(+) create mode 100644 guis/mobile/PayToOthers.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 2c8c9eb..0d80b39 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -38,6 +38,7 @@ mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml mobile/QRScannerOverlay.qml + mobile/PayToOthers.qml mobile/PayWithQR.qml mobile/SlideToApprove.qml mobile/ReceiveTab.qml diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml new file mode 100644 index 0000000..590e555 --- /dev/null +++ b/guis/mobile/PayToOthers.qml @@ -0,0 +1,236 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay; + +Page { + id: root + headerText: qsTr("Create Payment") + + Item { // data + QRScanner { + id: scanner + scanType: QRScanner.PaymentDetails + /*onFinished: { + var rc = scanResult + if (rc === "") { // scanning failed + thePile.pop(); + } + else { + payment.targetAddress = rc + priceBch.forceActiveFocus(); + } + } */ + } + Payment { + id: payment + account: portfolio.current + fiatPrice: Fiat.price + } + + AccountSelector { + id: accountSelector + width: root.width + x: -10 // to correct the indent added in the fullPage + y: (root.height - height) / 2 + onSelectedAccountChanged: payment.account = selectedAccount + selectedAccount: payment.account + } + + // Extra page to create new details. + Component { + id: paymentDetailSelector + Page { + headerText: qsTr("Add Payment Detail", "page title") + Flickable { + anchors.fill: parent + contentHeight: col.height + Column { + id: col + width: parent.width + TextButton { + text: "Add Destination" + onClicked: { + payment.addExtraOutput(); + thePile.pop(); + } + } + } + } + } + } + + // listitem of PaymentDetail showing payment-destination + Component { + id: destinationFields + Column { + property QtObject paymentDetail: parent.paymentDetail + property Component edit: destinationEditPage + width: parent.width + spacing: 6 + + /* This page just shows the results, editing is done + * on its own page. + */ + Flowee.Label { + text: qsTr("Addressee") + ":" + } + Flowee.LabelWithClipboard { + width: parent.width + text: paymentDetail.address + menuText: qsTr("Copy Address") + } + Flowee.Label { + text: qsTr("Amount")+ ":" + } + Flowee.BitcoinAmountLabel { + value: paymentDetail.paymentAmount + colorize: false + } + } + } + + Component { + id: destinationEditPage + Page { + headerText: qsTr("Edit Destination") + } + } + } + + Flickable { + id: contentArea + anchors.fill: parent + contentHeight: mainColumn.height + contentWidth: width + clip: true + Column { + id: mainColumn + width: parent.width + spacing: 10 + + Repeater { + model: payment.details + delegate: Item { + id: listItem + width: mainColumn.width + height: loader.height + 6 + + Loader { + id: loader + width: parent.width + height: status === Loader.Ready ? item.implicitHeight : 0 + sourceComponent: { + if (modelData.type === Payment.PayToAddress) + return destinationFields + if (modelData.type === Payment.InputSelector) + return inputFields + return null; // should never happen + } + onLoaded: item.paymentDetail = modelData + } + + // delete-detail interface + Rectangle { + id: leftBackground + property bool aboutToDelete: parent.x > 90 + anchors.right: loader.left + color: aboutToDelete ? "red" : "#00000000" // TODO make change happen earlier. + width: 300 + height: parent.height + Behavior on color { ColorAnimation { duration: 200 } } + Rectangle { + id: trashcan + color: "orange" // TODO replace this with an icon + width: 40 + height: 40 + y: 10 + x: { + let newX = parent.width - width; + let additional = Math.max(0, Math.min(listItem.x - width, 16)) + return newX - additional; + } + } + } + + Rectangle { + id: editIcon + color: "blue" + width: 40 + height: 40 + y: 10 + x: { + let additional = Math.max(0, Math.min(listItem.x * -1 - width, 16)) + return parent.width + additional; + } + } + + onXChanged: { + if (x < -75 && dragHandler.editStarted === false) { + dragHandler.editStarted = true; + dragHandler.enabled = false; // stop dragging + thePile.push(loader.item.edit) + } + } + + DragHandler { + id: dragHandler + property bool editStarted: false; + yAxis.enabled: false + xAxis.enabled: true + xAxis.minimum: -140 // swipe right + xAxis.maximum: 200 // swipe left + onActiveChanged: { + if (active) { + editStarted = false; + return; + } + if (editStarted) { + enabled = true; + parent.x= 0; + return; + } + // take action on user releasing the drag. + if (leftBackground.aboutToDelete) + payment.remove(modelData); + else // reset pos + parent.x = 0 + } + } + Behavior on x { NumberAnimation { duration: 100 } } + } + } + + TextButton { + text: qsTr("Add Detail...") + showPageIcon: true + onClicked: thePile.push(paymentDetailSelector); + } + + } + + // Add 'Next' button in header + // Add "Next" button here. + // on 'next' prepare and go to 'swipe to send' screen. + // we likely want to have transaction details shown on that screen. + } + +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 226581b..9adf278 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -32,6 +32,11 @@ FocusScope { showPageIcon: true onClicked: thePile.push("PayWithQR.qml") } + TextButton { + text: qsTr("Build transaction") + showPageIcon: true + onClicked: thePile.push("PayToOthers.qml") + } /* TextButton { text: qsTr("My Wallets") -- 2.54.0 From 7e3542e32d75133403687ba3b744ce5030fa4e27 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 11 Mar 2023 13:28:29 +0100 Subject: [PATCH 0352/1428] Re-do interaction. The swipes now makes much more sense and avoids several UX issues. --- guis/mobile/PayToOthers.qml | 134 +++++++++++++++++++++++++----------- 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 590e555..38a42aa 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -71,6 +71,7 @@ Page { onClicked: { payment.addExtraOutput(); thePile.pop(); + thePile.push(destinationEditPage); } } } @@ -112,6 +113,8 @@ Page { id: destinationEditPage Page { headerText: qsTr("Edit Destination") + + } } } @@ -148,15 +151,78 @@ Page { onLoaded: item.paymentDetail = modelData } + Item { + // an invisible item that is used to handle the drag events. + id: dragItem + width: parent.width + height: parent.height + + property bool editStarted: false + onXChanged: { + /* When we move, we move the icons etc with us. + */ + if (x < 0) { + leftArea.x = leftArea.width * -1 // out of screen + // moving left, opening the edit option + loader.x = x; + let a = x * -1; + editIcon.x = width - a + Math.max(0, a - editIcon.width); + if (!editStarted && x < -100) { + editStarted = true; + thePile.push(loader.item.edit) + } + } + else { + editIcon.x = width; + // moving right, opening the trashcan option + let a = x; + let base = Math.min(40, a); + let rest = a - base; + deleteTimer.running = rest > 90; + if (rest < 90) + deleteTimer.deleteTriggered = false + leftBackground.opacity = Math.max(rest - 40) / 100; + rest = Math.min(60, rest) / 4; // slower + leftArea.x = leftArea.width * -1 + base + rest; + loader.x = base + rest; + } + } + + DragHandler { + id: dragHandler + // property bool editStarted: false; + yAxis.enabled: false + xAxis.enabled: true + xAxis.maximum: 200 // swipe left + xAxis.minimum: -140 // swipe right + onActiveChanged: { + if (active) { + parent.editStarted = false; + return; + } + // take action on user releasing the drag. + if (deleteTimer.deleteTriggered) { + deleteTimer.deleteTriggered = false; + payment.remove(modelData); + } + parent.x = 0; + } + } + } + // delete-detail interface - Rectangle { - id: leftBackground - property bool aboutToDelete: parent.x > 90 - anchors.right: loader.left - color: aboutToDelete ? "red" : "#00000000" // TODO make change happen earlier. + Item { + id: leftArea width: 300 height: parent.height - Behavior on color { ColorAnimation { duration: 200 } } + x: -width; + Rectangle { + id: leftBackground + opacity: 0 + anchors.fill: parent + color: "red" + } + Rectangle { id: trashcan color: "orange" // TODO replace this with an icon @@ -165,10 +231,24 @@ Page { y: 10 x: { let newX = parent.width - width; - let additional = Math.max(0, Math.min(listItem.x - width, 16)) + let moved = parent.x + parent.width; + let additional = Math.max(0, Math.min(moved - width, 8)); return newX - additional; } } + + Timer { + /* + The intention of this timer is that the user can't just swipe hard + and suddenly lose their data. + We need to have the user actually see the red for half a second + before we delete. + */ + id: deleteTimer + interval: 400 + property bool deleteTriggered: false + onTriggered: deleteTriggered = true + } } Rectangle { @@ -183,42 +263,18 @@ Page { } } - onXChanged: { - if (x < -75 && dragHandler.editStarted === false) { - dragHandler.editStarted = true; - dragHandler.enabled = false; // stop dragging - thePile.push(loader.item.edit) - } - } - - DragHandler { - id: dragHandler - property bool editStarted: false; - yAxis.enabled: false - xAxis.enabled: true - xAxis.minimum: -140 // swipe right - xAxis.maximum: 200 // swipe left - onActiveChanged: { - if (active) { - editStarted = false; - return; - } - if (editStarted) { - enabled = true; - parent.x= 0; - return; - } - // take action on user releasing the drag. - if (leftBackground.aboutToDelete) - payment.remove(modelData); - else // reset pos - parent.x = 0 - } - } Behavior on x { NumberAnimation { duration: 100 } } } } + TextButton { + text: qsTr("Add Destination") + showPageIcon: true + onClicked: { + payment.addExtraOutput(); + thePile.push(destinationEditPage); + } + } TextButton { text: qsTr("Add Detail...") showPageIcon: true -- 2.54.0 From 9daefb38d2270dfb97b9872ccfc69a6b5b3ccf90 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 11 Mar 2023 22:05:15 +0100 Subject: [PATCH 0353/1428] Start work on the destination edit page --- guis/mobile/PayToOthers.qml | 176 +++++++++++++++++++++++++++++++++++- src/Payment.cpp | 14 ++- src/Payment.h | 4 +- 3 files changed, 184 insertions(+), 10 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 38a42aa..350924c 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -21,6 +21,27 @@ import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; + +/* + * Components embedded in this file: + Editors: + destinationEditPage + + editors have the property 'paymentDetail' + property QtObject paymentDetail: null + + ListItems: + destinationFields + + list items have the property 'edit' to link to Editors + property Component edit: [something] + list items have the property 'paymentDetail' + property QtObject paymentDetail: null + + Other: + paymentDetailSelector + */ + Page { id: root headerText: qsTr("Create Payment") @@ -69,9 +90,10 @@ Page { TextButton { text: "Add Destination" onClicked: { - payment.addExtraOutput(); + var detail = payment.addExtraOutput(); thePile.pop(); thePile.push(destinationEditPage); + thePile.currentItem.paymentDetail = detail; } } } @@ -112,8 +134,154 @@ Page { Component { id: destinationEditPage Page { - headerText: qsTr("Edit Destination") + property QtObject paymentDetail: null + headerText: qsTr("Edit Destination") + Flowee.Label { + id: destinationLabel + text: qsTr("Bitcoin Cash Address") + ":" + } + + Flowee.MultilineTextField { + id: destination + anchors.top: destinationLabel.bottom + anchors.left: parent.left + anchors.right: parent.right + focus: true + property bool addressOk: (addressType === Wallet.CashPKH + || addressType === Wallet.CashSH) + || (paymentDetail.forceLegacyOk + && (addressType === Wallet.LegacySH + || addressType === Wallet.LegacyPKH)); + property var addressType: Pay.identifyString(text); + onActiveFocusChanged: updateColor(); + onAddressOkChanged: { + updateColor(); + addressInfo.createInfo(); + } + text: paymentDetail.address + onTextChanged: { + paymentDetail.address = text + updateColor(); + addressInfo.createInfo(); + } + + function updateColor() { + if (!activeFocus && text !== "" && !addressOk) + color = Pay.useDarkSkin ? "#ff6568" : "red" + else + color = palette.windowText + } + } + Flowee.LabelWithClipboard { + id: nativeLabel + width: parent.width + anchors.top: destination.bottom + anchors.topMargin: 10 + + visible: text !== "" + text: { + let string = paymentDetail.formattedTarget + let index = string.indexOf(":"); + if (index >= 0) { + string = string.substr(index + 1); // cut off the prefix + } + return string; + } + clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: + font.italic: true + menuText: qsTr("Copy Address") + } + Item { + id: addressInfo + property QtObject info: null + visible: info != null + anchors.top: nativeLabel.bottom + width: parent.width + height: infoLabel.height + + function createInfo() { + if (destination.addressOk) { + var address = paymentDetail.formattedTarget + if (address === "") // it didn't need reformatting + address = paymentDetail.address + info = Pay.researchAddress(address, addressInfo) + } + else { + delete info; + info = null; + } + } + + Flowee.Label { + id: infoLabel + anchors.right: parent.right + font.italic: true + text: { + var info = addressInfo.info + if (info == null) + return ""; + if (portfolio.current.id === info.accountId) + return qsTr("self", "payment to self") + return info.accountName + } + } + } + + Rectangle { + color: "red" + radius: 15 + width: parent.width + height: warningColumn.height + 20 + anchors.top: nativeLabel.bottom + // BTC address entered warning. + visible: (destination.addressType === Wallet.LegacySH + || destination.addressType === Wallet.LegacyPKH) + && paymentDetail.forceLegacyOk === false; + + Column { + id: warningColumn + x: 10 + y: 10 + width: parent.width - 20 + spacing: 10 + Flowee.Label { + font.bold: true + font.pixelSize: warning.font.pixelSize * 1.2 + text: qsTr("Warning") + color: "white" + anchors.horizontalCenter: parent.horizontalCenter + } + Flowee.Label { + id: warning + width: parent.width + color: "white" + text: qsTr("This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address?") + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Item { + width: parent.width + height: okButton.height + QQC2.Button { + id: okButton + anchors.right: parent.right + text: qsTr("I am certain") + onClicked: paymentDetail.forceLegacyOk = true + } + } + } + } + + + + + + +/* TODO + * Amount in bch / eur + * + * Max button. + */ } } @@ -170,6 +338,7 @@ Page { if (!editStarted && x < -100) { editStarted = true; thePile.push(loader.item.edit) + thePile.currentItem.paymentDetail = modelData; } } else { @@ -271,8 +440,9 @@ Page { text: qsTr("Add Destination") showPageIcon: true onClicked: { - payment.addExtraOutput(); + var detail = payment.addExtraOutput(); thePile.push(destinationEditPage); + thePile.currentItem.paymentDetail = detail; } } TextButton { diff --git a/src/Payment.cpp b/src/Payment.cpp index fe17c12..5b3d984 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -517,27 +517,31 @@ Payment::BroadcastStatus Payment::broadcastStatus() const return TxWaiting; } -void Payment::addExtraOutput() +PaymentDetail *Payment::addExtraOutput() { // only the last in the sequence can have 'max' for (auto d : m_paymentDetails) { if (d->isOutput()) d->toOutput()->setMaxAllowed(false); } - addDetail(new PaymentDetailOutput(this)); + auto detail = new PaymentDetailOutput(this); + addDetail(detail); + return detail; } -void Payment::addInputSelector() +PaymentDetail *Payment::addInputSelector() { // only one input selector allowed for (auto d : m_paymentDetails) { if (d->isInputs()) { // un-collapse, but not add d->setCollapsed(false); - return; + return nullptr; } } - addDetail(new PaymentDetailInputs(this)); + auto detail = new PaymentDetailInputs(this); + addDetail(detail); + return detail; } void Payment::remove(PaymentDetail *detail) diff --git a/src/Payment.h b/src/Payment.h index 9530db6..ca23cc8 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -153,8 +153,8 @@ public: Q_INVOKABLE void prepare(); Q_INVOKABLE void broadcast(); Q_INVOKABLE void reset(); - Q_INVOKABLE void addExtraOutput(); - Q_INVOKABLE void addInputSelector(); + Q_INVOKABLE PaymentDetail* addExtraOutput(); + Q_INVOKABLE PaymentDetail* addInputSelector(); Q_INVOKABLE void remove(PaymentDetail *detail); Q_INVOKABLE void decrypt(const QString &password); -- 2.54.0 From 8bc4e70d09a792a3d4bbc7e0e43f048079ff824f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 11 Mar 2023 22:44:27 +0100 Subject: [PATCH 0354/1428] Move widgets to their own files Allow them to be re-used. --- guis/mobile.qrc | 2 + guis/mobile/NumericKeyboardWidget.qml | 80 ++++++++++ guis/mobile/PayWithQR.qml | 212 +------------------------- guis/mobile/PriceInputWidget.qml | 188 +++++++++++++++++++++++ 4 files changed, 278 insertions(+), 204 deletions(-) create mode 100644 guis/mobile/NumericKeyboardWidget.qml create mode 100644 guis/mobile/PriceInputWidget.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 0d80b39..9d8fe04 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -54,5 +54,7 @@ mobile/StartupScreen.qml mobile/ImportWalletPage.qml mobile/PriceDetails.qml + mobile/PriceInputWidget.qml + mobile/NumericKeyboardWidget.qml diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml new file mode 100644 index 0000000..9eada77 --- /dev/null +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -0,0 +1,80 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 + +Flow { + id: root + anchors.bottom: slideToApprove.top + anchors.bottomMargin: 15 + width: parent.width + enabled: !payment.details[0].maxSelected + Repeater { + model: 12 + delegate: Item { + width: root.width / 3 + height: textLabel.height + 20 + QQC2.Label { + id: textLabel + anchors.centerIn: parent + font.pixelSize: 28 + text: { + if (index < 9) + return index + 1; + if (index === 9) + return Qt.locale().decimalPoint + if (index === 10) + return "0" + return ""; // index === 11, the backspace. + } + // make dim when not enabled. + color: { + if (!enabled) + return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 2) : Qt.lighter(palette.buttonText, 2); + return palette.windowText; + } + } + Image { + visible: index === 11 + anchors.centerIn: parent + source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" + width: 27 + height: 17 + opacity: enabled ? 1 : 0.4 + } + + MouseArea { + anchors.fill: parent + onClicked: { + let editor = priceInput.editor; + if (index < 9) // these are digits + var shake = !editor.insertNumber("" + (index + 1)); + else if (index === 9) + shake = !editor.addSeparator() + else if (index === 10) + shake = !editor.insertNumber("0"); + else + shake = !editor.backspacePressed(); + if (shake) + priceInput.shake(); + } + } + } + } +} + diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index fff4815..f919c2e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -77,55 +77,17 @@ Page { } } - // if true, the bitcoin value is provided by the user (or QR), and the fiat follows - // if false, the user edits the fiat price and the bitcoin value is calculated. - // Notice that 'send all' overrules both and gets the data from the wallet-total - property bool fiatFollowsSats: true - - Flowee.BitcoinValueField { - id: priceBch - y: root.fiatFollowsSats ? 5 : 68 - value: payment.paymentAmount - focus: true - fontPixelSize: size - property double size: fiatFollowsSats ? 38 : commentLabel.font.pixelSize* 0.8 - onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true - Behavior on size { NumberAnimation { } } - Behavior on y { NumberAnimation { } } - Flowee.ObjectShaker { id: bchShaker } // 'shake' to give feedback on mistakes - - // this unchecks 'max' on user editing of the value - onValueEdited: payment.paymentAmount = value - } - MouseArea { - /* Since the valueField is centred but only allows clicking on its active surface, - we provide this one to make it possible to click on the full width and activate it. - */ - width: root.width - height: priceBch.height - y: priceBch.y - onClicked: priceBch.forceActiveFocus(); - } - - Flowee.FiatValueField { - id: priceFiat - value: payment.paymentAmountFiat - y: root.fiatFollowsSats ? 68 : 5 - focus: true - fontPixelSize: size - property double size: !fiatFollowsSats ? 38 : commentLabel.font.pixelSize * 0.8 - onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false - Behavior on size { NumberAnimation { } } - Behavior on y { NumberAnimation { } } - Flowee.ObjectShaker { id: fiatShaker } - onValueEdited: payment.paymentAmountFiat = value + PriceInputWidget { + id: priceInput + width: parent.width } Flowee.Label { id: commentLabel text: qsTr("Payment description") + ":" visible: userComment.text !== "" - y: 100 + anchors.top: priceInput.bottom + anchors.topMargin: 10 } Flowee.Label { id: userComment @@ -136,120 +98,13 @@ Page { anchors.top: commentLabel.bottom } - Rectangle { - id: inputChooser - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: userComment.bottom - anchors.topMargin: 10 - border.width: 1 - border.color: palette.midlight - color: palette.light - width: inputs.width + 20 - height: 40 - radius: 15 - - Rectangle { - color: palette.highlight - opacity: 0.3 - radius: 6 - width: 35 - height: parent.height - 4 - y: 2 - x: root.fiatFollowsSats ? 5 : 45 - - Behavior on x { NumberAnimation { } } - } - - Row { - id: inputs - x: 10 - y: 7.5 - height: parent.height - spacing: 4 - Image { - id: logo - source: "qrc:/bch.svg" - width: 25 - height: 25 - smooth: true - - MouseArea { - anchors.fill: parent - onClicked: priceBch.forceActiveFocus(); - } - } - Item { width: 8; height: 1 } // spacer - - Flowee.Label { - text: (Fiat.currencySymbolPost + Fiat.currencySymbolPrefix).trim() - width: 24 - font.pixelSize: 32 - horizontalAlignment: Text.AlignRight - anchors.baseline: logo.bottom - - MouseArea { - anchors.fill: parent - onClicked: priceFiat.forceActiveFocus(); - } - } - Rectangle { - width: 1 - y: inputs.y * -1 - height: parent.height - color: palette.dark - } - - Flowee.HamburgerMenu { - y: 6 - MouseArea { - anchors.fill: parent - anchors.margins: -12 - anchors.rightMargin: -25 - onClicked: languageMenu.open() - } - - Loader { - id: languageMenu - function open() { - sourceComponent = languageMenuComponent - } - onLoaded: item.open(); - } - - Component { - id: languageMenuComponent - - QQC2.Menu { - Repeater { - model: Pay.recentCountries() - QQC2.MenuItem { - text: { - var loc = Qt.locale(modelData); - return loc.currencySymbol(Locale.CurrencySymbol) + " " - + loc.currencySymbol(Locale.CurrencyDisplayName); - } - onClicked: Pay.setCountry(modelData); - } - } - QQC2.MenuItem { - text: qsTr("All Currencies") - onClicked: thePile.push("./CurrencySelector.qml") - } - - onVisibleChanged: if (!visible) languageMenu.sourceComponent = undefined; // unload us - } - } - } - Item { width: 5; height: 1 } // spacer - } - } - Flowee.Label { id: errorLabel text: payment.error visible: payment.isValid color: Pay.useDarkSkin ? "#cc5454" : "#6a0c0c" anchors.bottom: walletNameBackground.top + anchors.bottomMargin: 6 wrapMode: Text.Wrap width: parent.width } @@ -312,65 +167,14 @@ Page { } } - Flow { + NumericKeyboardWidget { id: numericKeyboard anchors.bottom: slideToApprove.top anchors.bottomMargin: 15 width: parent.width enabled: !payment.details[0].maxSelected - Repeater { - model: 12 - delegate: Item { - width: numericKeyboard.width / 3 - height: textLabel.height + 20 - QQC2.Label { - id: textLabel - anchors.centerIn: parent - font.pixelSize: 28 - text: { - if (index < 9) - return index + 1; - if (index === 9) - return Qt.locale().decimalPoint - if (index === 10) - return "0" - return ""; // index === 11, the backspace. - } - // make dim when not enabled. - color: { - if (!enabled) - return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 2) : Qt.lighter(palette.buttonText, 2); - return palette.windowText; - } - } - Image { - visible: index === 11 - anchors.centerIn: parent - source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" - width: 27 - height: 17 - opacity: enabled ? 1 : 0.4 - } - - MouseArea { - anchors.fill: parent - onClicked: { - let editor = fiatFollowsSats ? priceBch.money : priceFiat.money; - if (index < 9) // these are digits - var shake = !editor.insertNumber("" + (index + 1)); - else if (index === 9) - shake = !editor.addSeparator() - else if (index === 10) - shake = !editor.insertNumber("0"); - else - shake = !editor.backspacePressed(); - if (shake) - fiatFollowsSats ? bchShaker.start() : fiatShaker.start(); - } - } - } - } } + SlideToApprove { id: slideToApprove anchors.bottom: parent.bottom diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml new file mode 100644 index 0000000..bb6153e --- /dev/null +++ b/guis/mobile/PriceInputWidget.qml @@ -0,0 +1,188 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee + + +QQC2.Control { + id: root + // if true, the bitcoin value is provided by the user (or QR), and the fiat follows + // if false, the user edits the fiat price and the bitcoin value is calculated. + // Notice that 'send all' overrules both and gets the data from the wallet-total + property bool fiatFollowsSats: true + + property var editor: fiatFollowsSats ? priceBch.money : priceFiat.money; + + function shake() { + if (fiatFollowsSats) + bchShaker.start(); + else + fiatShaker.start(); + } + + implicitHeight: 140 + height: implicitHeight + + Flowee.BitcoinValueField { + id: priceBch + y: root.fiatFollowsSats ? 5 : 68 + value: payment.paymentAmount + focus: true + fontPixelSize: size + property double size: fiatFollowsSats ? 38 : root.font.pixelSize* 0.8 + onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true + Behavior on size { NumberAnimation { } } + Behavior on y { NumberAnimation { } } + Flowee.ObjectShaker { id: bchShaker } // 'shake' to give feedback on mistakes + + // this unchecks 'max' on user editing of the value + onValueEdited: payment.paymentAmount = value + } + MouseArea { + /* Since the valueField is centred but only allows clicking on its active surface, + we provide this one to make it possible to click on the full width and activate it. + */ + width: root.width + height: priceBch.height + y: priceBch.y + onClicked: priceBch.forceActiveFocus(); + } + + Flowee.FiatValueField { + id: priceFiat + value: payment.paymentAmountFiat + y: root.fiatFollowsSats ? 68 : 5 + focus: true + fontPixelSize: size + property double size: !fiatFollowsSats ? 38 : root.font.pixelSize * 0.8 + onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false + Behavior on size { NumberAnimation { } } + Behavior on y { NumberAnimation { } } + Flowee.ObjectShaker { id: fiatShaker } + onValueEdited: payment.paymentAmountFiat = value + } + + Rectangle { + id: inputChooser + y: 100 + height: 40 + anchors.horizontalCenter: parent.horizontalCenter + border.width: 1 + border.color: palette.midlight + color: palette.light + width: inputs.width + 20 + radius: 15 + + Rectangle { + color: palette.highlight + opacity: 0.3 + radius: 6 + width: 35 + height: parent.height - 4 + y: 2 + x: root.fiatFollowsSats ? 5 : 45 + + Behavior on x { NumberAnimation { } } + } + + Row { + id: inputs + x: 10 + y: 7.5 + height: parent.height + spacing: 4 + Image { + id: logo + source: "qrc:/bch.svg" + width: 25 + height: 25 + smooth: true + + MouseArea { + anchors.fill: parent + onClicked: priceBch.forceActiveFocus(); + } + } + Item { width: 8; height: 1 } // spacer + + Flowee.Label { + text: (Fiat.currencySymbolPost + Fiat.currencySymbolPrefix).trim() + width: 24 + font.pixelSize: 32 + horizontalAlignment: Text.AlignRight + anchors.baseline: logo.bottom + + MouseArea { + anchors.fill: parent + onClicked: priceFiat.forceActiveFocus(); + } + } + Rectangle { + width: 1 + y: inputs.y * -1 + height: parent.height + color: palette.dark + } + + Flowee.HamburgerMenu { + y: 6 + MouseArea { + anchors.fill: parent + anchors.margins: -12 + anchors.rightMargin: -25 + onClicked: languageMenu.open() + } + + Loader { + id: languageMenu + function open() { + sourceComponent = languageMenuComponent + } + onLoaded: item.open(); + } + + Component { + id: languageMenuComponent + + QQC2.Menu { + Repeater { + model: Pay.recentCountries() + QQC2.MenuItem { + text: { + var loc = Qt.locale(modelData); + return loc.currencySymbol(Locale.CurrencySymbol) + " " + + loc.currencySymbol(Locale.CurrencyDisplayName); + } + onClicked: Pay.setCountry(modelData); + } + } + QQC2.MenuItem { + text: qsTr("All Currencies") + onClicked: thePile.push("./CurrencySelector.qml") + } + + onVisibleChanged: if (!visible) languageMenu.sourceComponent = undefined; // unload us + } + } + } + Item { width: 5; height: 1 } // spacer + } + } +} + -- 2.54.0 From 9c79a7402dda67d1acfab57a3dd4a784d863462c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 11 Mar 2023 22:55:47 +0100 Subject: [PATCH 0355/1428] Use the shared widgets --- guis/mobile/PayToOthers.qml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 350924c..ebb1263 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -271,18 +271,20 @@ Page { } } } + // TODO Max button. - - - - - -/* TODO - * Amount in bch / eur - * - * Max button. - */ - + PriceInputWidget { + id: priceInput + width: parent.width + anchors.bottom: numericKeyboard.top + } + NumericKeyboardWidget { + id: numericKeyboard + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 + width: parent.width + enabled: !paymentDetail.maxSelected + } } } } -- 2.54.0 From a6e9001e4c11cf7ac444f3f28fec1a17ea33a305 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 20:37:39 +0100 Subject: [PATCH 0356/1428] Fix the cent-less currency display This partly reverts earlier "fix" from 9b87590 and fixes the problem in the right place. fixes #13 --- src/PriceDataProvider.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index fab08ad..0a73741 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -118,7 +118,6 @@ QString PriceDataProvider::formattedPrice(int fiatValue) const QString actualPrice = centsPrice.mid(offset, length); - QString dummy; // used when we inserted thousand separators. QStringRef needs the thing it refers to to outlive it. // group size is 3 if (length > 3 && length <= 15) { // lets insert some thousands separators. @@ -133,8 +132,7 @@ QString PriceDataProvider::formattedPrice(int fiatValue) const assert(actualPrice.at(i).unicode() < 127); // only digits buf[i + add] = actualPrice.at(i).unicode(); } - dummy = QString::fromLatin1(buf); - actualPrice = dummy.left(dummy.size() - (m_displayCents ? 0 : 2)); + actualPrice = QString::fromLatin1(buf); } if (m_displayCents) { @@ -188,13 +186,12 @@ QString PriceDataProvider::priceToStringSimple(int cents) const if (m_displayCents) { const QChar comma = QLocale::system().decimalPoint().at(0); if (cents < 10) - value = "0" + value; + return "0" % comma % "0" % value; if (cents < 100) - value = "0" % comma % value; - else - value = value.left(value.size() - 2) % comma % value.right(2); + return"0" % comma % value; + return value.left(value.size() - 2) % comma % value.right(2); } - return value; + return value.left(value.size() - 2); } void PriceDataProvider::fetch() -- 2.54.0 From 8b3a85a88b5291df61bd596284e5a34c5e223d6b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 12:58:41 +0100 Subject: [PATCH 0357/1428] Synchronize property names This makes the payment detail (output) have the same property name as the payment itself which was supposed to just be a proxy for a single-output payment. Consistency is good. --- guis/desktop/SendTransactionPane.qml | 8 ++++---- src/Payment.cpp | 11 ++++------- src/PaymentDetailOutput.cpp | 15 ++++----------- src/PaymentDetailOutput_p.h | 7 +++---- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 612f46f..39e92a1 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -277,7 +277,7 @@ Item { + " " + Pay.unitName; } else { - amount = Fiat.formattedPrice(paymentDetail.fiatAmount) + amount = Fiat.formattedPrice(paymentDetail.paymentAmountFiat) } return qsTr("%1 to %2", "summary text to pay X-euro to address M") @@ -375,8 +375,8 @@ Item { Flowee.FiatValueField { id: fiatValueField visible: Fiat.price > 0 - onValueEdited: destinationPane.paymentDetail.fiatAmount = value - value: destinationPane.paymentDetail.fiatAmount + onValueEdited: destinationPane.paymentDetail.paymentAmountFiat = value + value: destinationPane.paymentDetail.paymentAmountFiat } Flowee.CheckBox { id: amountSelector diff --git a/src/Payment.cpp b/src/Payment.cpp index fe17c12..bc22834 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -72,7 +72,7 @@ void Payment::setPaymentAmount(double amount) void Payment::setPaymentAmountFiat(int amount) { - soleOut()->setFiatAmount(amount); + soleOut()->setPaymentAmountFiat(amount); doAutoPrepare(); } @@ -394,13 +394,10 @@ void Payment::addDetail(PaymentDetail *detail) m_paymentDetails.append(detail); } connect (detail, SIGNAL(validChanged()), this, SIGNAL(validChanged())); - if (detail->isOutput()) { + if (detail->isOutput()) connect (detail, SIGNAL(paymentAmountChanged()), this, SLOT(recalcAmounts())); - connect (detail, SIGNAL(fiatAmountChanged()), this, SLOT(recalcAmounts())); - } - if (detail->isInputs()) { + if (detail->isInputs()) connect (detail, SIGNAL(selectedValueChanged()), this, SLOT(recalcAmounts())); - } assert(!m_paymentDetails.isEmpty()); emit paymentDetailsChanged(); emit validChanged(); // pretty sure we are invalid after ;-) @@ -683,7 +680,7 @@ int Payment::paymentAmountFiat() const return (totalBch * m_fiatPrice / 10000000 + 5) / 10; } - amount += out->fiatAmount(); + amount += out->paymentAmountFiat(); } return amount; } diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 6a1b91d..79e369b 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -86,7 +86,6 @@ void PaymentDetailOutput::setPaymentAmount(double amount_) // implicit changes first, it changes the representation setFiatFollows(true); setMaxSelected(false); - emit fiatAmountChanged(); emit paymentAmountChanged(); checkValid(); } @@ -186,7 +185,6 @@ void PaymentDetailOutput::setWallet(Wallet *) { if (m_maxAllowed && m_maxSelected) { emit paymentAmountChanged(); - emit fiatAmountChanged(); } } @@ -203,7 +201,6 @@ void PaymentDetailOutput::setMaxSelected(bool on) // implicit change first, it changes the representation setFiatFollows(on); emit maxSelectedChanged(); - emit fiatAmountChanged(); emit paymentAmountChanged(); checkValid(); } @@ -211,7 +208,6 @@ void PaymentDetailOutput::setMaxSelected(bool on) void PaymentDetailOutput::recalcMax() { if (m_maxSelected && m_maxAllowed) { - emit fiatAmountChanged(); emit paymentAmountChanged(); } } @@ -229,7 +225,7 @@ void PaymentDetailOutput::setFiatFollows(bool on) emit fiatFollowsChanged(); } -int PaymentDetailOutput::fiatAmount() const +int PaymentDetailOutput::paymentAmountFiat() const { if (m_fiatFollows) { Payment *p = qobject_cast(parent()); @@ -259,7 +255,7 @@ int PaymentDetailOutput::fiatAmount() const return m_fiatAmount; } -void PaymentDetailOutput::setFiatAmount(int amount) +void PaymentDetailOutput::setPaymentAmountFiat(int amount) { if (m_fiatAmount == amount) return; @@ -267,7 +263,6 @@ void PaymentDetailOutput::setFiatAmount(int amount) // implicit changes first, it changes the representation setFiatFollows(false); setMaxSelected(false); - emit fiatAmountChanged(); emit paymentAmountChanged(); checkValid(); } @@ -289,10 +284,8 @@ void PaymentDetailOutput::setMaxAllowed(bool max) m_maxAllowed = max; emit maxAllowedChanged(); - if (max == false && m_paymentAmount == -1) { + if (max == false && m_paymentAmount == -1) setPaymentAmount(0); - } else { + else emit paymentAmountChanged(); - emit fiatAmountChanged(); - } } diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 436d14a..6c25884 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -25,7 +25,7 @@ class PaymentDetailOutput : public PaymentDetail Q_OBJECT Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY paymentAmountChanged) - Q_PROPERTY(int fiatAmount READ fiatAmount WRITE setFiatAmount NOTIFY fiatAmountChanged) + Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY addressChanged) Q_PROPERTY(bool maxAllowed READ maxAllowed WRITE setMaxAllowed NOTIFY maxAllowedChanged) @@ -52,8 +52,8 @@ public: bool maxAllowed() const; void setMaxAllowed(bool on); - int fiatAmount() const; - void setFiatAmount(int amount); + int paymentAmountFiat() const; + void setPaymentAmountFiat(int amount); bool fiatFollows() const; void setFiatFollows(bool on); @@ -72,7 +72,6 @@ public: signals: void paymentAmountChanged(); void addressChanged(); - void fiatAmountChanged(); void fiatIsMainChanged(); void fiatFollowsChanged(); void maxSelectedChanged(); -- 2.54.0 From de60a4cdc7fb742764658c81e31f91a3f3de54fd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 17:19:39 +0100 Subject: [PATCH 0358/1428] Add color property to MultilineTextField --- guis/Flowee/MultilineTextField.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 35dd7c2..05c4564 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -33,6 +33,7 @@ QQC2.Control { property string text: "" property string placeholderText: "" property var nextFocusTarget: null + property color color: palette.text signal editingFinished; property alias readOnly: textEdit.readOnly @@ -75,7 +76,7 @@ QQC2.Control { width: parent.width - 10 height: parent.height - 10 activeFocusOnTab: true - color: showingPlaceholder ? Qt.darker(palette.text, Pay.useDarkSkin ? 1.6 : 0.65) : palette.text + color: showingPlaceholder ? Qt.darker(palette.text, Pay.useDarkSkin ? 1.6 : 0.65) : root.color selectedTextColor: palette.highlightedText selectionColor: palette.highlight selectByMouse: true -- 2.54.0 From 697024d823392f2c7f472f7df1e2ef6c70443021 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 12:12:42 +0100 Subject: [PATCH 0359/1428] Fix one more usecase for unknown historical price. --- src/PriceHistoryDataProvider.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index e84880e..b17e3dc 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -162,15 +162,15 @@ int PriceHistoryDataProvider::historicalPrice(const uint32_t timestamp, Historic answer = value; prevTimestamp = time; } - - // if the timestamp requested is older than any we know. - // And the user wants an accurate value, then we don't just return the - // oldest one we have but we return an empty one. - if (timestamp < prevTimestamp - && hpa == Accurate && prevTimestamp - timestamp > 3600 * 48) - return 0; } + // if the timestamp requested is older than any we know. + // And the user wants an accurate value, then we don't just return the + // oldest one we have but we return an empty one. + if (timestamp < prevTimestamp + && hpa == Accurate && prevTimestamp - timestamp > 3600 * 48) + return 0; + return answer; } -- 2.54.0 From 400f16b636af0915af4a8ac46fc9dd33d4cf21e9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 14:26:13 +0100 Subject: [PATCH 0360/1428] Make loading and using of the PriceInputWidget work We now can edit the payment details in our new 'build transaction' screens. --- guis/mobile/PayToOthers.qml | 68 ++++++++++++++++++++++++++------ guis/mobile/PayWithQR.qml | 1 + guis/mobile/PriceInputWidget.qml | 12 +++--- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index ebb1263..e8157d4 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -46,6 +46,13 @@ Page { id: root headerText: qsTr("Create Payment") + function pushToThePile(componentId, detail) { + thePile.push(loaderForPayments, + {"paymentDetail": detail, + "sourceComponent": componentId } + ); + } + Item { // data QRScanner { id: scanner @@ -92,8 +99,8 @@ Page { onClicked: { var detail = payment.addExtraOutput(); thePile.pop(); - thePile.push(destinationEditPage); - thePile.currentItem.paymentDetail = detail; + pushToThePile(destinationEditPage, + detail); } } } @@ -105,7 +112,6 @@ Page { Component { id: destinationFields Column { - property QtObject paymentDetail: parent.paymentDetail property Component edit: destinationEditPage width: parent.width spacing: 6 @@ -131,11 +137,49 @@ Page { } } + /* + * The different payment things work with a 'paymentDetail' + * and since we push those into the global stack, they get + * loaded and initialized first, only secondly we set the + * property paymentDetail on them. + * + * This means we either get a load of errors dereferencing a null + * object, or we need to alter a lot of code to account for that. + * + * This is the alternative solution: we add a layer of indirection + * and set the property on the loader and the item in the loader + * will simply find the paymentDetail present in the context in + * which it has been loaded. + */ + Component { + id: loaderForPayments + FocusScope { + property alias paymentDetail: loader2.paymentDetail + property Component sourceComponent: undefined + function takeFocus() { + // this is also present in 'page', and called from thePile + forceActiveFocus(); + } + // anchors.fill: parent + Loader { + property QtObject paymentDetail: null + id: loader2 + anchors.fill: parent + onLoaded: item.takeFocus(); + } + function load() { + if (paymentDetail != null && typeof sourceComponent != "undefined") { + loader2.sourceComponent = sourceComponent + } + } + onPaymentDetailChanged: load(); + onSourceComponentChanged: load(); + } + } + Component { id: destinationEditPage Page { - property QtObject paymentDetail: null - headerText: qsTr("Edit Destination") Flowee.Label { id: destinationLabel @@ -277,6 +321,9 @@ Page { id: priceInput width: parent.width anchors.bottom: numericKeyboard.top + paymentBackend: paymentDetail + fiatFollowsSats: paymentDetail.fiatFollows + onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats } NumericKeyboardWidget { id: numericKeyboard @@ -311,6 +358,7 @@ Page { id: loader width: parent.width height: status === Loader.Ready ? item.implicitHeight : 0 + property QtObject paymentDetail: modelData sourceComponent: { if (modelData.type === Payment.PayToAddress) return destinationFields @@ -318,7 +366,6 @@ Page { return inputFields return null; // should never happen } - onLoaded: item.paymentDetail = modelData } Item { @@ -339,8 +386,7 @@ Page { editIcon.x = width - a + Math.max(0, a - editIcon.width); if (!editStarted && x < -100) { editStarted = true; - thePile.push(loader.item.edit) - thePile.currentItem.paymentDetail = modelData; + pushToThePile(loader.item.edit, modelData); } } else { @@ -441,11 +487,7 @@ Page { TextButton { text: qsTr("Add Destination") showPageIcon: true - onClicked: { - var detail = payment.addExtraOutput(); - thePile.push(destinationEditPage); - thePile.currentItem.paymentDetail = detail; - } + onClicked: pushToThePile(destinationEditPage, payment.addExtraOutput()); } TextButton { text: qsTr("Add Detail...") diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index f919c2e..6922049 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -79,6 +79,7 @@ Page { PriceInputWidget { id: priceInput + paymentBackend: payment width: parent.width } diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index bb6153e..ec8e19a 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -26,8 +26,10 @@ QQC2.Control { // if false, the user edits the fiat price and the bitcoin value is calculated. // Notice that 'send all' overrules both and gets the data from the wallet-total property bool fiatFollowsSats: true - + // made available for the NumericKeyboardWidget property var editor: fiatFollowsSats ? priceBch.money : priceFiat.money; + // Payment object or the PaymentDetailOutput object. + required property QtObject paymentBackend; function shake() { if (fiatFollowsSats) @@ -42,7 +44,7 @@ QQC2.Control { Flowee.BitcoinValueField { id: priceBch y: root.fiatFollowsSats ? 5 : 68 - value: payment.paymentAmount + value: paymentBackend.paymentAmount focus: true fontPixelSize: size property double size: fiatFollowsSats ? 38 : root.font.pixelSize* 0.8 @@ -52,7 +54,7 @@ QQC2.Control { Flowee.ObjectShaker { id: bchShaker } // 'shake' to give feedback on mistakes // this unchecks 'max' on user editing of the value - onValueEdited: payment.paymentAmount = value + onValueEdited: paymentBackend.paymentAmount = value } MouseArea { /* Since the valueField is centred but only allows clicking on its active surface, @@ -66,7 +68,7 @@ QQC2.Control { Flowee.FiatValueField { id: priceFiat - value: payment.paymentAmountFiat + value: paymentBackend.paymentAmountFiat y: root.fiatFollowsSats ? 68 : 5 focus: true fontPixelSize: size @@ -75,7 +77,7 @@ QQC2.Control { Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } Flowee.ObjectShaker { id: fiatShaker } - onValueEdited: payment.paymentAmountFiat = value + onValueEdited: paymentBackend.paymentAmountFiat = value } Rectangle { -- 2.54.0 From a11614a66f3092a34f7fc80870a0fdca91cbfbbe Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 17:21:02 +0100 Subject: [PATCH 0361/1428] Continue adding functionality to the PayToOthers screen --- guis/Flowee/AddressInfoWidget.qml | 65 ++++++++++ guis/mobile/PayToOthers.qml | 203 +++++++++++++++--------------- guis/mobile/PriceInputWidget.qml | 6 +- guis/mobile/main.qml | 1 + guis/widgets.qrc | 1 + 5 files changed, 172 insertions(+), 104 deletions(-) create mode 100644 guis/Flowee/AddressInfoWidget.qml diff --git a/guis/Flowee/AddressInfoWidget.qml b/guis/Flowee/AddressInfoWidget.qml new file mode 100644 index 0000000..c09ec00 --- /dev/null +++ b/guis/Flowee/AddressInfoWidget.qml @@ -0,0 +1,65 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import Flowee.org.pay; + +Item { + id: addressInfo + property QtObject info: null + visible: info != null + width: infoLabel.implicitWidth + height: infoLabel.height + + // the outcome from a call to Pay.identifyString() + required property var addressType; + // set to true, if the address is usable for an actual payment + property bool addressOk: (addressType === Wallet.CashPKH + || addressType === Wallet.CashSH) + || (paymentDetail.forceLegacyOk + && (addressType === Wallet.LegacySH + || addressType === Wallet.LegacyPKH)); + + function createInfo() { + if (addressOk) { + var address = paymentDetail.formattedTarget + if (address === "") // it didn't need reformatting + address = paymentDetail.address + info = Pay.researchAddress(address, addressInfo) + } + else { + delete info; + info = null; + } + } + onAddressOkChanged: createInfo(); + + Label { + id: infoLabel + anchors.right: parent.right + font.italic: true + text: { + var info = addressInfo.info + if (info == null) + return ""; + if (portfolio.current.id === info.accountId) + return qsTr("self", "payment to self") + return info.accountName + } + } +} + diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index e8157d4..58319c3 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -22,52 +22,29 @@ import "../Flowee" as Flowee import Flowee.org.pay; -/* - * Components embedded in this file: - Editors: - destinationEditPage - - editors have the property 'paymentDetail' - property QtObject paymentDetail: null - - ListItems: - destinationFields - - list items have the property 'edit' to link to Editors - property Component edit: [something] - list items have the property 'paymentDetail' - property QtObject paymentDetail: null - - Other: - paymentDetailSelector - */ - Page { id: root - headerText: qsTr("Create Payment") - function pushToThePile(componentId, detail) { - thePile.push(loaderForPayments, - {"paymentDetail": detail, - "sourceComponent": componentId } - ); - } + Item { // data and non-visible stuff for this page + /* + * Components embedded in this file: + * Editors: + destinationEditPage - Item { // data - QRScanner { - id: scanner - scanType: QRScanner.PaymentDetails - /*onFinished: { - var rc = scanResult - if (rc === "") { // scanning failed - thePile.pop(); - } - else { - payment.targetAddress = rc - priceBch.forceActiveFocus(); - } - } */ - } + editors can use the property 'paymentDetail' + + * ListItems: + destinationFields + + list items have the property 'edit' to link to Editors + property Component edit: [something] + list items can use the property 'paymentDetail' + + * Other: + paymentDetailSelector + + * To open editors or list items, we use the pushToThePile() function. + */ Payment { id: payment account: portfolio.current @@ -95,7 +72,8 @@ Page { id: col width: parent.width TextButton { - text: "Add Destination" + text: qsTr("Add Destination") + subtext: qsTr("an address to send money to") onClicked: { var detail = payment.addExtraOutput(); thePile.pop(); @@ -120,12 +98,31 @@ Page { * on its own page. */ Flowee.Label { - text: qsTr("Addressee") + ":" + text: qsTr("Destination") + ":" } Flowee.LabelWithClipboard { width: parent.width - text: paymentDetail.address + font.italic: paymentDetail.address === "" + text: { + var s = paymentDetail.address + if (s === "") + return qsTr("unset", "indication of empty"); + if (addressInfo.addressOk) { + let index = s.indexOf(":"); + if (index >= 0) + s = s.substr(index + 1); // cut off the prefix + } + return s; + } + color: addressInfo.addressOk || paymentDetail.address === "" + ? palette.windowText : mainWindow.errorRed menuText: qsTr("Copy Address") + clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: + } + Flowee.AddressInfoWidget { + id: addressInfo + width: parent.width + addressType: Pay.identifyString(paymentDetail.address); } Flowee.Label { text: qsTr("Amount")+ ":" @@ -160,7 +157,6 @@ Page { // this is also present in 'page', and called from thePile forceActiveFocus(); } - // anchors.fill: parent Loader { property QtObject paymentDetail: null id: loader2 @@ -192,29 +188,17 @@ Page { anchors.left: parent.left anchors.right: parent.right focus: true - property bool addressOk: (addressType === Wallet.CashPKH - || addressType === Wallet.CashSH) - || (paymentDetail.forceLegacyOk - && (addressType === Wallet.LegacySH - || addressType === Wallet.LegacyPKH)); property var addressType: Pay.identifyString(text); - onActiveFocusChanged: updateColor(); - onAddressOkChanged: { - updateColor(); - addressInfo.createInfo(); - } text: paymentDetail.address + nextFocusTarget: priceInput onTextChanged: { paymentDetail.address = text - updateColor(); addressInfo.createInfo(); } - - function updateColor() { - if (!activeFocus && text !== "" && !addressOk) - color = Pay.useDarkSkin ? "#ff6568" : "red" - else - color = palette.windowText + color: { + if (!activeFocus && text !== "" && !addressInfo.addressOk) + return mainWindow.errorRed + return palette.windowText } } Flowee.LabelWithClipboard { @@ -236,44 +220,15 @@ Page { font.italic: true menuText: qsTr("Copy Address") } - Item { + Flowee.AddressInfoWidget { id: addressInfo - property QtObject info: null - visible: info != null anchors.top: nativeLabel.bottom width: parent.width - height: infoLabel.height - - function createInfo() { - if (destination.addressOk) { - var address = paymentDetail.formattedTarget - if (address === "") // it didn't need reformatting - address = paymentDetail.address - info = Pay.researchAddress(address, addressInfo) - } - else { - delete info; - info = null; - } - } - - Flowee.Label { - id: infoLabel - anchors.right: parent.right - font.italic: true - text: { - var info = addressInfo.info - if (info == null) - return ""; - if (portfolio.current.id === info.accountId) - return qsTr("self", "payment to self") - return info.accountName - } - } + addressType: destination.addressType } Rectangle { - color: "red" + color: mainWindow.errorRed radius: 15 width: parent.width height: warningColumn.height + 20 @@ -335,6 +290,14 @@ Page { } } } + // check the comment at loaderForPayments to understand this one + function pushToThePile(componentId, detail) { + thePile.push(loaderForPayments, + {"paymentDetail": detail, + "sourceComponent": componentId } + ); + } + Flickable { id: contentArea @@ -345,14 +308,18 @@ Page { Column { id: mainColumn width: parent.width - spacing: 10 + + Flowee.Label { + text: qsTr("Create Payment") + } + Item { width: 1; height: 12 } // spacer Repeater { model: payment.details delegate: Item { id: listItem width: mainColumn.width - height: loader.height + 6 + height: loader.height + 20 + (index <= 1 ? (dragInstructions.height + 20) : 0) Loader { id: loader @@ -410,7 +377,7 @@ Page { // property bool editStarted: false; yAxis.enabled: false xAxis.enabled: true - xAxis.maximum: 200 // swipe left + xAxis.maximum: index > 0 ? 200 : 0 // swipe left xAxis.minimum: -140 // swipe right onActiveChanged: { if (active) { @@ -431,13 +398,13 @@ Page { Item { id: leftArea width: 300 - height: parent.height + height: loader.height x: -width; Rectangle { id: leftBackground opacity: 0 anchors.fill: parent - color: "red" + color: mainWindow.errorRed } Rectangle { @@ -480,6 +447,39 @@ Page { } } + // UX help + Row { + id: row + anchors.bottom: parent.bottom + anchors.bottomMargin: 26 + anchors.horizontalCenter: parent.horizontalCenter + spacing: 6 + visible: index <= 1 + property bool dragToEdit: index === 0 + Repeater { + model: 4 + delegate: Flowee.ArrowPoint { y: 3; color: palette.highlight + rotation: row.dragToEdit ? 180 : 0; + } + } + Flowee.Label { + id: dragInstructions + text: index === 0 ? qsTr("Drag to Edit") : qsTr("Drag to Delete"); + font.italic: true + color: palette.highlight + } + Repeater { + model: 4 + delegate: Flowee.ArrowPoint { y: 3; color: palette.highlight + rotation: row.dragToEdit ? 180 : 0; + } + } + } + + VisualSeparator { + anchors.bottom: parent.bottom + } + Behavior on x { NumberAnimation { duration: 100 } } } } @@ -489,15 +489,16 @@ Page { showPageIcon: true onClicked: pushToThePile(destinationEditPage, payment.addExtraOutput()); } +/* TextButton { text: qsTr("Add Detail...") showPageIcon: true onClicked: thePile.push(paymentDetailSelector); } +*/ } - // Add 'Next' button in header // Add "Next" button here. // on 'next' prepare and go to 'swipe to send' screen. // we likely want to have transaction details shown on that screen. diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index ec8e19a..a1bfbdc 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -20,7 +20,7 @@ import QtQuick.Controls as QQC2 import "../Flowee" as Flowee -QQC2.Control { +FocusScope { id: root // if true, the bitcoin value is provided by the user (or QR), and the fiat follows // if false, the user edits the fiat price and the bitcoin value is calculated. @@ -47,7 +47,7 @@ QQC2.Control { value: paymentBackend.paymentAmount focus: true fontPixelSize: size - property double size: fiatFollowsSats ? 38 : root.font.pixelSize* 0.8 + property double size: fiatFollowsSats ? 38 : mainWindow.font.pixelSize* 0.8 onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } @@ -72,7 +72,7 @@ QQC2.Control { y: root.fiatFollowsSats ? 68 : 5 focus: true fontPixelSize: size - property double size: !fiatFollowsSats ? 38 : root.font.pixelSize * 0.8 + property double size: !fiatFollowsSats ? 38 : mainWindow.font.pixelSize * 0.8 onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index f929312..b86012f 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -53,6 +53,7 @@ ApplicationWindow { property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" + property color errorRed: Pay.useDarkSkin ? "#ff6568" : "red" StackView { id: thePile diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 9b85b12..ae0a716 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -34,5 +34,6 @@ Flowee/WalletSecretsView.qml Flowee/HamburgerMenu.qml Flowee/QRWidget.qml + Flowee/AddressInfoWidget.qml -- 2.54.0 From 8b50ae1ec0d4695459dc6fa954770f9464f6c40a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 20:31:02 +0100 Subject: [PATCH 0362/1428] Use the shared QML AddressInfoWidget --- guis/desktop/SendTransactionPane.qml | 49 ++++------------------------ guis/desktop/main.qml | 1 + 2 files changed, 7 insertions(+), 43 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 612f46f..4c72aa2 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -295,29 +295,19 @@ Item { Flowee.TextField { id: destination focus: true - property bool addressOk: (addressType === Wallet.CashPKH || addressType === Wallet.CashSH) - || (paymentDetail.forceLegacyOk && (addressType === Wallet.LegacySH || addressType === Wallet.LegacyPKH)) property var addressType: Pay.identifyString(text); Layout.fillWidth: true Layout.columnSpan: 3 - onActiveFocusChanged: updateColor(); - onAddressOkChanged: { - updateColor(); - addressInfo.createInfo(); - } placeholderText: qsTr("Enter Bitcoin Cash Address") text: destinationPane.paymentDetail.address onTextChanged: { destinationPane.paymentDetail.address = text - updateColor(); addressInfo.createInfo(); } - - function updateColor() { - if (!activeFocus && text !== "" && !addressOk) - color = Pay.useDarkSkin ? "#ff6568" : "red" - else - color = palette.windowText + color: { + if (!activeFocus && text !== "" && !addressInfo.addressOk) + return mainWindow.errorRed + return palette.windowText } } Label { @@ -334,37 +324,10 @@ Item { font.italic: true menuText: qsTr("Copy Address") } - Item { + Flowee.AddressInfoWidget { id: addressInfo width: parent.width - property QtObject info: null - visible: info != null - - function createInfo() { - if (destination.addressOk) { - var address = paymentDetail.formattedTarget - if (address === "") // it didn't need reformatting - address = paymentDetail.address - info = Pay.researchAddress(address, addressInfo) - } - else { - delete info; - info = null; - } - } - - Label { - anchors.right: parent.right - font.italic: true - text: { - var info = addressInfo.info - if (info == null) - return ""; - if (portfolio.current.id === info.accountId) - return qsTr("self", "payment to self") - return info.accountName - } - } + addressType: destination.addressType } Label { diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index f002104..70333ef 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -57,6 +57,7 @@ ApplicationWindow { property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" + property color errorRed: Pay.useDarkSkin ? "#ff6568" : "red" Item { id: mainScreen -- 2.54.0 From acd80c92f1614f8df375f61dab57ee75c6d8af63 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 12 Mar 2023 20:31:37 +0100 Subject: [PATCH 0363/1428] Minor fixes. --- guis/mobile/PayToOthers.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 58319c3..262aaa0 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -302,15 +302,18 @@ Page { Flickable { id: contentArea anchors.fill: parent - contentHeight: mainColumn.height + contentHeight: mainColumn.height + 10 contentWidth: width clip: true Column { id: mainColumn + y: 10 width: parent.width Flowee.Label { text: qsTr("Create Payment") + width: parent.width + horizontalAlignment: Qt.AlignHCenter } Item { width: 1; height: 12 } // spacer -- 2.54.0 From 6d036b072130e1125467c2841f8390c1295636aa Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 10:24:15 +0100 Subject: [PATCH 0364/1428] Move code to new file Move the code that shows the account info and amount held in it into a new file called AccountSelectorWidget --- guis/mobile.qrc | 1 + guis/mobile/AccountSelectorWidget.qml | 79 +++++++++++++++++++++++++++ guis/mobile/PayWithQR.qml | 53 +----------------- 3 files changed, 81 insertions(+), 52 deletions(-) create mode 100644 guis/mobile/AccountSelectorWidget.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 9d8fe04..85ba400 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -56,5 +56,6 @@ mobile/PriceDetails.qml mobile/PriceInputWidget.qml mobile/NumericKeyboardWidget.qml + mobile/AccountSelectorWidget.qml diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml new file mode 100644 index 0000000..ae0be4f --- /dev/null +++ b/guis/mobile/AccountSelectorWidget.qml @@ -0,0 +1,79 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee + +Rectangle { + id: walletNameBackground + x: -10 + width: parent.width + 20 + height: { + if (portfolio.singleAccountSetup) + return currentWalletValue.height + 10 + return currentWalletValue.height + currentWalletLabel.height + 25 + } + color: palette.alternateBase + + Flowee.HamburgerMenu { + x: 10 + anchors.verticalCenter: currentWalletLabel.verticalCenter + visible: portfolio.accounts.length > 1 + } + + Flowee.Label { + id: currentWalletLabel + y: 10 + x: 20 + width: parent.width - 30 + text: payment.account.name + visible: !portfolio.singleAccountSetup + } + + Flowee.BitcoinAmountLabel { + id: currentWalletValue + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + value: { + var wallet = payment.account; + return wallet.balanceConfirmed + wallet.balanceUnconfirmed; + } + } + + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (mouse.x < parent.width / 2) { + if (portfolio.accounts.length > 1) + accountSelector.open(); + } else { + while (priceMenu.count > 0) { + priceMenu.takeItem(0); + } + priceMenu.addAction(sendAllAction); + priceMenu.x = root.width / 2 + priceMenu.y = -40 + priceMenu.open(); + } + } + } + QQC2.Menu { + id: priceMenu + } +} diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 6922049..3e1cb98 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -111,61 +111,10 @@ Page { } - Rectangle { + AccountSelectorWidget { id: walletNameBackground - anchors.top: currentWalletLabel.visible ? currentWalletLabel.top : currentWalletValue.top - anchors.topMargin: -5 - width: parent.width + 20 - x: -10 - anchors.bottom: currentWalletValue.bottom - anchors.bottomMargin: -5 - color: palette.alternateBase - - MouseArea { - anchors.fill: parent - onClicked: (mouse) => { - if (mouse.x < parent.width / 2) { - if (portfolio.accounts.length > 1) - accountSelector.open(); - } else { - while (priceMenu.count > 0) { - priceMenu.takeItem(0); - } - priceMenu.addAction(sendAllAction); - priceMenu.x = root.width / 2 - priceMenu.y = -40 - priceMenu.open(); - } - } - } - QQC2.Menu { - id: priceMenu - } - } - - Flowee.HamburgerMenu { - anchors.verticalCenter: currentWalletLabel.verticalCenter - visible: portfolio.accounts.length > 1 - } - - Flowee.Label { - id: currentWalletLabel - text: payment.account.name - visible: !portfolio.singleAccountSetup - x: 10 - width: parent.width - 10 - anchors.bottom: currentWalletValue.top - } - - Flowee.BitcoinAmountLabel { - id: currentWalletValue - anchors.right: parent.right anchors.bottom: numericKeyboard.top anchors.bottomMargin: 10 - value: { - var wallet = payment.account; - return wallet.balanceConfirmed + wallet.balanceUnconfirmed; - } } NumericKeyboardWidget { -- 2.54.0 From 76b79a1c0ace880b55aa99c8e8a4a5b1125e6fe4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 10:32:12 +0100 Subject: [PATCH 0365/1428] Rename AccountSelector to {}Popup Following the pattern of ending with what it is. We now have Widget and Popup and Page. The idea is that the widgets and popups are re-usable. --- guis/mobile.qrc | 2 +- .../{AccountSelector.qml => AccountSelectorPopup.qml} | 0 guis/mobile/AccountSelectorWidget.qml | 10 +++++++++- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/PayToOthers.qml | 9 --------- guis/mobile/PayWithQR.qml | 9 --------- 6 files changed, 11 insertions(+), 21 deletions(-) rename guis/mobile/{AccountSelector.qml => AccountSelectorPopup.qml} (100%) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 85ba400..c56938a 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -49,7 +49,7 @@ mobile/PopupOverlay.qml mobile/TransactionDetails.qml mobile/TxInfoSmall.qml - mobile/AccountSelector.qml + mobile/AccountSelectorPopup.qml mobile/CurrencySelector.qml mobile/StartupScreen.qml mobile/ImportWalletPage.qml diff --git a/guis/mobile/AccountSelector.qml b/guis/mobile/AccountSelectorPopup.qml similarity index 100% rename from guis/mobile/AccountSelector.qml rename to guis/mobile/AccountSelectorPopup.qml diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index ae0be4f..ae541d5 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -20,7 +20,7 @@ import QtQuick.Controls as QQC2 import "../Flowee" as Flowee Rectangle { - id: walletNameBackground + id: root x: -10 width: parent.width + 20 height: { @@ -76,4 +76,12 @@ Rectangle { QQC2.Menu { id: priceMenu } + + AccountSelectorPopup { + id: accountSelector + width: root.width + y: -10 + onSelectedAccountChanged: payment.account = selectedAccount + selectedAccount: payment.account + } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index ebd9bed..a0966ef 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -125,7 +125,7 @@ QQC2.Control { anchors.baseline: logo.baseline } - AccountSelector { + AccountSelectorPopup { id: accountSelector width: root.width y: header.height diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 262aaa0..71a71ef 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -51,15 +51,6 @@ Page { fiatPrice: Fiat.price } - AccountSelector { - id: accountSelector - width: root.width - x: -10 // to correct the indent added in the fullPage - y: (root.height - height) / 2 - onSelectedAccountChanged: payment.account = selectedAccount - selectedAccount: payment.account - } - // Extra page to create new details. Component { id: paymentDetailSelector diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 3e1cb98..a3a7202 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -66,15 +66,6 @@ Page { // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" // userComment: "bla bla bla" } - - AccountSelector { - id: accountSelector - width: root.width - x: -10 // to correct the indent added in the fullPage - y: (root.height - height) / 2 - onSelectedAccountChanged: payment.account = selectedAccount - selectedAccount: payment.account - } } PriceInputWidget { -- 2.54.0 From 594b990dc60f7ac9284ab66345ddb99e17a376fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 10:36:27 +0100 Subject: [PATCH 0366/1428] Allow setting the wallet in build transaction. --- guis/mobile/PayToOthers.qml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 71a71ef..0e77018 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -24,6 +24,7 @@ import Flowee.org.pay; Page { id: root + headerText: qsTr("Build Transaction") Item { // data and non-visible stuff for this page /* @@ -300,13 +301,11 @@ Page { id: mainColumn y: 10 width: parent.width + spacing: 6 - Flowee.Label { - text: qsTr("Create Payment") - width: parent.width - horizontalAlignment: Qt.AlignHCenter - } - Item { width: 1; height: 12 } // spacer + AccountSelectorWidget { } + + VisualSeparator { } Repeater { model: payment.details -- 2.54.0 From b54ed48c32fe5177e3504807382dc43b7fb443cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 11:37:09 +0100 Subject: [PATCH 0367/1428] Use mainWindow instead of mainScreen This is easier to support on multiple front-ends. --- guis/Flowee/Dialog.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 74ffcfc..24790a7 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -44,8 +44,8 @@ QQC2.Popup { onAboutToShow: reposition() onHeightChanged: reposition() function reposition() { - // 'mainScreen' is defined in main.qml - var window = mainScreen; + // 'mainWindow' is defined in main.qml + var window = mainWindow.contentItem; var globalX = (window.width - root.width) / 2; var globalY = window.height / 3 - root.height; var local = mapFromItem(window, globalX, globalY); @@ -55,8 +55,8 @@ QQC2.Popup { Column { width: { - // 'mainScreen' is defined in main.qml - var window = mainScreen; + // 'mainWindow' is defined in main.qml + var window = mainWindow.contentItem; let wanted = Math.max(mainTextLabel.contentWidth, titleLabel.implicitWidth) if (content.item) -- 2.54.0 From 17f982af1c647c4d39bbdfd7cb07f8ceee4bbc86 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 11:52:47 +0100 Subject: [PATCH 0368/1428] Make build transaction screen work The minimal functionality is in place. --- guis/mobile/PayToOthers.qml | 112 ++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 0e77018..6caf7bc 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -43,6 +43,7 @@ Page { * Other: paymentDetailSelector + paymentInfoPage * To open editors or list items, we use the pushToThePile() function. */ @@ -78,6 +79,92 @@ Page { } } + // show info about payment and allow broadcast + Component { + id: paymentInfoPage + Page { + id: root + headerText: qsTr("Confirm Sending", "confirm we want to send the transaction") + + Flickable { + anchors.top: parent.top + anchors.bottom: slideToApprove.top + width: parent.width + contentHeight: col.implicitHeight + clip: true + + Column { + id: col + width: parent.width + spacing: 10 + + // the below is all very technical stuff that most people don't care about. + // it was easy, but its not useful. + // So, TODO insert here actually useful to end users details like amount sent, + // change address used. Etc. + + Flowee.Label { + text: qsTr("TXID") + ":" + } + Flowee.LabelWithClipboard { + id: txid + text: payment.txid + font.pixelSize: root.font.pixelSize * 0.8 + width: parent.width + // Change the color when the portfolio changed since 'prepare' was clicked. + menuText: qsTr("Copy transaction-ID") + } + Flowee.Label { + text: qsTr("Fee") + ":" + } + Flowee.BitcoinAmountLabel { + value: payment.assignedFee + colorize: false + } + Flowee.Label { + text: qsTr("Transaction size") + ":" + } + Flowee.Label { + text: qsTr("%1 bytes").arg(payment.txSize) + } + Flowee.Label { + text: qsTr("Fee per byte") + ":" + } + Flowee.Label { + text: { + var rc = payment.assignedFee / payment.txSize; + var fee = rc.toFixed(3); // no more than 3 numbers behind the separator + fee = (fee * 1.0).toString(); // remove trailing zero's (1.000 => 1) + return qsTr("%1 sat/byte", "fee").arg(fee); + } + } + } + } + + SlideToApprove { + id: slideToApprove + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + width: parent.width + onActivated: payment.broadcast() + } + + Flowee.BroadcastFeedback { + id: broadcastPage + anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. + anchors.rightMargin: -10 + onStatusChanged: { + if (status !== "") + root.headerText = status; + } + onCloseButtonPressed: { + thePile.pop(); // this screen + thePile.pop(); // parent screen + } + } + } + } + // listitem of PaymentDetail showing payment-destination Component { id: destinationFields @@ -489,12 +576,27 @@ Page { onClicked: thePile.push(paymentDetailSelector); } */ + Flowee.Button { + text: qsTr("Prepare Payment...") + enabled: payment.isValid + anchors.right: parent.right + onClicked: { + payment.prepare(); + if (payment.error !== "") { + console.log("error " + payment.error) + errorDialog.show(); + } + else { + thePile.push(paymentInfoPage); + } + } + Flowee.Dialog { + id: errorDialog + title: qsTr("Building Error", "error during build") + text: qsTr("Please check the details for errors, during build we detected this issue: %1").arg(payment.error) + } + } } - - // Add "Next" button here. - // on 'next' prepare and go to 'swipe to send' screen. - // we likely want to have transaction details shown on that screen. } - } -- 2.54.0 From 8053a77d8d4fe7ed325f446520bb9776aeaeda11 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 12:53:33 +0100 Subject: [PATCH 0369/1428] Make Flowee.Dialog useful on mobile --- guis/Flowee/Dialog.qml | 17 ++++++++++++----- guis/mobile/PayToOthers.qml | 14 +++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 24790a7..1cce1fe 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ QQC2.Popup { property alias title: titleLabel.text property alias text: mainTextLabel.text property alias contentComponent: content.sourceComponent + property alias standardButtons: buttons.standardButtons function accept() { accepted(); @@ -44,11 +45,13 @@ QQC2.Popup { onAboutToShow: reposition() onHeightChanged: reposition() function reposition() { + if (parent == null) + return; // 'mainWindow' is defined in main.qml var window = mainWindow.contentItem; var globalX = (window.width - root.width) / 2; var globalY = window.height / 3 - root.height; - var local = mapFromItem(window, globalX, globalY); + var local = parent.mapFromItem(null, globalX, globalY); x = local.x; y = local.y; } @@ -62,13 +65,17 @@ QQC2.Popup { if (content.item) wanted = Math.max(wanted, content.item.implicitWidth) let max = window.width - let min = buttons.implicitWidth + let min = Math.max(buttons.implicitWidth + 20, 300) let ideal = Math.max(min, max / 3) + if (max <= 360) // mobile width + max = max * 0.9; + else + max = max * 2 / 3; if (wanted < ideal) return ideal - if (wanted < max / 2 * 3) + if (wanted < max) return wanted - return max / 2 * 3; + return max; } spacing: 10 Label { diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 6caf7bc..480fde3 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -52,6 +52,12 @@ Page { account: portfolio.current fiatPrice: Fiat.price } + Flowee.Dialog { + id: errorDialog + standardButtons: QQC2.DialogButtonBox.Close + title: qsTr("Building Error", "error during build") + text: payment.error + } // Extra page to create new details. Component { @@ -583,19 +589,13 @@ Page { onClicked: { payment.prepare(); if (payment.error !== "") { - console.log("error " + payment.error) - errorDialog.show(); + errorDialog.visible = true; } else { thePile.push(paymentInfoPage); } } - Flowee.Dialog { - id: errorDialog - title: qsTr("Building Error", "error during build") - text: qsTr("Please check the details for errors, during build we detected this issue: %1").arg(payment.error) - } } } } -- 2.54.0 From 98b148d4b05a614840760f44276fcf6dc04da1fb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 16:52:29 +0100 Subject: [PATCH 0370/1428] Add edit icon for the swipe action --- guis/mobile.qrc | 2 ++ guis/mobile/PayToOthers.qml | 5 +++-- guis/mobile/images/edit-light.svg | 4 ++++ guis/mobile/images/edit.svg | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 guis/mobile/images/edit-light.svg create mode 100644 guis/mobile/images/edit.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c56938a..6447652 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -21,6 +21,8 @@ mobile/images/backspace-light.svg mobile/images/settingsIcon-light.svg mobile/images/settingsIcon.svg + mobile/images/edit.svg + mobile/images/edit-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 480fde3..5eb7b3f 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -521,9 +521,8 @@ Page { } } - Rectangle { + Image { id: editIcon - color: "blue" width: 40 height: 40 y: 10 @@ -531,6 +530,8 @@ Page { let additional = Math.max(0, Math.min(listItem.x * -1 - width, 16)) return parent.width + additional; } + source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + smooth: true } // UX help diff --git a/guis/mobile/images/edit-light.svg b/guis/mobile/images/edit-light.svg new file mode 100644 index 0000000..2daf2f5 --- /dev/null +++ b/guis/mobile/images/edit-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/edit.svg b/guis/mobile/images/edit.svg new file mode 100644 index 0000000..4f7bb38 --- /dev/null +++ b/guis/mobile/images/edit.svg @@ -0,0 +1,4 @@ + + + + -- 2.54.0 From 2a03a3efa6fa2ab418b99fe37ee46c83639b3412 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 16:58:05 +0100 Subject: [PATCH 0371/1428] Re-enable the action. After moving the AccountSelectorWidget to its own file, the link to the action was broken. This re-establishes that. --- guis/mobile/AccountSelectorWidget.qml | 32 ++++++++++++++++----------- guis/mobile/PayWithQR.qml | 2 ++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index ae541d5..049c72b 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -23,6 +23,10 @@ Rectangle { id: root x: -10 width: parent.width + 20 + + // list of actions to put in a menu when clicking on the 'balance' side. + property var balanceActions: [ ] + height: { if (portfolio.singleAccountSetup) return currentWalletValue.height + 10 @@ -59,19 +63,21 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: (mouse) => { - if (mouse.x < parent.width / 2) { - if (portfolio.accounts.length > 1) - accountSelector.open(); - } else { - while (priceMenu.count > 0) { - priceMenu.takeItem(0); - } - priceMenu.addAction(sendAllAction); - priceMenu.x = root.width / 2 - priceMenu.y = -40 - priceMenu.open(); - } - } + if (mouse.x < parent.width / 2) { + if (portfolio.accounts.length > 1) + accountSelector.open(); + } else if (balanceActions.length > 0) { + while (priceMenu.count > 0) { + priceMenu.takeItem(0); + } + for (let i = 0; i < balanceActions.length; ++i) { + priceMenu.addAction(balanceActions[i]); + } + priceMenu.x = root.width / 2 + priceMenu.y = -40 + priceMenu.open(); + } + } } QQC2.Menu { id: priceMenu diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index a3a7202..b6c4d64 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -106,6 +106,8 @@ Page { id: walletNameBackground anchors.bottom: numericKeyboard.top anchors.bottomMargin: 10 + + balanceActions: [ sendAllAction ] } NumericKeyboardWidget { -- 2.54.0 From b133c8fec361aed4cd911e1a34598d0f188d141b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 13:33:45 +0100 Subject: [PATCH 0372/1428] Minor UX fixes --- guis/mobile/PayToOthers.qml | 10 ++++++++-- guis/mobile/PayWithQR.qml | 9 +++++++-- guis/mobile/PriceInputWidget.qml | 9 +++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 5eb7b3f..e80ffcd 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -164,6 +164,8 @@ Page { root.headerText = status; } onCloseButtonPressed: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. thePile.pop(); // this screen thePile.pop(); // parent screen } @@ -545,7 +547,9 @@ Page { property bool dragToEdit: index === 0 Repeater { model: 4 - delegate: Flowee.ArrowPoint { y: 3; color: palette.highlight + delegate: Flowee.ArrowPoint { + color: palette.highlight + anchors.verticalCenter: dragInstructions.verticalCenter rotation: row.dragToEdit ? 180 : 0; } } @@ -557,7 +561,9 @@ Page { } Repeater { model: 4 - delegate: Flowee.ArrowPoint { y: 3; color: palette.highlight + delegate: Flowee.ArrowPoint { + color: palette.highlight + anchors.verticalCenter: dragInstructions.verticalCenter rotation: row.dragToEdit ? 180 : 0; } } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index b6c4d64..d25437e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -51,7 +51,8 @@ Page { } else { payment.targetAddress = rc - priceBch.forceActiveFocus(); + priceInput.forceActiveFocus(); + priceInput.fiatFollowsSats = false } } } @@ -136,6 +137,10 @@ Page { root.headerText = status; } - onCloseButtonPressed: thePile.pop(); + onCloseButtonPressed: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); + } } } diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index a1bfbdc..90edeea 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -31,6 +31,15 @@ FocusScope { // Payment object or the PaymentDetailOutput object. required property QtObject paymentBackend; + onFiatFollowsSatsChanged: { + if (!activeFocus) + return; + if (fiatFollowsSats) + priceBch.forceActiveFocus(); + else + priceFiat.forceActiveFocus(); + } + function shake() { if (fiatFollowsSats) bchShaker.start(); -- 2.54.0 From 09b03faab7cc682afd128a43cb56b2131653dc7d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 17:52:26 +0100 Subject: [PATCH 0373/1428] Make sure that the hamburger menu is removed on broadcast We now make sure that the menu accessible from the header is only there when we are actually on the page where it is relevant. --- guis/mobile/PayWithQR.qml | 10 +++++++--- src/QRScanner.cpp | 17 +++++++++++++++++ src/QRScanner.h | 6 ++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index d25437e..8321616 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -35,9 +35,13 @@ Page { payment.prepare(); // auto-prepare doesn't act on changes done on the details. } } - menuItems: [ - sendAllAction - ] + menuItems: { + // only have menu items as long as we are effectively + // showing this page and not some overlay. + if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) + return [ sendAllAction ]; + return []; + } Item { // data QRScanner { diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index bd61e22..f706c52 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -40,12 +40,14 @@ void QRScanner::start() resetScanResult(); if (m_scanType == static_cast(100)) throw std::runtime_error("Required property scanType not set"); + setIsScanning(true); FloweePay::instance()->cameraController()->startRequest(this); } void QRScanner::abort() { FloweePay::instance()->cameraController()->abortRequest(this); + setIsScanning(false); } QRScanner::ScanType QRScanner::scanType() const @@ -65,6 +67,7 @@ void QRScanner::finishedScan(const QString &resultString) { setScanResult(resultString); emit finished(); + setIsScanning(false); } QString QRScanner::scanResult() const @@ -78,6 +81,7 @@ void QRScanner::setScanResult(const QString &newScanResult) return; m_scanResult = newScanResult; emit scanResultChanged(); + setIsScanning(false); } void QRScanner::resetScanResult() @@ -98,6 +102,19 @@ void QRScanner::setAutostart(bool newAutostart) emit autostartChanged(); } +bool QRScanner::isScanning() const +{ + return m_isScanning; +} + +void QRScanner::setIsScanning(bool now) +{ + if (m_isScanning == now) + return; + m_isScanning = now; + emit isScanningChanged(); +} + void QRScanner::completed() { if (m_autostart) diff --git a/src/QRScanner.h b/src/QRScanner.h index 034e30f..4b89b8f 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -28,6 +28,7 @@ class QRScanner : public QObject Q_PROPERTY(ScanType scanType READ scanType WRITE setScanType NOTIFY scanTypeChanged REQUIRED) Q_PROPERTY(QString scanResult READ scanResult WRITE setScanResult RESET resetScanResult NOTIFY scanResultChanged) Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) + Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) public: explicit QRScanner(QObject *parent = nullptr); ~QRScanner(); @@ -56,6 +57,9 @@ public: bool autostart() const; void setAutostart(bool newAutostart); + bool isScanning() const; + void setIsScanning(bool now); + private slots: void completed(); @@ -64,11 +68,13 @@ signals: void scanTypeChanged(); void scanResultChanged(); void autostartChanged(); + void isScanningChanged(); private: ScanType m_scanType; QString m_scanResult; bool m_autostart = false; + bool m_isScanning = false; }; #endif -- 2.54.0 From d7963e4d82681898d5cc9fda8b09311aef755874 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 13 Mar 2023 18:05:14 +0100 Subject: [PATCH 0374/1428] Make pasting a payment url work in more usecases This moves the code simply to a detail, allowing us to parse things like the amount as well in a bip21 compatible URL in such usage. --- src/Payment.cpp | 33 ++----------------------------- src/PaymentDetailOutput.cpp | 39 +++++++++++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 6ac6982..28277d2 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -76,38 +76,9 @@ void Payment::setPaymentAmountFiat(int amount) doAutoPrepare(); } -void Payment::setTargetAddress(const QString &address_) +void Payment::setTargetAddress(const QString &address) { - QString address = address_.trimmed(); - - auto out = soleOut(); - /* - * Users may paste an address that is really a payment url. - * This basically means we may have a price added after a questionmark. - * bitcoincash:qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45?amount=2.1 - */ - int urlStart = address.indexOf('?'); - if (urlStart > 0) { - QUrl url(address); - auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); - for (const auto &item : query.queryItems()) { - if (item.first == "amount") { - bool ok; - auto amount = item.second.toDouble(&ok); - if (ok) { - out->setPaymentAmount(amount * 1E8); - emit amountChanged(); - } - } - else if (item.first == "label" || item.first == "message") { - setUserComment(item.second); - } - } - - address = address.left(urlStart); - } - - out->setAddress(address); + soleOut()->setAddress(address); emit targetAddressChanged(); doAutoPrepare(); } diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 79e369b..757e3e4 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -97,14 +97,41 @@ const QString &PaymentDetailOutput::address() const void PaymentDetailOutput::setAddress(const QString &address_) { - const QString address = address_.trimmed(); - if (m_address == address) + const QString addressOrURL = address_.trimmed(); + if (m_address == addressOrURL) return; - m_address = address; + m_address = addressOrURL; const std::string &chainPrefixCopy = chainPrefix(); - std::string encodedAddress; - switch (FloweePay::instance()->identifyString(address)) { + /* + * Users may paste an address that is really a payment url. + * This basically means we may have a price added after a questionmark. + * bitcoincash:qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45?amount=2.1 + */ + int urlStart = addressOrURL.indexOf('?'); + if (urlStart > 0) { + QUrl url(addressOrURL); + auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); + for (const auto &item : query.queryItems()) { + if (item.first == "amount") { + bool ok; + auto amount = item.second.toDouble(&ok); + if (ok) + setPaymentAmount(amount * 1E8); + } + else if (item.first == "label" || item.first == "message") { + // message goes on the main payment.. + Payment *p = qobject_cast(parent()); + assert(p); + p->setUserComment(item.second); + } + } + + m_address = addressOrURL.left(urlStart); + } + + std::string encodedAddress; + switch (FloweePay::instance()->identifyString(m_address)) { case WalletEnums::LegacyPKH: { CBase58Data legacy; auto ok = legacy.SetString(m_address.toStdString()); @@ -150,7 +177,7 @@ void PaymentDetailOutput::setAddress(const QString &address_) // lets see if the encoded address is substantially different from the user-given address auto full = QString::fromStdString(encodedAddress); auto formattedTarget = full.mid(size + 1); - if (full != address && formattedTarget != address) + if (full != m_address && formattedTarget != m_address) m_formattedTarget = full; } -- 2.54.0 From da2cdb4cdae58f3d4d0515f07eaab3ae6bdb36b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 10:28:17 +0100 Subject: [PATCH 0375/1428] Improve description of feature. The old was technically not entirely correct. --- guis/mobile/AccountPageListItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index d48f126..29608ca 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -180,7 +180,7 @@ QQC2.Control { text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked - tooltipText: qsTr("Switches between unused and used Bitcoin addresses") + tooltipText: qsTr("Switches between still in use addresses and formerly used, nmw empty, addresses") } Flowee.WalletSecretsView { -- 2.54.0 From 0b99ad174219da63db422a9c431016559440d673 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 10:29:46 +0100 Subject: [PATCH 0376/1428] Make showing the hd-index possible This adds a config option on mobile and makes it again show up properly on desktop. HDIndex is the derivation-index of a private key. --- guis/Flowee/WalletSecretsView.qml | 7 +++++-- guis/mobile/AccountPageListItem.qml | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index d052952..b54ee92 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -24,6 +24,8 @@ ListView { clip: true model: root.account.secrets + property bool showHdIndex: true + delegate: Rectangle { id: delegateRoot color: (index % 2) == 0 ? palette.base : palette.alternateBase @@ -44,7 +46,7 @@ ListView { anchors.baseline: addressLabel.baseline anchors.right: addressLabel.left anchors.rightMargin: 10 - visible: root.account.isHDWallet + visible: root.showHdIndex && root.account.isHDWallet } LabelWithClipboard { @@ -54,6 +56,7 @@ ListView { anchors.bottomMargin: 6 text: address anchors.left: parent.left + anchors.leftMargin: root.showHdIndex ? 40 : 0 anchors.right: hamburgerMenu.left anchors.rightMargin: 6 menuText: qsTr("Copy Address") @@ -64,7 +67,7 @@ ListView { width: 12 height: column.height anchors.right: parent.right - anchors.bottom: addressLabel.bottom + anchors.verticalCenter: addressLabel.verticalCenter Column { id: column spacing: 3 diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 29608ca..df9a375 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -164,6 +164,20 @@ QQC2.Control { id: backupDetails Page { headerText: qsTr("Wallet keys") + + property QtObject showIndexAction: QQC2.Action { + text: qsTr("Show Index", "toggle to show numbers") + checkable: true + checked: listView.showHdIndex + onTriggered: listView.showHdIndex = checked + } + + menuItems: { + if (root.account.isHDWallet) + return [showIndexAction]; + return []; + } + Flowee.CheckBox { id: changeAddresses text: qsTr("Change Addresses") @@ -184,12 +198,14 @@ QQC2.Control { } Flowee.WalletSecretsView { + id: listView anchors.top: usedAddresses.visible ? usedAddresses.bottom : parent.top anchors.topMargin: 10 anchors.bottom: parent.bottom width: parent.width account: root.account clip: true + showHdIndex: false } } } -- 2.54.0 From fbe7a74b367baf6c336c12bcb978866a1975ceb3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 10:32:40 +0100 Subject: [PATCH 0377/1428] Transform the Schnorr tooltip from hover to click This allows it to be used on a mouse-less interface as well. --- guis/Flowee/WalletSecretsView.qml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index b54ee92..3e55cb8 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -141,21 +141,12 @@ ListView { font.bold: true text: "S" MouseArea { - id: mousy anchors.fill: parent anchors.margins: -10 hoverEnabled: true - - QQC2.ToolTip { - parent: schnorrIndicator - text: qsTr("Signed with Schnorr signatures in the past") - delay: 700 - visible: mousy.containsMouse - } + onClicked: QQC2.ToolTip.show(qsTr("Signed with Schnorr signatures in the past"), 7500); } } } - - } } -- 2.54.0 From cfdc72a2f2ecd520b5aba8c3ca08c9e86c5ad2b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 10:39:44 +0100 Subject: [PATCH 0378/1428] Fix centering of text in case of hamburgermenu. --- guis/mobile/Page.qml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 323d491..8ff15b8 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -85,7 +85,14 @@ QQC2.Control { id: headerLabel color: "white" anchors.left: backButton.right - anchors.right: headerButton.visible ? headerButton.left : parent.right + anchors.right: { + // we assume at most one of these two is in use. + if (headerButton.visible) + return headerButton.left; + if (hamburgerMenu.visible) + return hamburgerMenu.left; + return parent.right; + } horizontalAlignment: Text.AlignHCenter anchors.verticalCenter: parent.verticalCenter } @@ -99,6 +106,7 @@ QQC2.Control { onClicked: root.headerButtonClicked() } Flowee.HamburgerMenu { + id: hamburgerMenu visible: root.menuItems.length > 0 anchors.right: parent.right anchors.rightMargin: 15 -- 2.54.0 From a9c781c8d78612e877513fc76033f3b9b9c1ef69 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 10:54:08 +0100 Subject: [PATCH 0379/1428] Add explanation header --- guis/Flowee/WalletSecretsView.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 3e55cb8..349fb68 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,20 @@ ListView { model: root.account.secrets property bool showHdIndex: true + header: Column { + width: root.width + Label { + width: parent.width + font.italic: true + text: qsTr("Explanation:"); + } + Label { + width: parent.width + font.italic: true + text: qsTr("Coins a / b\n a) active coin-count.\n b) historical coin-count."); + } + } + delegate: Rectangle { id: delegateRoot color: (index % 2) == 0 ? palette.base : palette.alternateBase -- 2.54.0 From e032e79f9662eda0bb6032476ee83b1ec7537cd7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 11:19:55 +0100 Subject: [PATCH 0380/1428] Make single items also have the 'grouping' rect Feedback has shown that it is prettier to simply have a single rect for an item that is ungrouped. --- guis/mobile/AccountHistory.qml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index b3f78c6..64ec411 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -167,17 +167,23 @@ ListView { Rectangle { width: parent.width - 16 x: 8 - visible: transactionDelegate.placementInGroup !== Wallet.Ungrouped; // we always have the rounded circles, but if we should not see them, we move them out of the clipped area. height: { var h = 80 - if (transactionDelegate.placementInGroup !== Wallet.GroupStart) + var placement = transactionDelegate.placementInGroup; + + if (placement !== Wallet.GroupStart && placement !== Wallet.Ungrouped) h += 20; - if (transactionDelegate.placementInGroup !== Wallet.GroupEnd) + if (placement !== Wallet.GroupEnd && placement !== Wallet.Ungrouped) h += 20; return h; } - y: transactionDelegate.placementInGroup === Wallet.GroupStart ? 0 : -20; + y: { + var placement = transactionDelegate.placementInGroup; + if (placement === Wallet.GroupStart || placement === Wallet.Ungrouped) + return 0; + return -20; + } radius: 20 color: palette.alternateBase -- 2.54.0 From 3171898c8cf8d9423494e676e6f659e95aff2eab Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 13:35:03 +0100 Subject: [PATCH 0381/1428] Fix "hamburger menu too hard to touch" This enlarges the touch area by 3x in order to make it much easier to hit. Notice that we are still limiting this to the header-area only, so only 3 times as wide, really. Thanks fly to fshinetop for reporting it (and lots more). --- guis/mobile/MainViewBase.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index a0966ef..e80dd3d 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -75,7 +75,8 @@ QQC2.Control { } MouseArea { anchors.fill: menuButton - anchors.margins: -20 + // make the touch area a lot bigger, its safe as its limited to the 'header' area anyway. + anchors.margins: -60 cursorShape: Qt.PointingHandCursor onClicked: menuOverlay.open = true; } -- 2.54.0 From fde8cad59798c879f5a040de9bb101bd6d4cfcdf Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 15:36:45 +0100 Subject: [PATCH 0382/1428] Be consistent with usage of 'pixelSize'. Also make the unit-label follow the widget property for font sizing. --- guis/Flowee/BitcoinAmountLabel.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 554b22e..355506f 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -93,7 +93,7 @@ QQC2.Control { var pos = s.length - 5 return s.substring(pos, pos + 3); } - font.pointSize: satsLabel.font.pointSize + font.pixelSize: satsLabel.font.pixelSize color: main.color opacity: (satsLabel.opacity !== 1 && text === "000") ? 0.3 : 1 Layout.alignment: Qt.AlignBaseline @@ -105,7 +105,7 @@ QQC2.Control { var s = row.amountString return s.substring(s.length - 2); } - font.pointSize: main.font.pointSize / 10 * 8 + font.pixelSize: main.font.pixelSize * 0.8 color: main.color opacity: text === "00" ? 0.3 : 1 Layout.alignment: Qt.AlignBaseline @@ -114,6 +114,7 @@ QQC2.Control { Label { text: Pay.unitName + font.pixelSize: main.font.pixelSize color: main.color visible: root.includeUnit Layout.alignment: Qt.AlignBaseline -- 2.54.0 From f05f3d52a3a8563e4804ba63585e6e0b9334f52d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 16:03:19 +0100 Subject: [PATCH 0383/1428] Fix rounding for negative numbers. --- src/PriceDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 0a73741..768c369 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -102,7 +102,7 @@ QString PriceDataProvider::formattedPrice(double amountSats, int price) const int PriceDataProvider::priceFor(double amountSats, int price) const { qint64 fiatValue = amountSats * price; - fiatValue = (fiatValue + 50000000) / qint64(100000000); + fiatValue = (fiatValue + (amountSats > 0 ? 50000000: -50000000)) / qint64(100000000); assert(fiatValue < INT_MAX); return static_cast(fiatValue); } -- 2.54.0 From 4b74e8234c531962a922e38bc559f405a932baf9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 16:04:59 +0100 Subject: [PATCH 0384/1428] Coding style; use 'return' in JS. --- guis/Flowee/BitcoinAmountLabel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 355506f..48be8dd 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -129,7 +129,7 @@ QQC2.Control { fiatPrice = Fiat.price; // todays price else fiatPrice = Fiat.historicalPrice(root.fiatTimestamp); - Fiat.formattedPrice(root.value, fiatPrice) + return Fiat.formattedPrice(root.value, fiatPrice) } } } -- 2.54.0 From 0f11944b031abbba7ba71213d750ebb7f9bfa2e3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 16:06:23 +0100 Subject: [PATCH 0385/1428] Iterate on account history look. This show the bch price on the front page too and shows 'moved' as a label in the popup. We give the full width to cashfusion transactions, no point is listing the amount of sats fee paid. This always puts a plus or minus in front of the price indicating the receiving vs sending of funds. --- guis/mobile/AccountHistory.qml | 47 +++++++++++++++++++++++++++------- guis/mobile/TxInfoSmall.qml | 27 +++++++++---------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 64ec411..1766070 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -160,6 +160,15 @@ ListView { id: transactionDelegate property var placementInGroup: model.placementInGroup + property double amountBch: model.fundsOut - model.fundsIn + // Is this transaction a 'move between addresses' tx. + // This is a heuristic and not available in the model, which is why its in the view. + property bool isMoved: { + if (model.isCoinbase || model.isCashFusion || model.fundsIn === 0) + return false; + return amountBch > 0 && amountBch < -2500 // then the diff is likely just fees. + } + width: root.width height: 80 clip: true @@ -208,10 +217,10 @@ ListView { Flowee.Label { id: commentLabel anchors.bottom: ruler.top - anchors.right: price.left + anchors.right: price.visible ? price.left : price.right anchors.left: parent.left anchors.leftMargin: 80 - clip: true // TODO wordwrap? + clip: true // future, maybe wordwrap? text: { var comment = model.comment if (comment !== "") @@ -223,13 +232,13 @@ ListView { return qsTr("Cash Fusion"); if (model.fundsIn === 0) return qsTr("Received"); - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // then the diff is likely just fees. + if (isMoved) return qsTr("Moved"); return qsTr("Sent"); } } QQC2.Label { + id: dateLine anchors.top: ruler.bottom anchors.left: commentLabel.left color: palette.text @@ -258,16 +267,19 @@ ListView { } } + // price in a green rectangle Rectangle { id: price width: amount.width + 10 - height: amount.height + 10 + height: amount.height + 8 anchors.right: parent.right + anchors.bottom: ruler.top + anchors.bottomMargin: -4 anchors.rightMargin: 20 - anchors.verticalCenter: ruler.verticalCenter radius: 6 + baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: + visible: !model.isCashFusion - property int amountBch: model.fundsOut - model.fundsIn color: amountBch < 0 ? "#00000000" : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background Flowee.Label { @@ -278,13 +290,30 @@ ListView { var fiatPrice = Fiat.price; else fiatPrice = Fiat.historicalPrice(dat); - return Fiat.formattedPrice(price.amountBch, fiatPrice); + return Fiat.formattedPrice(Math.abs(amountBch), fiatPrice); } anchors.centerIn: parent - opacity: Math.abs(price.amountBch) < 2000 ? 0.5 : 1 + opacity: Math.abs(amountBch) < 2000 ? 0.5 : 1 } } + Flowee.Label { // plus or minus in front of the price + visible: price.visible + text: amountBch >= 0 ? "+" : "-" + anchors.baseline: price.baseline + anchors.right: price.left + opacity: price.opacity + } + Flowee.BitcoinAmountLabel { + visible: price.visible + anchors.right: parent.right + anchors.rightMargin: 25 + anchors.baseline: dateLine.baseline + value: amountBch + showFiat: false + fontPixelSize: amount.font.pixelSize * 0.9 + } + // horizontal separator Rectangle { visible: transactionDelegate.placementInGroup !== Wallet.GroupEnd && transactionDelegate.placementInGroup !== Wallet.Ungrouped; diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 221b555..e454fa0 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -21,14 +21,20 @@ import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; +/* + * This is a simple widget that is used in the AccountHistory page. + * Notice that to work it expects in the parent context to be available several things, + * among others the isMoved and amountBch values for the transaction it is displaying + */ GridLayout { id: root columns: 2 rowSpacing: 10 + // set by the parent page property QtObject infoObject: null - property int minedHeight: model.height + property int minedHeight: model.height // local cache QQC2.Label { Layout.columnSpan: 2 @@ -70,12 +76,6 @@ GridLayout { } Flowee.Label { id: paymentTypeLabel - visible: { - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // this is our heuristic to mark it as 'moved' - return false; - return true; - } text: { if (model.isCoinbase) return qsTr("Miner Reward") + ":"; @@ -83,8 +83,7 @@ GridLayout { return qsTr("Cash Fusion") + ":"; if (model.fundsIn === 0) return qsTr("Received") + ":"; - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // then the diff is likely just fees. + if (isMoved) // inherited from AccountHistory.qml return qsTr("Moved") + ":"; return qsTr("Sent") + ":"; } @@ -106,8 +105,7 @@ GridLayout { return false; if (model.isCashFusion) return false; - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // then the diff is likely just fees. + if (isMoved) return false; if (valueThenLabel.fiatPrice === 0) return false; @@ -119,8 +117,9 @@ GridLayout { Layout.fillWidth: true id: valueThenLabel visible: priceAtMining.visible + // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false property int fiatPrice: Fiat.historicalPriceAccurate(model.date) - text: Fiat.formattedPrice(Math.abs(model.fundsOut - model.fundsIn), fiatPrice) + text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) } Flowee.Label { visible: priceAtMining.visible @@ -134,9 +133,7 @@ GridLayout { return ""; var fiatPriceNow = Fiat.price; var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - - var sats = Math.abs(model.fundsOut - model.fundsIn); - return Fiat.formattedPrice(sats, fiatPriceNow) + return Fiat.formattedPrice(amountBch, fiatPriceNow) + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; } } -- 2.54.0 From 84fde6137324d6484072a992b1889df3216cc879 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 17:17:29 +0100 Subject: [PATCH 0386/1428] Give credit to a very active beta tester. --- guis/mobile/About.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index b4c8ae4..da500b3 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,6 +70,10 @@ You? You? +## Beta Testers + +fshinetop + ## Translations Nederland
-- 2.54.0 From 9bfbb3ea5133da57ae2a9462c557e94ad299975c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 18:08:22 +0100 Subject: [PATCH 0387/1428] Replace placeholder with some basic icons. --- guis/mobile.qrc | 9 +++++++++ guis/mobile/AccountHistory.qml | 18 +++++++++++++++--- guis/mobile/images/tx-coinbase-light.svg | 4 ++++ guis/mobile/images/tx-coinbase.svg | 4 ++++ guis/mobile/images/tx-move-light.svg | 13 +++++++++++++ guis/mobile/images/tx-move.svg | 13 +++++++++++++ guis/mobile/images/tx-receiving-light.svg | 11 +++++++++++ guis/mobile/images/tx-receiving.svg | 11 +++++++++++ guis/mobile/images/tx-send-light.svg | 7 +++++++ guis/mobile/images/tx-send.svg | 7 +++++++ 10 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 guis/mobile/images/tx-coinbase-light.svg create mode 100644 guis/mobile/images/tx-coinbase.svg create mode 100644 guis/mobile/images/tx-move-light.svg create mode 100644 guis/mobile/images/tx-move.svg create mode 100644 guis/mobile/images/tx-receiving-light.svg create mode 100644 guis/mobile/images/tx-receiving.svg create mode 100644 guis/mobile/images/tx-send-light.svg create mode 100644 guis/mobile/images/tx-send.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6447652..2574ae2 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -2,6 +2,7 @@ images/FloweePay-light.svg images/FloweePay.svg + images/cashfusion.svg images/bch.svg mobile/images/back-arrow.svg mobile/images/maslenica.svg @@ -23,6 +24,14 @@ mobile/images/settingsIcon.svg mobile/images/edit.svg mobile/images/edit-light.svg + mobile/images/tx-receiving.svg + mobile/images/tx-receiving-light.svg + mobile/images/tx-send.svg + mobile/images/tx-send-light.svg + mobile/images/tx-coinbase.svg + mobile/images/tx-coinbase-light.svg + mobile/images/tx-move.svg + mobile/images/tx-move-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 1766070..8d06e93 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -206,11 +206,23 @@ ListView { height: 6 anchors.verticalCenter: parent.verticalCenter } - Rectangle { + // icon + Image { + source: { + if (model.isCashFusion) + return "qrc:/cashfusion.svg"; + if (model.fundsIn === 0) + var base = "receiving"; + else if (isMoved) + base = "move"; + else + base = "send"; + return "qrc:/tx-"+ base + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + } width: 45 height: 45 - radius: 22.5 x: 20 + smooth: true anchors.verticalCenter: ruler.verticalCenter } @@ -237,7 +249,7 @@ ListView { return qsTr("Sent"); } } - QQC2.Label { + Flowee.Label { id: dateLine anchors.top: ruler.bottom anchors.left: commentLabel.left diff --git a/guis/mobile/images/tx-coinbase-light.svg b/guis/mobile/images/tx-coinbase-light.svg new file mode 100644 index 0000000..c0f8bd3 --- /dev/null +++ b/guis/mobile/images/tx-coinbase-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/tx-coinbase.svg b/guis/mobile/images/tx-coinbase.svg new file mode 100644 index 0000000..946ba3e --- /dev/null +++ b/guis/mobile/images/tx-coinbase.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/tx-move-light.svg b/guis/mobile/images/tx-move-light.svg new file mode 100644 index 0000000..c964e6b --- /dev/null +++ b/guis/mobile/images/tx-move-light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/guis/mobile/images/tx-move.svg b/guis/mobile/images/tx-move.svg new file mode 100644 index 0000000..b19a691 --- /dev/null +++ b/guis/mobile/images/tx-move.svg @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/guis/mobile/images/tx-receiving-light.svg b/guis/mobile/images/tx-receiving-light.svg new file mode 100644 index 0000000..a80fbc5 --- /dev/null +++ b/guis/mobile/images/tx-receiving-light.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/guis/mobile/images/tx-receiving.svg b/guis/mobile/images/tx-receiving.svg new file mode 100644 index 0000000..2cadab5 --- /dev/null +++ b/guis/mobile/images/tx-receiving.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/guis/mobile/images/tx-send-light.svg b/guis/mobile/images/tx-send-light.svg new file mode 100644 index 0000000..eb71272 --- /dev/null +++ b/guis/mobile/images/tx-send-light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/guis/mobile/images/tx-send.svg b/guis/mobile/images/tx-send.svg new file mode 100644 index 0000000..b22b9f7 --- /dev/null +++ b/guis/mobile/images/tx-send.svg @@ -0,0 +1,7 @@ + + + + + + + -- 2.54.0 From 9508df00dc5eaf0becdd2a15a269020000f216e7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 15 Mar 2023 19:19:16 +0100 Subject: [PATCH 0388/1428] fix isMoved check --- guis/mobile/AccountHistory.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 8d06e93..c684494 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -166,7 +166,7 @@ ListView { property bool isMoved: { if (model.isCoinbase || model.isCashFusion || model.fundsIn === 0) return false; - return amountBch > 0 && amountBch < -2500 // then the diff is likely just fees. + return amountBch < 0 && amountBch > -2500 // then the diff is likely just fees. } width: root.width -- 2.54.0 From 7b35d70e535f2daafa35667403b989c453c44789 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 18:23:11 +0100 Subject: [PATCH 0389/1428] Update Qt version --- android/build-pay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 89d5d68..87d7ba7 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="flowee/buildenv-android:v6.4.1" + _docker_name_="flowee/buildenv-android:v6.4.3" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then -- 2.54.0 From 969ed7a4d4b941512d4db02e9cc51ec5f4eed812 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 19:18:24 +0100 Subject: [PATCH 0390/1428] Move to Qt 6.4.3 for Android --- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index b2f252e..8825b52 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.4.2 +ARG QtVersion=v6.4.3 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index f803748..a7f1c84 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.4.2 +QtVersion=v6.4.3 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion -- 2.54.0 From bfac327fb1c05d7a33b7ab64d97a46f2e0a91c52 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 21:27:08 +0100 Subject: [PATCH 0391/1428] Make the font smaller to make it fit. --- guis/desktop/main.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 70333ef..876f13d 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -450,6 +450,7 @@ ApplicationWindow { id: balanceDetailsPane property bool showDetails: false width: parent.width + clip: true // avoid the balance overlapping the tabbar. height: balance.height + (showDetails ? extraBalances.height + 10 : 0) Flowee.BitcoinAmountLabel { id: balance @@ -469,8 +470,8 @@ ApplicationWindow { color: palette.windowText fontPixelSize: { if (leftColumn.width < 240) // max width is 252 - return leftColumn.width / 7 - return 36; + return leftColumn.width / 9 + return 27; } } -- 2.54.0 From 80c365f3c46425e0ad3590a5986a70bcad8df3af Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 21:39:36 +0100 Subject: [PATCH 0392/1428] Avoid cutting off large balances Split the BCH and fiat prices over two lines. --- guis/mobile/AccountHistory.qml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index c684494..848e317 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -64,7 +64,7 @@ ListView { /* The structure here is a bit funny. But we want to have the entire page scroll in order to show all the transactions we can. - To avoid a mess of two scroll-areas (of Flickables) we simply make the top part into a + To avoid a mess of two scroll-areas (or Flickables) we simply make the top part into a header of the listview. */ header: Rectangle { @@ -79,14 +79,15 @@ ListView { Flowee.BitcoinAmountLabel { opacity: Pay.hideBalance ? 0.2 : 1 - fontPixelSize: 34 + fontPixelSize: 30 + anchors.horizontalCenter: parent.horizontalCenter value: { if (Pay.hideBalance) return 88888888; return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed } colorize: false - + showFiat: false MouseArea { anchors.fill: parent onClicked: popupOverlay.open(priceDetails, parent) @@ -96,6 +97,16 @@ ListView { PriceDetails { } } } + Flowee.Label { // fiat price + opacity: Pay.hideBalance ? 0.2 : 1 + anchors.horizontalCenter: parent.horizontalCenter + text: { + if (Pay.hideBalance) + return Fiat.currencySymbolPrefix + "——" + Fiat.currencySymbolPost + Fiat.formattedPrice(portfolio.current.balanceConfirmed + + portfolio.current.balanceUnconfirmed, Fiat.price) + } + } AccountSyncState { account: portfolio.current -- 2.54.0 From 4f595b5a36a5ea8a6ee9835ece81ab629c81a999 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 21:43:34 +0100 Subject: [PATCH 0393/1428] Document "Pay" properties. --- src/FloweePay.h | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 281b05f..1cef15e 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -49,23 +49,27 @@ QString renderAddress(const KeyId &pubkeyhash); class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterface { Q_OBJECT - Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString libsVersion READ libsVersion CONSTANT) - Q_PROPERTY(int windowWidth READ windowWidth WRITE setWindowWidth NOTIFY windowWidthChanged) - Q_PROPERTY(int windowHeight READ windowHeight WRITE setWindowHeight NOTIFY windowHeightChanged) - Q_PROPERTY(int unitAllowedDecimals READ unitAllowedDecimals NOTIFY unitChanged) + Q_PROPERTY(QString platform READ platform CONSTANT) + // p2p net Q_PROPERTY(int headerChainHeight READ headerChainHeight NOTIFY headerChainHeightChanged) Q_PROPERTY(int expectedChainHeight READ expectedChainHeight NOTIFY expectedChainHeightChanged) Q_PROPERTY(int chainHeight READ chainHeight NOTIFY headerChainHeightChanged) - Q_PROPERTY(bool useDarkSkin READ darkSkin WRITE setDarkSkin NOTIFY darkSkinChanged) Q_PROPERTY(bool isMainChain READ isMainChain CONSTANT) + // GUI user settings + Q_PROPERTY(int windowWidth READ windowWidth WRITE setWindowWidth NOTIFY windowWidthChanged) + Q_PROPERTY(int windowHeight READ windowHeight WRITE setWindowHeight NOTIFY windowHeightChanged) + Q_PROPERTY(bool useDarkSkin READ darkSkin WRITE setDarkSkin NOTIFY darkSkinChanged) Q_PROPERTY(bool hideBalance READ hideBalance WRITE setHideBalance NOTIFY hideBalanceChanged) - Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged); - Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) - Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) - Q_PROPERTY(QString platform READ platform CONSTANT) + Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) + // User setting based on which unit (milli/sats/etc) the user choose + Q_PROPERTY(int unitAllowedDecimals READ unitAllowedDecimals NOTIFY unitChanged) + Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) + Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) + // notifications + Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged); public: enum UnitOfBitcoin { BCH, -- 2.54.0 From c4836213e5a6d17a5e387b1b9e5eee2a1177d003 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 21:55:41 +0100 Subject: [PATCH 0394/1428] Remove unused include --- src/FloweePay.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 1cef15e..0443ba3 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -19,7 +19,6 @@ #define FLOWEEPAY_H #include "NotificationManager.h" -#include "PriceHistoryDataProvider.h" #include "WalletEnums.h" #include -- 2.54.0 From 3bdebe7b8d3fda571eb43025f6e26c99482e1d82 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 22:25:41 +0100 Subject: [PATCH 0395/1428] Introduce GUI settting: show bch. On the main screen, also known as the "Activity view" this allows the user to choose to only see fiat or see both Bitcoin Cash and fiat values listed next to each transaction. --- guis/desktop/SettingsPane.qml | 16 +++++++++++++++- guis/desktop/WalletTransaction.qml | 25 +++++++++++++++++++++++-- guis/mobile/AccountHistory.qml | 5 +++-- guis/mobile/GuiSettings.qml | 15 +++++++++++++++ src/FloweePay.cpp | 22 ++++++++++++++++++++-- src/FloweePay.h | 9 ++++++++- 6 files changed, 84 insertions(+), 8 deletions(-) diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index edfed75..4e29457 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,8 @@ Pane { GridLayout { columns: 3 + rowSpacing: 10 + columnSpacing: 6 Label { text: qsTr("Unit") + ":" @@ -90,6 +92,18 @@ Pane { } } + Flowee.CheckBox { + id: showBchOnActivity + Layout.alignment: Qt.AlignRight + checked: Pay.activityShowsBch + onCheckedChanged: Pay.activityShowsBch = checked + } + Flowee.CheckBoxLabel { + Layout.columnSpan: 2 + buddy: showBchOnActivity + text: qsTr("Show Bitcoin Cash value on Activity page") + } + Flowee.CheckBox { id: showBlockNotificationsChooser Layout.alignment: Qt.AlignRight diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index 948fd70..761c6f6 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -133,6 +133,7 @@ Rectangle { Flowee.BitcoinAmountLabel { id: bitcoinAmountLabel + visible: Pay.activityShowsBch value: { let inputs = model.fundsIn let outputs = model.fundsOut @@ -142,11 +143,31 @@ Rectangle { return inputs; return diff; } - fiatTimestamp: model.date; - anchors.top: mainLabel.top anchors.right: parent.right } + Flowee.Label { + anchors.top: mainLabel.top + anchors.right: parent.right + visible: !Pay.activityShowsBch + text: { + var timestamp = model.date; + if (timestamp === undefined) + var fiatPrice = Fiat.price; // todays price + else + fiatPrice = Fiat.historicalPrice(timestamp); + return Fiat.formattedPrice(bitcoinAmountLabel.value, fiatPrice) + } + color: { + var num = bitcoinAmountLabel.value + if (num > 0) // positive value + return Pay.useDarkSkin ? "#86ffa8" : "green"; + else if (num < 0) // negative + return Pay.useDarkSkin ? "#ffdede" : "#444446"; + // zero is shown without normally + return palette.windowText + } + } MouseArea { anchors.fill: parent diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 848e317..3a3932e 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -296,7 +296,8 @@ ListView { width: amount.width + 10 height: amount.height + 8 anchors.right: parent.right - anchors.bottom: ruler.top + anchors.bottom: Pay.activityShowsBch ? ruler.top : undefined + anchors.verticalCenter: Pay.activityShowsBch ? undefined : ruler.verticalCenter anchors.bottomMargin: -4 anchors.rightMargin: 20 radius: 6 @@ -327,7 +328,7 @@ ListView { opacity: price.opacity } Flowee.BitcoinAmountLabel { - visible: price.visible + visible: price.visible && Pay.activityShowsBch anchors.right: parent.right anchors.rightMargin: 25 anchors.baseline: dateLine.baseline diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index cbf4ab0..ec1359f 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -139,5 +139,20 @@ Page { showPageIcon: true onClicked: thePile.push("./CurrencySelector.qml") } + + Flowee.Label { + text: qsTr("Main View") + font.bold: true + } + Item { + width: 10 + height: 10 + } + + Flowee.CheckBox { + text: qsTr("Show Bitcoin Cash value") + checked: Pay.activityShowsBch + onCheckedChanged: Pay.activityShowsBch = checked + } } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index de9c435..8bdc584 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +50,8 @@ constexpr const char *WINDOW_WIDTH = "window/width"; constexpr const char *WINDOW_HEIGHT = "window/height"; constexpr const char *FONTSCALING = "window/font-scaling"; constexpr const char *DARKSKIN = "darkSkin"; -constexpr const char *HIDEBALANCE = "hideBalance"; +constexpr const char *ACTIVITYSHOWBCH = "activity-show-bch"; +constexpr const char *HIDEBALANCE = "hide-balance"; constexpr const char *USERAGENT = "net/useragent"; constexpr const char *DSPTIMEOUT = "payment/dsp-timeout"; constexpr const char *CURRENCY_COUNTRIES = "countryCodes"; // historical @@ -108,6 +109,7 @@ FloweePay::FloweePay() m_windowHeight = defaultConfig.value(WINDOW_HEIGHT, -1).toInt(); m_windowWidth = defaultConfig.value(WINDOW_WIDTH, -1).toInt(); m_darkSkin = defaultConfig.value(DARKSKIN, true).toBool(); + m_activityShowsBch = defaultConfig.value(ACTIVITYSHOWBCH, false).toBool(); m_dspTimeout = defaultConfig.value(DSPTIMEOUT, 3000).toInt(); m_fontScaling = defaultConfig.value(FONTSCALING, 100).toInt(); m_createStartWallet = defaultConfig.value(CREATE_START_WALLET, false).toBool(); @@ -117,6 +119,7 @@ FloweePay::FloweePay() m_windowHeight = appConfig.value(WINDOW_HEIGHT, m_windowHeight).toInt(); m_windowWidth = appConfig.value(WINDOW_WIDTH, m_windowWidth).toInt(); m_darkSkin = appConfig.value(DARKSKIN, m_darkSkin).toBool(); + m_activityShowsBch = appConfig.value(ACTIVITYSHOWBCH, m_activityShowsBch).toBool(); m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); @@ -694,6 +697,21 @@ uint32_t FloweePay::walletStartHeightHint() const return time(nullptr); } +bool FloweePay::activityShowsBch() const +{ + return m_activityShowsBch; +} + +void FloweePay::setActivityShowsBch(bool newActivityShowsBch) +{ + if (m_activityShowsBch == newActivityShowsBch) + return; + m_activityShowsBch = newActivityShowsBch; + emit activityShowsBchChanged(); + QSettings appConfig; + appConfig.setValue(ACTIVITYSHOWBCH, m_activityShowsBch); +} + int FloweePay::fontScaling() const { return m_fontScaling; diff --git a/src/FloweePay.h b/src/FloweePay.h index 0443ba3..96ca652 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -61,6 +61,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(int windowHeight READ windowHeight WRITE setWindowHeight NOTIFY windowHeightChanged) Q_PROPERTY(bool useDarkSkin READ darkSkin WRITE setDarkSkin NOTIFY darkSkinChanged) Q_PROPERTY(bool hideBalance READ hideBalance WRITE setHideBalance NOTIFY hideBalanceChanged) + Q_PROPERTY(bool activityShowsBch READ activityShowsBch WRITE setActivityShowsBch NOTIFY activityShowsBchChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) // User setting based on which unit (milli/sats/etc) the user choose @@ -265,6 +266,9 @@ public: int fontScaling() const; void setFontScaling(int newFontScaling); + bool activityShowsBch() const; + void setActivityShowsBch(bool newActivityShowsBch); + signals: void loadComplete(); /// \internal @@ -282,6 +286,7 @@ signals: void hideBalanceChanged(); void newBlockMutedChanged(); void fontScalingChanged(); + void activityShowsBchChanged(); private slots: void loadingCompleted(); @@ -313,6 +318,8 @@ private: bool m_darkSkin = true; bool m_createStartWallet = false; bool m_hideBalance = false; + /// Show the BCH amount involved in a transaction on the activity screen. + bool m_activityShowsBch = false; bool m_offline = false; bool m_gotHeadersSyncedOnce = false; }; -- 2.54.0 From 01f87acc281cbe212d98d87c9237aafaedada061 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 21 Mar 2023 23:33:23 +0100 Subject: [PATCH 0396/1428] Add address on QR This exports the payment-request address to the QML side. We use this to show under the QR the address we are requesting to send to. This is quite useful for users wanting to manually check if things are going well. --- guis/Flowee/QRWidget.qml | 111 +++++++++++++++--------- guis/desktop/ReceiveTransactionPane.qml | 3 +- guis/mobile/ReceiveTab.qml | 1 + guis/mobile/StartupScreen.qml | 3 +- src/PaymentRequest.cpp | 38 ++++---- src/PaymentRequest.h | 9 +- 6 files changed, 103 insertions(+), 62 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 2649af5..109019d 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -18,54 +18,87 @@ import QtQuick import Flowee.org.pay -Image { +Item { id: root property QtObject request: null - - width: height - height: { - var h = parent.height - 220; - return Math.min(h, 256) - } - source: root.request == null ? "" : "image://qr/" + root.request.qr - smooth: false + property alias qrSize: qrImage.width + implicitWidth: qrImage.width + implicitHeight: qrImage.width + addressLine.height opacity: root.request == null || root.request.state === PaymentRequest.Unpaid ? 1: 0 - MouseArea { - anchors.fill: parent - onClicked: { - Pay.copyToClipboard(root.request.qr) - // invert the feedback so a second tap removes the feedback again. - clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 - } - } - - Rectangle { - id: clipboardFeedback - opacity: 0 - width: feedbackText.width + 20 - height: feedbackText.height + 14 - radius: 10 - color: Pay.useDarkSkin ? "#333" : "#ddd" - anchors.bottom: parent.bottom - anchors.bottomMargin: -8 + Image { + id: qrImage + source: root.request == null ? "" : "image://qr/" + root.request.qr + smooth: false + width: 256 // exported at root level + height: width anchors.horizontalCenter: parent.horizontalCenter - Label { - id: feedbackText - x: 10 - y: 10 - text: qsTr("Copied to clipboard") - wrapMode: Text.WordWrap + MouseArea { + anchors.fill: parent + onClicked: { + Pay.copyToClipboard(root.request.qr) + // invert the feedback so a second tap removes the feedback again. + clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 + } } - Behavior on opacity { OpacityAnimator {} } + Rectangle { + id: clipboardFeedback + opacity: 0 + width: feedbackText.width + 20 + height: feedbackText.height + 14 + radius: 10 + color: Pay.useDarkSkin ? "#333" : "#ddd" + anchors.bottom: parent.bottom + anchors.bottomMargin: -8 + anchors.horizontalCenter: parent.horizontalCenter - /// after 8 seconds, remove feedback. - Timer { - interval: 8000 - running: clipboardFeedback.opacity >= 1 - onTriggered: clipboardFeedback.opacity = 0 + Label { + id: feedbackText + x: 10 + y: 10 + text: qsTr("Copied to clipboard") + wrapMode: Text.WordWrap + } + + Behavior on opacity { OpacityAnimator {} } + + /// after 8 seconds, remove feedback. + Timer { + interval: 8000 + running: clipboardFeedback.opacity >= 1 + onTriggered: clipboardFeedback.opacity = 0 + } + } + } + Rectangle { + id: addressLine + width: parent.width + height: addressLabel.height + 10 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + radius: 6 + color: palette.base + Label { + id: addressLabel + anchors.centerIn: parent + width: parent.width + text: { + if (root.request == null) + return ""; + var address = root.request.address + let index = address.indexOf(":"); + if (index >= 0) + address = address.substr(index + 1); // cut off the prefix + return address; + } + horizontalAlignment: Text.AlignHCenter + + // font: + minimumPixelSize: 2 // min + font.pixelSize: 20 // max + fontSizeMode: Text.HorizontalFit // fit in width } } diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index 0f25351..8e1f622 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -53,8 +53,9 @@ Pane { Flowee.QRWidget { id: qr - anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(parent.width, 360) anchors.top: instructions.bottom + anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 20 } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index def991a..7b7ab95 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -72,6 +72,7 @@ FocusScope { anchors.horizontalCenter: parent.horizontalCenter anchors.top: instructions.bottom anchors.topMargin: 20 + width: parent.width } // the "payment received" screen. diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 8ef8a2e..fac81d3 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -152,9 +152,8 @@ Page { } Flowee.QRWidget { - id: bla request: portfolio.current.createPaymentRequest(root) - x: (column.width - width) / 2 + width: parent.width } Item { width: 1; height: 40 } // spacer diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 114aec7..0f8ac58 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -77,6 +77,23 @@ void PaymentRequest::setPaymentState(PaymentState newState) emit paymentStateChanged(); } +QString PaymentRequest::address() const +{ + if (m_address.IsNull()) + return QString(); + if (m_useLegacyAddressFormat) { + CBase58Data legacy; + legacy.setData(m_address, CBase58Data::PubkeyType, + FloweePay::instance()->chain() == P2PNet::MainChain + ? CBase58Data::Mainnet : CBase58Data::Testnet); + return QString::fromStdString(legacy.ToString()); + } + CashAddress::Content c; + c.hash = std::vector(m_address.begin(), m_address.end()); + c.type = CashAddress::PUBKEY_TYPE; + return QString::fromStdString(CashAddress::encodeCashAddr(FloweePay::instance()->chainPrefix(), c)); +} + void PaymentRequest::setWallet(Wallet *wallet) { if (m_wallet == wallet) @@ -264,24 +281,9 @@ qint64 PaymentRequest::amount() const QString PaymentRequest::qrCodeString() const { - QString rc; if (m_address.IsNull()) - return rc; - // add address - if (m_useLegacyAddressFormat) { - CBase58Data legacy; - legacy.setData(m_address, CBase58Data::PubkeyType, - FloweePay::instance()->chain() == P2PNet::MainChain - ? CBase58Data::Mainnet : CBase58Data::Testnet); - rc += QString::fromStdString(legacy.ToString()); - } - else { - CashAddress::Content c; - c.hash = std::vector(m_address.begin(), m_address.end()); - c.type = CashAddress::PUBKEY_TYPE; - rc += QString::fromStdString(CashAddress::encodeCashAddr(FloweePay::instance()->chainPrefix(), c)); - } - + return QString(); + QString rc = address(); bool separatorInserted = false; // the questionmark. if (m_amountRequested > 0) { // Amount is in whole BCHs diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 189a12b..0905691 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2022 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ class AccountInfo; class PaymentRequest : public QObject { Q_OBJECT + Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) Q_PROPERTY(QString qr READ qrCodeString NOTIFY qrCodeStringChanged) Q_PROPERTY(double amount READ amountFP WRITE setAmountFP NOTIFY amountChanged) @@ -111,6 +112,9 @@ public: void setWallet(Wallet *wallet); + QString address() const; + void setAddress(const QString &newAddress); + signals: void messageChanged(); void qrCodeStringChanged(); @@ -122,6 +126,8 @@ signals: void walletChanged(); void storedChanged(); + void addressChanged(); + private slots: void walletEncryptionChanged(); @@ -132,7 +138,6 @@ protected: private: void setPaymentState(PaymentState newState); - Wallet *m_wallet; QString m_message; KeyId m_address; -- 2.54.0 From e6efedee87ba0863f8944e6fce06f33860bf7622 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 23 Mar 2023 12:34:16 +0100 Subject: [PATCH 0397/1428] Fix positioning Anchors are not fully declarative. See report on jira at bugreports.qt.io with issue-code: QTBUG-112230 --- guis/mobile/AccountHistory.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 3a3932e..8a090b6 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -296,9 +296,11 @@ ListView { width: amount.width + 10 height: amount.height + 8 anchors.right: parent.right - anchors.bottom: Pay.activityShowsBch ? ruler.top : undefined - anchors.verticalCenter: Pay.activityShowsBch ? undefined : ruler.verticalCenter - anchors.bottomMargin: -4 + y: { + if (Pay.activityShowsBch) // my bottom at parent verticalCenter + return parent.height / 2 - height + return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter + } anchors.rightMargin: 20 radius: 6 baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: -- 2.54.0 From 67d2553d2e2409bbc56f5674fe684b1716283840 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 23 Mar 2023 12:55:53 +0100 Subject: [PATCH 0398/1428] Fixes and cleanups The dialog now visually looks the same as others on mobile. --- guis/Flowee/Dialog.qml | 13 +++++++++---- guis/Flowee/DialogButtonBox.qml | 12 +++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 1cce1fe..abbd986 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -18,7 +18,6 @@ import QtQuick 2.11 import QtQuick.Controls as QQC2 import QtQuick.Layouts -import "." as Flowee; QQC2.Popup { id: root @@ -28,7 +27,7 @@ QQC2.Popup { property alias title: titleLabel.text property alias text: mainTextLabel.text property alias contentComponent: content.sourceComponent - property alias standardButtons: buttons.standardButtons + property alias standardButtons: buttons.standardButtons function accept() { accepted(); @@ -56,6 +55,13 @@ QQC2.Popup { y = local.y; } + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + Column { width: { // 'mainWindow' is defined in main.qml @@ -85,7 +91,6 @@ QQC2.Popup { } Label { id: mainTextLabel - // this next line will always create a binding loop. But its harmless, so ignore that comment. width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere } @@ -93,7 +98,7 @@ QQC2.Popup { id: content width: parent.width } - Flowee.DialogButtonBox { + DialogButtonBox { id: buttons standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel anchors.right: parent.right diff --git a/guis/Flowee/DialogButtonBox.qml b/guis/Flowee/DialogButtonBox.qml index ec50cab..1ab0865 100644 --- a/guis/Flowee/DialogButtonBox.qml +++ b/guis/Flowee/DialogButtonBox.qml @@ -15,9 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import "." as Flowee; +import QtQuick +import QtQuick.Controls as QQC2 /* * This is a buttonbox that swaps button order based on platform defaults. @@ -48,13 +47,12 @@ QQC2.DialogButtonBox { } alignment: Qt.AlignRight - delegate: Flowee.Button { - // Round because fractional width values are possible. - width: Math.round(Math.min( + delegate: Button { + width: Math.min( implicitWidth, // Divide availableWidth (width - leftPadding - rightPadding) by the number of buttons, // then subtract the spacing between each button. - (root.availableWidth / root.count) - (root.spacing * (root.count-1)) + (root.availableWidth / root.count) - (root.spacing * (root.count-1) )) } -- 2.54.0 From dac5cf12227188de714aa57737623f745099de9f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 23 Mar 2023 20:03:39 +0100 Subject: [PATCH 0399/1428] Replace circle with simple progresbar Built a simple progresbar from rectangles. --- guis/mobile/AccountSyncState.qml | 134 ++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 802ca94..b2262ad 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -16,13 +16,12 @@ * along with this program. If not, see . */ import QtQuick -import "../Flowee" as Flowee import Flowee.org.pay; -import QtQuick.Shapes // for shape-path +import "../Flowee" as Flowee Item { id: root - height: indicator.height + 3 + (uptodate ? 0 : circleShape.height) + height: indicator.height + 3 + (uptodate ? 0 : progressbar.height + 10) property QtObject account: null property bool uptodate: false property int startPos: account.initialBlockHeight @@ -46,51 +45,106 @@ Item { function onLastBlockSynchedChanged() { checkIfDone(); } } - // The 'progress' circle. - Shape { - id: circleShape - - property int goalHeight: Pay.expectedChainHeight + // The progressbar + Rectangle { + id: progressbar + y: 10 // top spacing of widget, only when doing progress report + x: 10 + width: parent.width - 20 + height: 40 // percentLabel.height + 10 + color: "#00000000" + border.width: 2 + border.color: palette.midlight + radius: 10 visible: !root.uptodate - anchors.horizontalCenter: parent.horizontalCenter - width: 200 - height: 100 - antialiasing: true - smooth: true - ShapePath { - strokeColor: mainWindow.floweeBlue - strokeWidth: 3 - fillColor: mainWindow.floweeGreen - startX: 0; startY: 100 - PathAngleArc { - id: progressCircle - centerX: 100 - centerY: 100 - radiusX: 100; radiusY: 100 - startAngle: -180 - sweepAngle: { - let end = circleShape.goalHeight; - let startPos = root.startPos; - if (startPos == 0) - return 0; - let currentPos = root.account.lastBlockSynched; - let totalDistance = end - startPos; - if (totalDistance <= 6) - return 180; // uptodate - let ourProgress = currentPos - startPos; - return 180 * (ourProgress / totalDistance); - } - Behavior on sweepAngle { NumberAnimation { duration: 50 } } + property double progress: { + let startPos = root.startPos; + let end = Pay.expectedChainHeight + if (startPos == 0) + return 0; + let currentPos = root.account.lastBlockSynched; + let totalDistance = end - startPos; + if (totalDistance <= 6) + return 100; // uptodate + let ourProgress = currentPos - startPos; + return ourProgress / totalDistance; + } + + Rectangle { + width: Math.min(10, parent.width * parent.progress) + y: 2 + height: parent.height - 4 + color: palette.highlight + radius: 10 + } + + Rectangle { + id: bar + x: 5 + y: 2 + width: { + var full = progressbar.width; + var w = full * progressbar.progress + w = Math.min(w, full - 5) // the right max + w -= 5; // our X offset + Math.max(0, w); + } + height: parent.height - 4 + color: palette.highlight + clip: true + Flowee.Label { + id: percentLabel + text: (100 * progressbar.progress).toFixed(0) + "%" + y: 5 + x: progressbar.width / 2 - 10 - width / 2 + color: palette.highlightedText + } + } + Rectangle { + width: { + // just the last 10 pixels. + var full = progressbar.width; + return Math.max(0, full * progressbar.progress - (full - 10)); + } + x: progressbar.width - 10 + y: 2 + height: parent.height - 4 + color: palette.highlight + radius: 10 + } + Rectangle { + id: groove + y: 2 + height: parent.height - 4 + color: palette.light + clip: true + x: { + var full = progressbar.width; + var distance = progressbar.progress * full; + distance = Math.min(distance, full - 5); // and right + distance = Math.max(5, distance); // avoid the left rounding area + return distance; + } + width: { + var full = progressbar.width; + var w = full - progressbar.progress * full; + w -= 5; + w = Math.min(w, full - 10); // the rounded corners + return w; + } + Flowee.Label { + text: percentLabel.text + y: 5 + x: progressbar.width / 2 - 5 - width / 2 - parent.x + // color: palette.highlightedText } - PathLine { x: 100; y: 100 } - PathLine { x: 0; y: 100 } } } Flowee.Label { id: indicator width: parent.width - y: root.uptodate ? 0 : circleShape.height + 3 + y: root.uptodate ? 0 : progressbar.height + 13 wrapMode: Text.Wrap text: { if (isLoading) -- 2.54.0 From 082cba55ae6d220b42bed394aa2108c4011b8c7e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Mar 2023 15:42:53 +0200 Subject: [PATCH 0400/1428] Comments and const fixes. --- src/Wallet.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 97cfb48..9b0211a 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -554,7 +554,7 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s setUserOwnedWallet(true); emit utxosChanged(); emit appendedTransactions(firstNewTransaction, transactionsToSave.size()); - for (auto &tx : transactionsToSave) { // save the Tx to disk. + for (const auto &tx : transactionsToSave) { // save the Tx to disk. saveTransaction(tx); } emit startDelayedSave(); @@ -1370,9 +1370,10 @@ void Wallet::createNewPrivateKey(uint32_t currentBlockheight) m_secretsChanged = true; saveSecrets(); - if (currentBlockheight < 10000000) - // if its out of this range, likely its a timestamp (headers are not yet synched) + if (currentBlockheight < 10000000) { + // if that is false, its a timestamp: headers are not yet synched) rebuildBloom(); + } } bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) @@ -1395,8 +1396,10 @@ bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) m_secretsChanged = true; saveSecrets(); - if (startBlockHeight < 10000000) + if (startBlockHeight < 10000000) { + // if that is false, its a timestamp: headers are not yet synched) rebuildBloom(); + } return true; } logFatal() << "ERROR. Wallet: added string is not a private key"; -- 2.54.0 From d0f56c85eedd8e9d36f9639b29856fda72d71207 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Mar 2023 15:43:22 +0200 Subject: [PATCH 0401/1428] Don't send bloom filter more often than needed. --- src/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 9b0211a..dfeb70e 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -237,6 +237,7 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda if (needChangeAddresses + needMainAddresses > 0) { deriveHDKeys(needMainAddresses, needChangeAddresses); + updateBloom = false; // the deriveHDKeys just called rebuildBloom return true; } return false; @@ -421,7 +422,6 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s // so we make sure we matched all we can auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); wtx.outputs = newWtx.outputs; - needNewBloom = true; } wtx.minedBlock = header.createHash(); wtx.minedBlockHeight = blockHeight; -- 2.54.0 From ec8fa4b95d2cfb73e1a775510eed36d9e64cceb3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Mar 2023 16:06:12 +0200 Subject: [PATCH 0402/1428] Minor spacing fix --- src/PriceDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 768c369..b4e6583 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -254,7 +254,7 @@ void PriceDataProvider::finishedDownload() m_timer.start(20 * 1000); return; } - logCritical() << "Current fiat price: " << m_currencySymbolPrefix << m_currentPrice.price << m_currencySymbolPost; + logCritical().nospace() << "Current fiat price: " << m_currencySymbolPrefix << m_currentPrice.price << m_currencySymbolPost; m_timer.start(ReloadTimeout); } -- 2.54.0 From 6f46a18608bb99409222c1d2794ef336907f9079 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Mar 2023 16:08:07 +0200 Subject: [PATCH 0403/1428] Follow flowee-libs changes This implements the new API needed for the change in flowee libs. The commit there is: 1d309cd7ae9bb668b481fe650e17e15fad037b2b --- src/Wallet.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Wallet.h b/src/Wallet.h index aa9a8c8..bbe6e6f 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -105,6 +105,9 @@ public: void newTransactions(const BlockHeader &header, int blockHeight, const std::deque &blockTransactions) override; // notify about unconfirmed Tx. void newTransaction(const Tx &tx) override; + void rebuildFilter() override { + rebuildBloom(); + } /// Let the wallet know that it is up-to-date to \a height void setLastSynchedBlockHeight(int height) override; void headerSyncComplete() override; -- 2.54.0 From a06a0735957ba81d87dde9f50d200f1465a07e89 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Apr 2023 11:46:32 +0200 Subject: [PATCH 0404/1428] Fix assert, as found by linter. --- src/Wallet_support.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 6a36580..4fb8d8e 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -104,7 +104,7 @@ Wallet::OutputRef::OutputRef(uint64_t encoded) encoded >>= 16; assert(encoded < 0xFFFFFFFF); m_txid = encoded & 0x7FFFFFFF; - assert(m_txid >= 0); + assert(static_cast(m_txid) >= 0); } Wallet::OutputRef::OutputRef(int txIndex, int outputIndex) -- 2.54.0 From 73e10f8befad7494d82527e7c559d35ea282f83c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Apr 2023 13:36:07 +0200 Subject: [PATCH 0405/1428] Fix reordering of finding transactions In our wallet we very rarely end up in a situation where we have to ask the same transactions twice from a peer due to us having generated a bunch of new keys from a HD wallet in the middle of a series we asked that peer. Making the filter we sent at the start of the series be outdated halfway through. This is now handled properly. This adds a unit test which tests that we now reach the right balance and can add the same block multiple times without it failing. Fixes: #12 --- src/Wallet.cpp | 13 +++- src/Wallet.h | 20 +++++- src/Wallet_p.h | 2 +- src/Wallet_support.cpp | 104 +++++++++++++++++++++++++++++++ testing/wallet/TestWallet.cpp | 114 ++++++++++++++++++++++++++++------ testing/wallet/TestWallet.h | 10 +-- 6 files changed, 234 insertions(+), 29 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index dfeb70e..45b2756 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -379,15 +379,22 @@ void Wallet::newTransaction(const Tx &tx) rebuildBloom(); } -void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const std::deque &blockTransactions) +void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) { auto transactions = WalletPriv::sortTransactions(blockTransactions); std::deque transactionsToSave; - std::set ejectedTransactions; int firstNewTransaction; bool needNewBloom = false; { QMutexLocker locker(&m_lock); + std::set ejectedTransactions; + /* + * Our UTXO based system requires interpretation of state based on explicit ordering. + * So if we get something inserted out-of-order we're going to rollback the ones we already + * have AFTER this and re-apply them later. + */ + const auto insertBeforeData = removeTransactionsAfter(blockHeight - 1); // remove them for this block too! + firstNewTransaction = m_nextWalletTransactionId; for (auto &tx: transactions) { const uint256 txid = tx.createHash(); @@ -423,7 +430,7 @@ void Wallet::newTransactions(const BlockHeader &header, int blockHeight, const s auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); wtx.outputs = newWtx.outputs; } - wtx.minedBlock = header.createHash(); + wtx.minedBlock = blockId; wtx.minedBlockHeight = blockHeight; // Remember the signature type used for specific private keys diff --git a/src/Wallet.h b/src/Wallet.h index bbe6e6f..308d067 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -37,6 +37,7 @@ class WalletInfoObject; class TransactionInfo; class PaymentRequest; +struct WalletInsertBeforeData; // private wallet data struct. namespace P2PNet { struct Notification; @@ -102,7 +103,7 @@ public: * @param blockHeight the blockheight we know the header under. * @param blockTransactions The actual transactions. */ - void newTransactions(const BlockHeader &header, int blockHeight, const std::deque &blockTransactions) override; + void newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) override; // notify about unconfirmed Tx. void newTransaction(const Tx &tx) override; void rebuildFilter() override { @@ -502,6 +503,23 @@ private: */ Tx loadTransaction(const uint256 &txid, Streaming::BufferPool &pool) const; + struct InsertBeforeData + { + InsertBeforeData(Wallet *wallet); + // on destructor we re-insert the transactions. + ~InsertBeforeData(); + std::vector transactions; + Wallet *parent; + }; + bool m_inInsertBeforeCallback = false; + + /* + * Our UTXO based system requires interpretation of state based on explicit ordering. + * So if we get something inserted out-of-order we're going to rollback the ones we already + * have AFTER the header and re-apply them in the destructor of the InsertBeforeData struct. + */ + InsertBeforeData removeTransactionsAfter(int blockHeight); + /// helper method to upgrade older wallets and find the sigType from transactions to populate the field in walletSecrets. /// Returns true if any changes were made. void populateSigType(); diff --git a/src/Wallet_p.h b/src/Wallet_p.h index 9c6c9c8..2b37ff4 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 4fb8d8e..59e3b0e 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -361,3 +361,107 @@ void Wallet::populateSigType() } logCritical() << "Wallet upgrade finished"; } + + +// ////////////////////////////////////////////////// + +Wallet::InsertBeforeData::InsertBeforeData(Wallet *wallet) + : parent(wallet) +{ +} + +Wallet::InsertBeforeData::~InsertBeforeData() +{ + if (transactions.empty()) + return; + + // we proceed to call 'addTransactions' for each block + parent->m_inInsertBeforeCallback = true; + int blockHeight = 0; + uint256 blockId; + std::deque list; + for (auto wtx = transactions.rbegin(); wtx != transactions.rend(); ++wtx) { + if (wtx->minedBlockHeight != blockHeight) { + if (!list.empty()) + parent->newTransactions(blockId, blockHeight, list); + blockId = wtx->minedBlock; + blockHeight = wtx->minedBlockHeight; + list = std::deque(); + } + + // we need to re-load the tx from disk + auto tx = parent->loadTransaction(wtx->txid, Streaming::pool(0)); + assert(tx.isValid()); + list.push_back(tx); + } + if (!list.empty()) + parent->newTransactions(blockId, blockHeight, list); + parent->m_inInsertBeforeCallback = true; +} + +Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) +{ + InsertBeforeData ibd(this); + if (m_inInsertBeforeCallback) + return ibd; + + int lastToRemove = m_nextWalletTransactionId; + int txIndex = -1; + assert(m_walletTransactions.find(lastToRemove) == m_walletTransactions.end()); + for (auto i = m_walletTransactions.rbegin(); i != m_walletTransactions.rend(); ++i) { // backwards iterator + if (i->second.minedBlockHeight <= blockHeight) { + // avoid walking the entire chain, since they are in-order we can just abort here. + break; + } + lastToRemove = i->first; + if (txIndex == -1) // remember first to remove. + txIndex = i->first; + } + assert (lastToRemove <= m_nextWalletTransactionId); + + // we do this in two loops because a map reverse iterator doesn't have a matching 'erase' + // method. + // Using the 'key' in the next loop instead. + + while (txIndex >= lastToRemove) { + auto i = m_walletTransactions.find(txIndex--); + assert (i != m_walletTransactions.end()); + // this transaction was appended to the UTXO and now something has + // to be inserted before, which may alter the UTXO to such an extend that THIS + // transaction may be found to spend more outputs. + // + // first, re-add the utxo's that were removed in initial add. + const auto &wtx = i->second; + for (auto input : wtx.inputToWTX) { + OutputRef ref(input.second); + // the unspent outputs struct holds the value, which means we need to look that up again. + auto prev = m_walletTransactions.find(ref.txIndex()); + assert(prev != m_walletTransactions.end()); + const auto &outputs = prev->second.outputs; + auto prevOut = outputs.find(ref.outputIndex()); + assert(prevOut != outputs.end()); + m_unspentOutputs.insert(std::make_pair(input.second, prevOut->second.value)); + } + + // second, remove the UTXOs this transaction added. + for (auto o = wtx.outputs.begin(); o != wtx.outputs.end(); ++o) { + const auto key = OutputRef(i->first, o->first).encoded(); + auto utxo = m_unspentOutputs.find(key); + assert(utxo != m_unspentOutputs.end()); + m_unspentOutputs.erase(utxo); + + // things like coinbases may get a lock record, lets remove one if it exists. + auto lock = m_lockedOutputs.find(key); + if (lock != m_lockedOutputs.end()) { + m_lockedOutputs.erase(lock); + } + } + + ibd.transactions.push_back(i->second); + m_walletTransactions.erase(i); + auto txidIter = m_txidCache.find(wtx.txid); + assert(txidIter != m_txidCache.end()); + m_txidCache.erase(txidIter); + } + return ibd; +} diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index ebe83be..b26d48a 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -109,7 +109,7 @@ void TestWallet::addingTransactions() // now confirm it. std::deque list; list.push_back(t1); - wallet->newTransactions(MockBlockHeader(), 1, list); + wallet->newTransactions(dummyBlockId, 1, list); QCOMPARE(wallet->balanceConfirmed(), 1000000); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -204,12 +204,94 @@ void TestWallet::addingTransactions() QCOMPARE(wallet->balanceImmature(), 0); list.clear(); list.push_back(t4); - wallet->newTransactions(MockBlockHeader(), 2, list); + wallet->newTransactions(dummyBlockId, 2, list); QCOMPARE(wallet->balanceConfirmed(), 200000 - 1673); // output from b4 is confirmed QCOMPARE(wallet->balanceImmature(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 500000); } +void TestWallet::addingTransactions2() +{ + /* + * addTransaction() is for adding unconfirmed transactions. + * addTransactions() for confirmed transactions. + * + * The wallet may receive transactions out of order, for various reasons a first + * scan may miss transactions (private keys (HD) not generated yet, for instance). + * and thus we may get a transaction that is before the latest ones. + * + * We need to handle that. + * + * Test setup; + * + * tx1: deposits funds into 2 UTXOs. (1000 / 5000) + * tx2: spends tx1, utxo1 and deposits into a new utxo. (1000 -> 999) + * tx3: spends tx1 utxo2 and tx2 utxo and deposits it into a new utxo + * (1000 + 5000 -> 5990) + * + * add tx1 at block 200 + * add tx3 at block 210 + * add tx2 at block 202 + */ + + auto wallet = createWallet(); + + uint256 blockId200 = uint256S("0x09830942309482"); + uint256 blockId202 = uint256S("0x4905009200a93a"); + uint256 blockId210 = uint256S("0x590684209398322"); + + TransactionBuilder b1; + uint256 prevTxId = uint256S("0x12830924807308721309128309128"); + b1.appendInput(prevTxId, 0); + b1.appendOutput(1000); + b1.pushOutputPay2Address(wallet->nextUnusedAddress()); + b1.appendOutput(5000); + b1.pushOutputPay2Address(wallet->nextUnusedAddress()); + Streaming::BufferPool pool; + Tx tx1 = b1.createTransaction(&pool); + QCOMPARE(wallet->balanceConfirmed(), 0); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + wallet->newTransactions(blockId200, 200, {tx1}); + QCOMPARE(wallet->balanceConfirmed(), 6000); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + + TransactionBuilder b2; + b2.appendInput(tx1.createHash(), 0); + b2.appendOutput(999); + b2.pushOutputPay2Address(wallet->nextUnusedAddress()); + Tx tx2 = b2.createTransaction(&pool); + + TransactionBuilder b3; + b3.appendInput(tx1.createHash(), 1); + b3.appendInput(tx2.createHash(), 0); + b3.appendOutput(5990); + b3.pushOutputPay2Address(wallet->nextUnusedAddress()); + Tx tx3 = b3.createTransaction(&pool); + + wallet->newTransactions(blockId210, 210, {tx3}); + // the wallet doesn't know we spent one of its own + // outputs, so it assumes its new money. + // as such we deposited 1000 + 5990 + QCOMPARE(wallet->balanceConfirmed(), 6990); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + + wallet->newTransactions(blockId202, 202, {tx2}); + // the code should insert tx2 AND re-apply tx3 + // therefore coming up with the proper amount + // which removes the 1000 and leaves just the 5990 + QCOMPARE(wallet->balanceConfirmed(), 5990); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + // should have no effect: + wallet->newTransactions(blockId210, 210, {tx3}); + QCOMPARE(wallet->balanceConfirmed(), 5990); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); +} + void TestWallet::lockingOutputs() { Streaming::BufferPool pool; @@ -335,7 +417,7 @@ void TestWallet::testSpam() b1.pushOutputPay2Address(wallet->nextUnusedAddress()); Streaming::BufferPool pool; Tx t1 = b1.createTransaction(&pool); - wallet->newTransactions(MockBlockHeader(), 1, { t1 }); + wallet->newTransactions(dummyBlockId, 1, { t1 }); QCOMPARE(wallet->balanceConfirmed(), 1000000); @@ -357,7 +439,7 @@ void TestWallet::testSpam() b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); } Tx t2 = b2.createTransaction(&pool); - wallet->newTransactions(MockBlockHeader(), 2, { t2 } ); + wallet->newTransactions(dummyBlockId, 2, { t2 } ); QCOMPARE(wallet->balanceConfirmed(), 200000); // does NOT include the 547 QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -441,7 +523,7 @@ void TestWallet::saveTransaction2() Tx t1 = b1.createTransaction(&pool); std::deque list; list.push_back(t1); - wallet->newTransactions(MockBlockHeader(), 10, list); + wallet->newTransactions(dummyBlockId, 10, list); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 50); @@ -452,7 +534,7 @@ void TestWallet::saveTransaction2() b2.pushOutputPay2Address(wallet->nextUnusedAddress()); Tx t2 = b2.createTransaction(&pool); list[0] = t2; - wallet->newTransactions(MockBlockHeader(), 50, list); + wallet->newTransactions(dummyBlockId, 50, list); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 101); @@ -466,7 +548,7 @@ void TestWallet::saveTransaction2() b3.pushOutputPay2Address(id); Tx t3 = b3.createTransaction(&pool); list[0] = t3; - wallet->newTransactions(MockBlockHeader(), 140, list); + wallet->newTransactions(dummyBlockId, 140, list); QCOMPARE(wallet->balanceConfirmed(), 40); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 51); @@ -515,7 +597,7 @@ void TestWallet::unconfirmed() QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); - wallet->newTransactions(MockBlockHeader(), 10, std::deque() = { t1 }); + wallet->newTransactions(dummyBlockId, 10, std::deque() = { t1 }); QCOMPARE(wallet->balanceConfirmed(), 1000000); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -936,6 +1018,11 @@ std::unique_ptr TestWallet::openWallet(uint32_t encryptionSeed) return wallet; } +TestWallet::TestWallet() + : dummyBlockId(uint256S("0x198795438759712937981273")) +{ +} + void TestWallet::cleanup() { if (m_dir.isEmpty()) @@ -962,15 +1049,4 @@ void TestWallet::testRef() QCOMPARE(c.txIndex(), 12); } -MockBlockHeader::MockBlockHeader() -{ - // not entirely illogical values - nVersion = 2; - hashPrevBlock = uint256S("0120120120120"); - hashMerkleRoot = uint256S("12b90980918230a"); - nTime = 1616233780; - nBits = 1; - nNonce = 6; -} - QTEST_MAIN(TestWallet) diff --git a/testing/wallet/TestWallet.h b/testing/wallet/TestWallet.h index 65387b6..e47bc59 100644 --- a/testing/wallet/TestWallet.h +++ b/testing/wallet/TestWallet.h @@ -29,21 +29,20 @@ struct ECC_State { ~ECC_State() { ECC_Stop(); } }; -struct MockBlockHeader : public BlockHeader -{ - MockBlockHeader(); -}; - class MockWallet; class TestWallet : public QObject { Q_OBJECT +public: + TestWallet(); + private slots: void cleanup(); // called after each testcase. void testRef(); void transactionOrdering(); void addingTransactions(); + void addingTransactions2(); void lockingOutputs(); void testSpam(); void saveTransaction(); @@ -62,6 +61,7 @@ private: std::unique_ptr openWallet(uint32_t encryptionSeed = 0); QString m_dir; const ECC_State m_state; + const uint256 dummyBlockId; }; #endif -- 2.54.0 From 9f201a2627bb103e25e2687c76c24a3fd3ed7006 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Apr 2023 16:24:28 +0200 Subject: [PATCH 0406/1428] Start using logging categories. Instead of dumping everything in zero, use actually distinct categories which allows logViewer to use them better. --- src/CMakeLists.txt | 3 ++ src/Wallet.cpp | 80 +++++++++++++++++++-------------------- src/WalletCoinsModel.cpp | 2 +- src/Wallet_encryption.cpp | 10 ++--- src/Wallet_spending.cpp | 16 ++++---- src/Wallet_support.cpp | 16 ++++---- 6 files changed, 65 insertions(+), 62 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09eb0ab..4a62c0a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,9 @@ option(local_qml "Allow local QML loading" OFF) option(networkLog "Include network-logging client" OFF) option(NetworkLogClient "Include the network based logging in the executables" OFF) +add_definitions(-DLOG_DEFAULT_SECTION=10000) +add_definitions(-DLOG_WALLET=10001) + set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 45b2756..6c2a55e 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -76,7 +76,7 @@ Wallet::Wallet(const boost::filesystem::path &basedir, uint16_t segmentId, uint3 in.close(); } } catch (const std::exception &e) { - logInfo() << "Failed to read a wallet-name file" << e; + logInfo(LOG_WALLET) << "Failed to read a wallet-name file" << e; } loadSecrets(); @@ -114,7 +114,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co int inputIndex = -1; int outputIndex = -1; Output output; - logDebug() << "new tx." << wtx.txid; + logDebug(LOG_WALLET) << "new tx." << wtx.txid; Tx::Iterator iter(tx); while (iter.next() != Tx::End) { if (iter.tag() == Tx::PrevTxHash) { @@ -125,7 +125,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co auto i = m_txidCache.find(prevTxhash); prevTx.setTxIndex((i != m_txidCache.end()) ? i->second : 0); if (i != m_txidCache.end()) - logDebug() << " Input:" << inputIndex << "prevTx:" << prevTxhash + logDebug(LOG_WALLET) << " Input:" << inputIndex << "prevTx:" << prevTxhash << Log::Hex << i->second << prevTx.encoded(); } } else if (iter.tag() == Tx::PrevTxIndex) { @@ -135,7 +135,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co auto utxo = m_unspentOutputs.find(prevTx.encoded()); if (utxo != m_unspentOutputs.end()) { // input is spending one of our UTXOs - logDebug() << " -> spent UTXO"; + logDebug(LOG_WALLET) << " -> spent UTXO"; wtx.inputToWTX.insert(std::make_pair(inputIndex, prevTx.encoded())); if (notifier) notifier->spent += utxo->second; @@ -170,7 +170,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co else if (iter.tag() == Tx::OutputScript) { output.walletSecretId = findSecretFor(iter.byteData()); if (output.walletSecretId > 0) { - logDebug() << " output"<< outputIndex << "pays to wallet id" << output.walletSecretId; + logDebug(LOG_WALLET) << " output"<< outputIndex << "pays to wallet id" << output.walletSecretId; wtx.outputs.insert(std::make_pair(outputIndex, output)); if (notifier) notifier->deposited += output.value; @@ -185,7 +185,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co && bytes[3] == 0x55 // 'U' && bytes[4] == 0x5a // 'Z' && bytes[5] == 0) { - logDebug() << "Transaction" << txid << "is a Cash_Fusion transaction."; + logDebug(LOG_WALLET) << "Transaction" << txid << "is a Cash_Fusion transaction."; wtx.isCashFusionTx = true; } } @@ -256,13 +256,13 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) --mainChain; secret.hdDerivationIndex = ++m_hdData->lastMainKey; m_hdData->derivationPath[count - 2] = 0; - logDebug() << "Creating new private key. Derivation: 0," << secret.hdDerivationIndex; + logDebug(LOG_WALLET) << "Creating new private key. Derivation: 0," << secret.hdDerivationIndex; } else { assert(changeChain > 0); --changeChain; secret.fromChangeChain = true; secret.hdDerivationIndex = ++m_hdData->lastChangeKey; - logDebug() << "Creating new private key for change chain:" << secret.hdDerivationIndex; + logDebug(LOG_WALLET) << "Creating new private key for change chain:" << secret.hdDerivationIndex; m_hdData->derivationPath[count - 2] = 1; } m_hdData->derivationPath[count - 1] = secret.hdDerivationIndex; @@ -349,7 +349,7 @@ void Wallet::newTransaction(const Tx &tx) uint64_t key = m_nextWalletTransactionId; key <<= 16; key += i->first; - logDebug() << " inserting output"<< i->first << Log::Hex << "TxIndex:" << i->second.walletSecretId << "outRef:" << key; + logDebug(LOG_WALLET) << " inserting output"<< i->first << Log::Hex << "TxIndex:" << i->second.walletSecretId << "outRef:" << key; m_unspentOutputs.insert(std::make_pair(key, i->second.value)); const int privKeyId = i->second.walletSecretId; @@ -366,7 +366,7 @@ void Wallet::newTransaction(const Tx &tx) m_walletTransactions.insert(std::make_pair(m_nextWalletTransactionId++, wtx)); m_walletChanged = true; - logCritical() << "Wallet" << m_segment->segmentId() << "claims" << tx.createHash() << "[unconfirmed]"; + logCritical(LOG_WALLET) << "Wallet" << m_segment->segmentId() << "claims" << tx.createHash() << "[unconfirmed]"; } // mutex scope saveTransaction(tx); recalculateBalance(); @@ -461,7 +461,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { auto key = OutputRef(m_nextWalletTransactionId, i->first).encoded(); if (!wasUnconfirmed) { // unconfirmed transactions already had their outputs added - logDebug() << " inserting output"<< i->first << Log::Hex << i->second.walletSecretId << key; + logDebug(LOG_WALLET) << " inserting output"<< i->first << Log::Hex << i->second.walletSecretId << key; m_unspentOutputs.insert(std::make_pair(key, i->second.value)); if (!m_singleAddressWallet && i->second.value == 547) { // special case so-called 'spam' outputs and instantly lock it for privacy reasons @@ -512,7 +512,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: } m_walletChanged = true; - logCritical() << "Wallet" << m_segment->segmentId() << "claims" << tx.createHash() << "@" << blockHeight; + logCritical(LOG_WALLET) << "Wallet" << m_segment->segmentId() << "claims" << tx.createHash() << "@" << blockHeight; if (wasUnconfirmed) emit transactionConfirmed(walletTransactionId); if (notification.blockHeight > 0) { @@ -528,7 +528,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: for (auto ejectedTx : ejectedTransactions) { auto tx = m_walletTransactions.find(ejectedTx); Q_ASSERT(tx != m_walletTransactions.end()); - logDebug() << "Confirmed transaction(s) in block" << blockHeight << + logDebug(LOG_WALLET) << "Confirmed transaction(s) in block" << blockHeight << "made invalid transaction:" << ejectedTx << tx->second.txid; auto &wtx = tx->second; wtx.minedBlockHeight = WalletPriv::Rejected; @@ -601,7 +601,7 @@ void Wallet::saveTransaction(const Tx &tx) txSaver.open((localdir + filename).toStdString()); txSaver.write(data.begin(), data.size()); } catch (const std::exception &e) { - logFatal() << "Could not store transaction" << e.what(); + logFatal(LOG_WALLET) << "Could not store transaction" << e.what(); throw; } } @@ -672,7 +672,7 @@ void Wallet::setTransactionComment(const Tx &transaction, const QString &comment } } else { - logCritical() << "Comment added to not known transaction"; + logCritical(LOG_WALLET) << "Comment added to not known transaction"; } } @@ -762,7 +762,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons { assert(m_hdData.get() == nullptr); if (m_hdData.get()) { - logFatal() << "Refusing to replace HDMasterkey with new one"; + logFatal(LOG_WALLET) << "Refusing to replace HDMasterkey with new one"; return; } // we only convert here in order to be 100% certain that the input from the user is @@ -953,7 +953,7 @@ void Wallet::rebuildBloom() // yet at the tip of the headerchain, which makes sending out a bloom filter irrelevant. for (const auto &i : m_walletSecrets) { if (i.second.initialHeight >= 10000000) { - logDebug() << " not building bloom, still have date based private keys"; + logDebug(LOG_WALLET) << " not building bloom, still have date based private keys"; return; } } @@ -979,7 +979,7 @@ void Wallet::rebuildBloom() } } } - logDebug() << "Rebuilding bloom filter. UTXO-size:" << secretsWithBalance.size(); + logDebug(LOG_WALLET) << "Rebuilding bloom filter. UTXO-size:" << secretsWithBalance.size(); for (auto &i : m_walletSecrets) { const auto &secret = i.second; assert(secret.initialHeight < 10000000); // see similar check at start of this method @@ -1051,7 +1051,7 @@ void Wallet::broadcastTxFinished(int txIndex, bool success) if (!success) { auto wtx = m_walletTransactions.find(txIndex); if (wtx != m_walletTransactions.end()) { - logCritical() << "Marking transaction invalid"; + logCritical(LOG_WALLET) << "Marking transaction invalid"; auto &tx = wtx->second; if (tx.minedBlockHeight == WalletPriv::Unconfirmed) { // a transaction that has been added before, but now marked @@ -1077,7 +1077,7 @@ void Wallet::broadcastTxFinished(int txIndex, bool success) } else { assert(false); // Can't imagine the usecase, so if this hits in a debug build lets fail-fast - logWarning() << "Transaction marked rejected that had blockHeight:" << tx.minedBlockHeight; + logWarning(LOG_WALLET) << "Transaction marked rejected that had blockHeight:" << tx.minedBlockHeight; } tx.minedBlockHeight = WalletPriv::Rejected; } @@ -1337,12 +1337,12 @@ void Wallet::broadcastUnconfirmed() if (tx.data().size() > 64) { auto bc = std::make_shared(this, iter->first, tx); bc->moveToThread(thread()); - logDebug() << " broadcasting transaction" << tx.createHash() << tx.size(); + logDebug(LOG_WALLET) << " broadcasting transaction" << tx.createHash() << tx.size(); m_broadcastingTransactions.append(bc); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(bc); } else { - logCritical() << "Unconfirmed transaction could not be found on disk!"; + logCritical(LOG_WALLET) << "Unconfirmed transaction could not be found on disk!"; } } } @@ -1409,7 +1409,7 @@ bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) } return true; } - logFatal() << "ERROR. Wallet: added string is not a private key"; + logFatal(LOG_WALLET) << "ERROR. Wallet: added string is not a private key"; return false; } @@ -1451,7 +1451,7 @@ void Wallet::loadSecrets() Streaming::BufferPool pool(fileData.size()); int newSize = crypto.decrypt(fileData.begin(), fileData.size(), pool.data()); if (newSize == 0) { - logCritical() << "Reading (encrypted) secrets file failed"; + logCritical(LOG_WALLET) << "Reading (encrypted) secrets file failed"; return; } fileData = pool.commit(newSize); @@ -1477,7 +1477,7 @@ void Wallet::loadSecrets() memcpy(c.hash.data(), secret.address.begin(), 20); c.type = CashAddress::PUBKEY_TYPE; auto ad = CashAddress::encodeCashAddr("bitcoincash", c); - logCritical() << "Loaded" << index << ad; + logCritical(LOG_WALLET) << "Loaded" << index << ad; #endif } secret = WalletSecret(); @@ -1558,7 +1558,7 @@ void Wallet::loadSecrets() assert(m_hdData.get() == nullptr); if ((xpub.empty() && mnemonic.isEmpty()) != derivationPath.empty()) - logFatal() << "Found incomplete data for HD wallet"; + logFatal(LOG_WALLET) << "Found incomplete data for HD wallet"; else if (!mnemonic.isEmpty()) m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonic, derivationPath, mnemonicPwd)); else if (!xpub.empty()) @@ -1663,7 +1663,7 @@ void Wallet::saveSecrets() outFile.close(); boost::filesystem::rename(m_basedir / "secrets.dat~", m_basedir / "secrets.dat"); } catch (const std::exception &e) { - logFatal() << "Failed to save the database. Reason:" << e.what(); + logFatal(LOG_WALLET) << "Failed to save the database. Reason:" << e.what(); } m_secretsChanged = false; } @@ -1697,7 +1697,7 @@ void Wallet::loadWallet() pool.reserve(dataSize); dataSize = crypto.decrypt(data.begin(), data.size(), pool.data()); if (dataSize == 0) { - logCritical() << "Reading (encrypted) wallet.dat file failed"; + logCritical(LOG_WALLET) << "Reading (encrypted) wallet.dat file failed"; return; } } @@ -1727,16 +1727,16 @@ void Wallet::loadWallet() newTx.insert(index); #ifdef DEBUGUTXO - logFatal() << "Wallet has tx: " << wtx.txid << "@" << wtx.minedBlockHeight; + logFatal(LOG_WALLET) << "Wallet has tx: " << wtx.txid << "@" << wtx.minedBlockHeight; for (auto pair : wtx.outputs) { - logFatal() << " ++ " << pair.first << pair.second.value << "sat"; + logFatal(LOG_WALLET) << " ++ " << pair.first << pair.second.value << "sat"; } for (auto pair : wtx.inputToWTX) { OutputRef ref(pair.second); - logFatal() << " -- " << pair.first << ref.txIndex() << ref.outputIndex(); + logFatal(LOG_WALLET) << " -- " << pair.first << ref.txIndex() << ref.outputIndex(); auto w = m_walletTransactions.find(ref.txIndex()); if (w != m_walletTransactions.end()) - logFatal() << " " << w->second.txid; + logFatal(LOG_WALLET) << " " << w->second.txid; } #endif @@ -1853,7 +1853,7 @@ void Wallet::loadWallet() if (i != m_walletSecrets.end()) { i->second.reserved = true; } else { - logFatal() << "PaymentRequest refers to non-existing wallet-secret!"; + logFatal(LOG_WALLET) << "PaymentRequest refers to non-existing wallet-secret!"; } } else if (parser.tag() == WalletPriv::PaymentRequestMessage) { @@ -1917,11 +1917,11 @@ void Wallet::loadWallet() auto out = utxo->second.outputs.find(ref.outputIndex()); assert(out != utxo->second.outputs.end()); assert(out->second.value == output.second); - logFatal() << "Unspent: " << utxo->second.txid << ref.outputIndex() << "\t->" << out->second.value << "sats"; + logFatal(LOG_WALLET) << "Unspent: " << utxo->second.txid << ref.outputIndex() << "\t->" << out->second.value << "sats"; auto locked = m_lockedOutputs.find(ref.encoded()); if (locked != m_lockedOutputs.end()) { - logFatal() << " \\= Locked UTXO" << locked->second; + logFatal(LOG_WALLET) << " \\= Locked UTXO" << locked->second; } } #endif @@ -1963,7 +1963,7 @@ void Wallet::loadWallet() for (auto i = m_lockedOutputs.begin(); i != m_lockedOutputs.end();) { auto utxoIter = m_unspentOutputs.find(i->first); if (utxoIter == m_unspentOutputs.end()) { - logCritical() << "Found faulty 'locked' output-ref, dropping"; + logCritical(LOG_WALLET) << "Found faulty 'locked' output-ref, dropping"; i = m_lockedOutputs.erase(i); // this should never happen, cleanup m_walletChanged = true; } else { @@ -1992,7 +1992,7 @@ void Wallet::saveWallet() boost::filesystem::rename(m_basedir / "name~", m_basedir / "name"); m_walletNameChanged = false; } catch (const std::exception &e) { - logFatal() << "Failed to save the wallet-name. Reason:" << e; + logFatal(LOG_WALLET) << "Failed to save the wallet-name. Reason:" << e; } } if (!m_walletChanged) { @@ -2005,7 +2005,7 @@ void Wallet::saveWallet() } if (m_encryptionLevel == FullyEncrypted && !m_haveEncryptionKey) { - logFatal() << "Wallet" << m_name << (m_walletChanged ? "was changed" : "has changed payment requests") + logFatal(LOG_WALLET) << "Wallet" << m_name << (m_walletChanged ? "was changed" : "has changed payment requests") << "We can't save it since its fully encrypted and not open right now"; throw std::runtime_error("Can not save a pin to open wallet that is closed."); } @@ -2096,14 +2096,14 @@ void Wallet::saveWallet() int size = crypto.encrypt(data.begin(), data.size(), pool.data()); data = pool.commit(size); } - + std::ofstream outFile((m_basedir / "wallet.dat~").string()); outFile.write(data.begin(), data.size()); outFile.flush(); outFile.close(); boost::filesystem::rename(m_basedir / "wallet.dat~", m_basedir / "wallet.dat"); } catch (const std::exception &e) { - logFatal() << "Failed to save the wallet.dat. Reason:" << e; + logFatal(LOG_WALLET) << "Failed to save the wallet.dat. Reason:" << e; } m_walletChanged = false; } diff --git a/src/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp index dba606c..7300c18 100644 --- a/src/WalletCoinsModel.cpp +++ b/src/WalletCoinsModel.cpp @@ -65,7 +65,7 @@ QVariant WalletCoinsModel::data(const QModelIndex &index, int role) const auto iter = m_rowsToOutputRefs.find(index.row()); if (iter == m_rowsToOutputRefs.end()) { - logDebug() << "This looks wrong"; + logDebug(LOG_WALLET) << "This looks wrong"; return QVariant(); } Wallet::OutputRef outRef(iter->second); diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index e59657e..25832e2 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -64,7 +64,7 @@ bool Wallet::parsePassword(const QString &password) Streaming::BufferPool pool(data.size()); int newSize = crypto.decrypt(data.begin(), data.size(), pool.data()); if (newSize == 0) { - logCritical() << "Reading (encrypted) secrets file failed"; + logCritical(LOG_WALLET) << "Reading (encrypted) secrets file failed"; return false; } data = pool.commit(newSize); @@ -109,7 +109,7 @@ void Wallet::setEncryption(EncryptionLevel level, const QString &password) throw std::runtime_error("Upgrading encryption requires you to decrypt first"); } else if (!parsePassword(password)) { - logCritical() << "Decrypt failed, bad password"; + logCritical(LOG_WALLET) << "Decrypt failed, bad password"; return; } assert(m_haveEncryptionKey); @@ -133,7 +133,7 @@ void Wallet::setEncryption(EncryptionLevel level, const QString &password) QFile reader(base + path); reader.open(QIODevice::ReadOnly); if (!reader.isOpen()) { - logDebug() << "Missing transaction file"; + logDebug(LOG_WALLET) << "Missing transaction file"; continue; } auto &pool = Streaming::pool(reader.size()); @@ -161,7 +161,7 @@ void Wallet::setEncryption(EncryptionLevel level, const QString &password) QFile writer(newPath); writer.open(QIODevice::WriteOnly); if (!writer.isOpen()) { - logCritical() << "Could not write to" << newPath; + logCritical(LOG_WALLET) << "Could not write to" << newPath; continue; } writer.write(newFile.begin(), newFile.size()); @@ -226,7 +226,7 @@ bool Wallet::decrypt(const QString &password) // set, and check password correctness if (!parsePassword(password)) { - logCritical() << "Decrypt() failed, bad password"; + logCritical(LOG_WALLET) << "Decrypt() failed, bad password"; return false; } assert(m_haveEncryptionKey); diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index ec0b289..e8f07c9 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -78,7 +78,7 @@ int Wallet::scoreForSolution(const OutputSet &set, int64_t change, size_t unspen ++foundFusionOutputs; } if (foundFusionOutputs) - logUtxo() << foundFusionOutputs << "fusion, out of" << set.outputs.size() << "outputs"; + logUtxo(10002) << foundFusionOutputs << "fusion, out of" << set.outputs.size() << "outputs"; if (foundFusionOutputs == 1 && set.outputs.size() == 1) score = 1001; // perfection @@ -191,7 +191,7 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz int scoreAdjust = scoreForSolution(bestSet, bestSet.totalSats - bestSet.fee - output, unspentOutputs.size()); - logUtxo() << "Initial set, size:" << bestSet.outputs.size() << "paying" << bestSet.totalSats << "+" + logUtxo(10002) << "Initial set, size:" << bestSet.outputs.size() << "paying" << bestSet.totalSats << "+" << bestSet.fee << "fee, gives change of:" << bestSet.totalSats - bestSet.fee - output << "got score" << bestScore << "+" << scoreAdjust; bestScore += scoreAdjust; @@ -214,17 +214,17 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz if (current.totalSats - current.fee >= output) { scoreAdjust = scoreForSolution(current, current.totalSats - current.fee - output, unspentOutputs.size()); - logUtxo() << "Size-based set, size:" << current.outputs.size() << "paying" << current.totalSats << "+" + logUtxo(10002) << "Size-based set, size:" << current.outputs.size() << "paying" << current.totalSats << "+" << current.fee << "fee, gives change of:" << current.totalSats - current.fee - output << "got score" << score << "+" << scoreAdjust; score += scoreAdjust; if (current.outputs.size() > MAX_INPUTS) { - logUtxo() << " + Skipping due to too-many-inputs"; + logUtxo(10002) << " + Skipping due to too-many-inputs"; } // compare with the cost of oldest to newest. else if (score > bestScore) { - logUtxo() << " + New BEST"; + logUtxo(10002) << " + New BEST"; bestScore = score; bestSet = current; } @@ -251,18 +251,18 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz scoreAdjust = scoreForSolution(current, (current.totalSats - current.fee) - output, unspentOutputs.size()); - logUtxo() << "Random set, size:" << current.outputs.size() << "paying" << current.totalSats << "+" + logUtxo(10002) << "Random set, size:" << current.outputs.size() << "paying" << current.totalSats << "+" << current.fee << "fee, gives change of:" << current.totalSats - current.fee - output << "got score" << score << "+" << scoreAdjust; score += scoreAdjust; Q_ASSERT(current.totalSats - current.fee >= output); if (current.outputs.size() > MAX_INPUTS) { - logUtxo() << " + Skipping due to too-many-inputs"; + logUtxo(10002) << " + Skipping due to too-many-inputs"; } else if (score > bestScore // or if the score is the same, prefer a smaller change || (score == bestScore && current.totalSats < bestSet.totalSats)) { - logUtxo() << " + New BEST"; + logUtxo(10002) << " + New BEST"; bestScore = score; bestSet = current; } diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 59e3b0e..3b2653a 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -139,7 +139,7 @@ WalletInfoObject::WalletInfoObject(Wallet *wallet, int txIndex, const Tx &tx) void WalletInfoObject::txRejected(RejectReason reason, const std::string &message) { // reason is hinted using BroadcastTxData::RejectReason - logCritical() << "Transaction rejected" << reason << message; + logCritical(LOG_WALLET) << "Transaction rejected" << reason << message; ++m_rejectedPeerCount; } @@ -308,17 +308,17 @@ void Wallet::populateSigType() { const auto &txs = m_walletTransactions; // short alias for readability - logCritical().nospace() << "Upgrading wallet '" << m_name << "', Finding signature types from seen transactions"; + logCritical(LOG_WALLET).nospace() << "Upgrading wallet '" << m_name << "', Finding signature types from seen transactions"; // iterate though each private key for (auto s = m_walletSecrets.begin(); s != m_walletSecrets.end(); ++s) { auto &secret = s->second; - // logDebug() << "Secret" << s->first << "Hd:" << secret.fromHdWallet << secret.fromChangeChain << "index:" << secret.hdDerivationIndex; + // logDebug(LOG_WALLET) << "Secret" << s->first << "Hd:" << secret.fromHdWallet << secret.fromChangeChain << "index:" << secret.hdDerivationIndex; // iterate through transactions and outputs to find one that deposited funds there. for (auto t1 = txs.cbegin(); secret.signatureType == NotUsedYet && t1 != txs.cend(); ++t1) { const auto &tx1 = t1->second; for (auto o = tx1.outputs.cbegin(); secret.signatureType == NotUsedYet && o != tx1.outputs.cend(); ++o) { if (o->second.walletSecretId == s->first) { - // logDebug() << " Found an out for secret" << t1->first << o->first; + // logDebug(LOG_WALLET) << " Found an out for secret" << t1->first << o->first; const auto ref = OutputRef(t1->first, o->first).encoded(); // check UTXO, if still there, then its unspent. // Unspent means no signature, so find another output to check. @@ -331,12 +331,12 @@ void Wallet::populateSigType() for (auto i = tx2.inputToWTX.cbegin(); i != tx2.inputToWTX.cend(); ++i) { if (i->second == ref) { // found one, now fetch the input script and check the type. - // logDebug() << "Found tx2 which spends output. Tx2:" << t2->first << i->first; + // logDebug(LOG_WALLET) << "Found tx2 which spends output. Tx2:" << t2->first << i->first; Tx tx = loadTransaction(tx2.txid, Streaming::pool(0)); Tx::Iterator txIter(tx); for (int x = i->first; x >= 0; --x) { if (txIter.next(Tx::TxInScript) != Tx::TxInScript) { - logCritical() << "Internal error; tx has too little inputs" << tx2.txid << i->first; + logCritical(LOG_WALLET) << "Internal error; tx has too little inputs" << tx2.txid << i->first; return; } } @@ -346,7 +346,7 @@ void Wallet::populateSigType() opcodetype type; script.GetOp(scriptIter, type); secret.signatureType = type == 65 ? SignedAsSchnorr : SignedAsEcdsa; - logInfo() << "Secret key" << s->first << "detected signature type from tx-out:" + logInfo(LOG_WALLET) << "Secret key" << s->first << "detected signature type from tx-out:" << tx1.txid << o->first << "signed by tx-input:" << tx2.txid << i->first << "SigType:" << (type == 65 ? "SignedAsSchnorr" : "SignedAsEcdsa"); @@ -359,7 +359,7 @@ void Wallet::populateSigType() } } } - logCritical() << "Wallet upgrade finished"; + logCritical(LOG_WALLET) << "Wallet upgrade finished"; } -- 2.54.0 From e20b476357c0e73e7c60c25ddcc49fc2c290b265 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Apr 2023 21:55:42 +0200 Subject: [PATCH 0407/1428] Update fusion comment on recheck When we notice we used all HD keys on finding a transaction, we check it again after creating a bunch of extra keys. Now we also use the re-created comment which is where fusion transactions place the amount of outputs matched. This is a purely visual change. --- src/Wallet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 6c2a55e..a138059 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -333,6 +333,7 @@ void Wallet::newTransaction(const Tx &tx) // so we make sure we matched all we can auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); wtx.outputs = newWtx.outputs; + wtx.userComment = newWtx.userComment; createdNewKeys = true; } @@ -429,6 +430,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: // so we make sure we matched all we can auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); wtx.outputs = newWtx.outputs; + wtx.userComment = newWtx.userComment; } wtx.minedBlock = blockId; wtx.minedBlockHeight = blockHeight; -- 2.54.0 From 515b09e721a9bf234628056fb2c8b6acf5fe9509 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Apr 2023 22:33:28 +0200 Subject: [PATCH 0408/1428] Add some logging for the new code. --- src/Wallet_support.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 3b2653a..682c504 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -382,8 +382,10 @@ Wallet::InsertBeforeData::~InsertBeforeData() std::deque list; for (auto wtx = transactions.rbegin(); wtx != transactions.rend(); ++wtx) { if (wtx->minedBlockHeight != blockHeight) { - if (!list.empty()) + if (!list.empty()) { + logDebug(LOG_WALLET) << " +- combining" << list.size() << "tx. Height:" << blockHeight << "blockId:" << blockId; parent->newTransactions(blockId, blockHeight, list); + } blockId = wtx->minedBlock; blockHeight = wtx->minedBlockHeight; list = std::deque(); @@ -393,9 +395,12 @@ Wallet::InsertBeforeData::~InsertBeforeData() auto tx = parent->loadTransaction(wtx->txid, Streaming::pool(0)); assert(tx.isValid()); list.push_back(tx); + logDebug(LOG_WALLET) << "Re-applying tx:" << wtx->txid; } - if (!list.empty()) + if (!list.empty()) { + logDebug(LOG_WALLET) << " +- combining" << list.size() << "tx. Height:" << blockHeight << "blockId:" << blockId; parent->newTransactions(blockId, blockHeight, list); + } parent->m_inInsertBeforeCallback = true; } @@ -462,6 +467,7 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) auto txidIter = m_txidCache.find(wtx.txid); assert(txidIter != m_txidCache.end()); m_txidCache.erase(txidIter); + logDebug(LOG_WALLET) << "Rolling back tx:" << wtx.txid << wtx.minedBlockHeight; } return ibd; } -- 2.54.0 From 88f1d814438af80782d62ce3281221f1835834fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Apr 2023 14:49:11 +0200 Subject: [PATCH 0409/1428] Make sure we send the bloom filter a lot less often One notable behavior change is that we increase the 'change' gap considerably for wallets that are known to have 'cashfusion' transactions as those use a lot of change addresses. This chang also increases the normal gap to avoid people losing history when importing from another wallet. --- src/FloweePay.cpp | 2 -- src/Wallet.cpp | 56 ++++++++++++++++++++++++++++++++++------------- src/Wallet.h | 2 ++ 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8bdc584..7a157ec 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -860,8 +860,6 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; wallet->createHDMasterKey(mnemonic.trimmed().remove('\n'), password.trimmed().remove('\n'), derivationPath, startHeight); - wallet->segment()->blockSynched(startHeight); - wallet->segment()->blockSynched(startHeight); // yes, twice emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. diff --git a/src/Wallet.cpp b/src/Wallet.cpp index a138059..927625f 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -53,6 +53,13 @@ Wallet *Wallet::createWallet(const boost::filesystem::path &basedir, uint16_t se return wallet; } +// the 'gap' used in bloom filters of net yet used addresses to send. +// this is a bit larger than others may use because we use a filter for a series +// of blocks that can hold a large number of our transactions. +constexpr int filter_unusedToInclude = 40; +constexpr int filter_hdUnusedToInclude = 30; +constexpr int filter_changeUnusedToInclude = 50; + Wallet::Wallet() : m_walletChanged(true), m_walletVersion(2) @@ -212,10 +219,13 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda if (m_hdData.get() == nullptr) return false; - static constexpr int ExtraAddresses = 50; + static constexpr int ExtraAddresses = 100; int needChangeAddresses = 0; int needMainAddresses = 0; + + const int changeGap = filter_changeUnusedToInclude + (m_walletStoresCashFusions ? 50 : 0); + for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { auto privKey = m_walletSecrets.find(i->second.walletSecretId); assert(privKey != m_walletSecrets.end()); // invalid wtx @@ -223,14 +233,20 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda if (ws.fromHdWallet) { if (ws.fromChangeChain) { assert(m_hdData->lastChangeKey >= ws.hdDerivationIndex); - if (m_hdData->lastChangeKey - ws.hdDerivationIndex < 15) - needChangeAddresses = ExtraAddresses; - updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 10; + if (m_hdData->lastChangeKey - ws.hdDerivationIndex < changeGap) { + needChangeAddresses = ExtraAddresses + + (m_walletStoresCashFusions ? 75 : 0); + } + // notice that here we trigger on there only being 15 addresses between what we used and + // what was sent, while what we sent is (as set in filter_changeUnusedToInclude) much + // heigher. This is intentional so we update the filter only when we used a larger list + // of addresses instead of refreshing the filter after every single new key being used. + updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 15; // the gap } else { assert(m_hdData->lastMainKey >= ws.hdDerivationIndex); - if (m_hdData->lastMainKey - ws.hdDerivationIndex < 15) + if (m_hdData->lastMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude + 10) needMainAddresses = ExtraAddresses; - updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < 5; + updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < 15; // the gap } } } @@ -246,6 +262,7 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) { // mutex already locked + assert(mainChain + changeChain > 0); while (changeChain + mainChain > 0) { WalletSecret secret; secret.initialHeight = startHeight; @@ -282,7 +299,7 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) } rebuildBloom(); - saveSecrets(); // no-op if secrets are unchanged + saveSecrets(); } void Wallet::updateSignatureTypes(const std::map &txData) @@ -415,6 +432,8 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: needNewBloom = true; continue; } + if (wtx.isCashFusionTx) // improve behavior of bloom filters + m_walletStoresCashFusions = true; } else { // we already seen it before. @@ -487,7 +506,6 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: // update this to the actual initial usage as we find a transaction matching it ws->second.initialHeight = blockHeight; m_secretsChanged = true; - needNewBloom = true; // make sure we let the remote know about our 'gap' addresses } // check the payment requests @@ -790,9 +808,10 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons // So to reuse the derivationPath we store the last-used-index elsewhere. m_hdData->lastMainKey = -1; m_hdData->lastChangeKey = -1; + m_segment->blockSynched(startHeight); + m_segment->blockSynched(startHeight); // yes, twice QMutexLocker locker(&m_lock); - deriveHDKeys(50, 50, startHeight); - saveSecrets(); + deriveHDKeys(200, 200, startHeight); } int Wallet::lastTransactionTimestamp() const @@ -944,9 +963,14 @@ int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript) const void Wallet::rebuildBloom() { - int unusedToInclude = 20; - int hdUnusedToInclude = 10; - int changeUnusedToInclude = 30; + int unusedToInclude = filter_unusedToInclude; + int hdUnusedToInclude = filter_hdUnusedToInclude; + int changeUnusedToInclude = filter_changeUnusedToInclude; + if (m_walletStoresCashFusions) { + // a wallet that uses fusions eats through change addresses like + // cheap candy, lets avoid the need to upload it too often + changeUnusedToInclude += 50; + } // on wallet creation we may not have yet synced the entire header chain, // in that case the secrets are given a height that is in seconds, in order @@ -1218,7 +1242,7 @@ int Wallet::reserveUnusedAddress(KeyId &keyId, PrivKeyType pkt) // no unused addresses, lets make some. if (m_hdData.get()) { - deriveHDKeys(50, 0); + deriveHDKeys(100, 0); return reserveUnusedAddress(keyId); } @@ -1228,7 +1252,7 @@ int Wallet::reserveUnusedAddress(KeyId &keyId, PrivKeyType pkt) return 0; int answer = m_nextWalletSecretId; - for (int i = 0; i < 50; ++i) { + for (int i = 0; i < 100; ++i) { WalletSecret secret; secret.privKey.makeNewKey(); const PublicKey pubkey = secret.privKey.getPubKey(); @@ -1766,6 +1790,8 @@ void Wallet::loadWallet() } else if (parser.tag() == WalletPriv::TxIsCashFusion) { wtx.isCashFusionTx = parser.boolData(); + if (wtx.isCashFusionTx) + m_walletStoresCashFusions = true; } else if (parser.tag() == WalletPriv::InputIndex) { inputIndex = parser.intData(); diff --git a/src/Wallet.h b/src/Wallet.h index 308d067..ce6b98e 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -565,6 +565,8 @@ private: friend class WalletSecretsModel; friend class WalletCoinsModel; + // auto-detected, causes us to send bigger gaps for bloom filters. + bool m_walletStoresCashFusions = false; // user settings bool m_singleAddressWallet = false; bool m_userOwnedWallet = true; -- 2.54.0 From adc7edb250ec00e9d5aded080d432376b9ec0e31 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Apr 2023 19:41:40 +0200 Subject: [PATCH 0410/1428] Make the wallet emit tx removals Now the wallet handles inserts-in-place by making removing and then re-adding of transactions, the 'txIndex' is no longer guarenteed to live forever. So we now tell the world, and specifically the history model, about the removal of txIIndexes. --- src/PriceDataProvider.cpp | 3 +++ src/Wallet.cpp | 10 +++++++++- src/Wallet.h | 2 ++ src/WalletHistoryModel.cpp | 15 +++++++++++++-- src/WalletHistoryModel.h | 1 + src/Wallet_support.cpp | 1 + 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index b4e6583..785ac17 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -24,6 +24,7 @@ #include #include #include +#include // CoinGecko static const char *CoinGeckoURL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin-cash&vs_currencies=%1"; @@ -101,6 +102,8 @@ QString PriceDataProvider::formattedPrice(double amountSats, int price) const int PriceDataProvider::priceFor(double amountSats, int price) const { + if (std::isnan(amountSats)) + return 0; qint64 fiatValue = amountSats * price; fiatValue = (fiatValue + (amountSats > 0 ? 50000000: -50000000)) / qint64(100000000); assert(fiatValue < INT_MAX); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 927625f..41d3b53 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -402,6 +402,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: auto transactions = WalletPriv::sortTransactions(blockTransactions); std::deque transactionsToSave; int firstNewTransaction; + std::set removedTransactionIds; bool needNewBloom = false; { QMutexLocker locker(&m_lock); @@ -412,6 +413,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: * have AFTER this and re-apply them later. */ const auto insertBeforeData = removeTransactionsAfter(blockHeight - 1); // remove them for this block too! + removedTransactionIds = insertBeforeData.oldTransactionIds; firstNewTransaction = m_nextWalletTransactionId; for (auto &tx: transactions) { @@ -577,6 +579,12 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: } } // mutex scope + // the 'insert before' concept simply deletes and re-inserts, let + // users know old transaction IDs no longer exist. + for (auto id : removedTransactionIds) { + emit transactionRemoved(id); + } + if (!transactionsToSave.empty()) { setUserOwnedWallet(true); emit utxosChanged(); @@ -916,7 +924,7 @@ void Wallet::addPaymentRequest(PaymentRequest *pr) void Wallet::removePaymentRequest(PaymentRequest *pr) { QMutexLocker locker(&m_lock); - for (auto prData = m_paymentRequests.begin(); prData != m_paymentRequests.end(); ++prData) { + for (auto prData = m_paymentRequests.cbegin(); prData != m_paymentRequests.cend(); ++prData) { if (prData->pr == pr) { if (prData->saved) m_walletChanged = true; diff --git a/src/Wallet.h b/src/Wallet.h index ce6b98e..9ee9b60 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -380,6 +380,7 @@ signals: void userOwnedChanged(); void transactionChanged(int txIndex); void transactionConfirmed(int txIndex); + void transactionRemoved(int txIndex); void encryptionChanged(); // \internal @@ -509,6 +510,7 @@ private: // on destructor we re-insert the transactions. ~InsertBeforeData(); std::vector transactions; + std::set oldTransactionIds; Wallet *parent; }; bool m_inInsertBeforeCallback = false; diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index ac1bd38..7e13285 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -102,6 +102,7 @@ WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) connect(wallet, SIGNAL(appendedTransactions(int,int)), SLOT(appendTransactions(int,int)), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionConfirmed(int)), SLOT(transactionChanged(int)), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionChanged(int)), SLOT(transactionChanged(int)), Qt::QueuedConnection); + connect(wallet, SIGNAL(transactionRemoved(int)), SLOT(removeTransaction(int)), Qt::QueuedConnection); } int WalletHistoryModel::rowCount(const QModelIndex &parent) const @@ -124,8 +125,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const const int txIndex = txIndexFromRow(index.row()); // logDebug() << " getting" << index.row() << "=>" << txIndex; auto itemIter = m_wallet->m_walletTransactions.find(txIndex); - assert(itemIter != m_wallet->m_walletTransactions.end()); - if (itemIter == m_wallet->m_walletTransactions.end()) + if (itemIter == m_wallet->m_walletTransactions.end()) // the wallet changed, we're probably waiting for a queued connection return QVariant(); const auto &item = itemIter->second; switch (role) { @@ -300,6 +300,17 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) } } +void WalletHistoryModel::removeTransaction(int txIndex) +{ + const int index = m_rowsProxy.indexOf(txIndex); + int row = m_rowsProxy.size() - index - 1; + m_rowsProxy.removeAt(index); + if (m_rowsSilentlyInserted > 0) + row -= m_rowsSilentlyInserted; + beginRemoveRows(QModelIndex(), row, row); + endRemoveRows(); +} + void WalletHistoryModel::transactionChanged(int txIndex) { int row = m_rowsProxy.size() - m_rowsProxy.indexOf(txIndex) - 1; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index a732199..37c989c 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -101,6 +101,7 @@ protected: protected slots: void appendTransactions(int firstNew, int count); + void removeTransaction(int txIndex); void transactionChanged(int txIndex); void createMap(); diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 682c504..3501956 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -462,6 +462,7 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) } } + ibd.oldTransactionIds.insert(i->first); ibd.transactions.push_back(i->second); m_walletTransactions.erase(i); auto txidIter = m_txidCache.find(wtx.txid); -- 2.54.0 From 0970d45cc4dc95249c51004e6bdb082cd469bad6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Apr 2023 12:44:22 +0200 Subject: [PATCH 0411/1428] Fix ordering of transaction history Move the emits of newly found transactions in order to make the (semi-)recursive call of adding transactions be done in the right order. --- src/Wallet.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 41d3b53..3c123d5 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -402,7 +402,6 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: auto transactions = WalletPriv::sortTransactions(blockTransactions); std::deque transactionsToSave; int firstNewTransaction; - std::set removedTransactionIds; bool needNewBloom = false; { QMutexLocker locker(&m_lock); @@ -413,7 +412,6 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: * have AFTER this and re-apply them later. */ const auto insertBeforeData = removeTransactionsAfter(blockHeight - 1); // remove them for this block too! - removedTransactionIds = insertBeforeData.oldTransactionIds; firstNewTransaction = m_nextWalletTransactionId; for (auto &tx: transactions) { @@ -577,18 +575,22 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: } } } + + // the 'insert before' concept simply deletes and re-inserts, let + // users know old transaction IDs no longer exist. + for (auto id : insertBeforeData.oldTransactionIds) { + emit transactionRemoved(id); + } + if (!transactionsToSave.empty()) { + emit utxosChanged(); + emit appendedTransactions(firstNewTransaction, transactionsToSave.size()); + } + } // mutex scope - // the 'insert before' concept simply deletes and re-inserts, let - // users know old transaction IDs no longer exist. - for (auto id : removedTransactionIds) { - emit transactionRemoved(id); - } - + // outside the mutex do the file-IO heavy things like saving our newly found transactions. if (!transactionsToSave.empty()) { setUserOwnedWallet(true); - emit utxosChanged(); - emit appendedTransactions(firstNewTransaction, transactionsToSave.size()); for (const auto &tx : transactionsToSave) { // save the Tx to disk. saveTransaction(tx); } -- 2.54.0 From bb94edee2313753db2230c0477370261d608117f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Apr 2023 12:53:04 +0200 Subject: [PATCH 0412/1428] Only show the title "your wallets" when you have > 1 --- guis/mobile/AccountSelectorPopup.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 5010223..32b3e44 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -47,6 +47,7 @@ QQC2.Popup { Flowee.Label { text: qsTr("Your Wallets") font.bold: true + visible: portfolio.accounts.length > 1 } Repeater { // portfolio holds all our accounts -- 2.54.0 From b5dce3070ad79f491eccc5645f8aca0f44774756 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Apr 2023 14:03:23 +0200 Subject: [PATCH 0413/1428] Make ScrollThumb better suited for mobile. --- guis/Flowee/ScrollThumb.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index 1c6f493..c68e13a 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -82,7 +82,7 @@ QQC2.ScrollBar { Timer { running: thumbRect.open && !thumbRect.moving - interval: 500 + interval: 1200 onTriggered: thumbRect.open = false; } @@ -98,7 +98,7 @@ QQC2.ScrollBar { id: thumbInput // make it easier to grab by having a bigger mouse area than the visial thumb width: thumbRect.width + 20 + root.width - height: thumbRect.height + 20 + height: thumbRect.height + 50 anchors.right: parent.right enabled: thumbRect.moving || thumbRect.open y: { -- 2.54.0 From ab779242aa3a48a30ab7b989dacd44fde6525daf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Apr 2023 14:05:44 +0200 Subject: [PATCH 0414/1428] Add scroll thumb to the history list. This allows us to navigate a longer list much easier. --- guis/mobile/AccountHistory.qml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 8a090b6..c92061b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -61,6 +61,26 @@ ListView { } } + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { + id: thumb + minimumSize: 0.05 + visible: size < 0.9 + preview: Rectangle { + width: label.width + 20 + height: label.height + 20 + radius: 5 + color: palette.light + border.width: 1 + border.color: palette.highlight + Flowee.Label { + id: label + anchors.centerIn: parent + color: palette.dark + text: root.model.dateForItem(thumb.position); + } + } + } + /* The structure here is a bit funny. But we want to have the entire page scroll in order to show all the transactions we can. -- 2.54.0 From 939a22a015f59808a56b90eff7addd3b6370ec3f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Apr 2023 14:19:01 +0200 Subject: [PATCH 0415/1428] Show 'payment to self' better --- guis/mobile/AccountHistory.qml | 11 +++++++---- guis/mobile/TxInfoSmall.qml | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index c92061b..a9aff1e 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -191,13 +191,15 @@ ListView { id: transactionDelegate property var placementInGroup: model.placementInGroup - property double amountBch: model.fundsOut - model.fundsIn + property double amountBch: isMoved ? model.fundsIn + : (model.fundsOut - model.fundsIn) // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. property bool isMoved: { if (model.isCoinbase || model.isCashFusion || model.fundsIn === 0) return false; - return amountBch < 0 && amountBch > -2500 // then the diff is likely just fees. + var amount = model.fundsOut - model.fundsIn + return amount < 0 && amount > -2500 // then the diff is likely just fees. } width: root.width @@ -326,7 +328,7 @@ ListView { baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: visible: !model.isCashFusion - color: amountBch < 0 ? "#00000000" + color: (isMoved || amountBch < 0) ? "#00000000" : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background Flowee.Label { id: amount @@ -343,7 +345,7 @@ ListView { } } Flowee.Label { // plus or minus in front of the price - visible: price.visible + visible: price.visible && !isMoved text: amountBch >= 0 ? "+" : "-" anchors.baseline: price.baseline anchors.right: price.left @@ -357,6 +359,7 @@ ListView { value: amountBch showFiat: false fontPixelSize: amount.font.pixelSize * 0.9 + colorize: isMoved === false } // horizontal separator diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index e454fa0..1a94df6 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -76,6 +76,7 @@ GridLayout { } Flowee.Label { id: paymentTypeLabel + Layout.columnSpan: isMoved ? 2 : 1 text: { if (model.isCoinbase) return qsTr("Miner Reward") + ":"; @@ -83,14 +84,14 @@ GridLayout { return qsTr("Cash Fusion") + ":"; if (model.fundsIn === 0) return qsTr("Received") + ":"; - if (isMoved) // inherited from AccountHistory.qml - return qsTr("Moved") + ":"; + if (isMoved) + return qsTr("Payment to self"); return qsTr("Sent") + ":"; } } Flowee.BitcoinAmountLabel { + visible: isMoved === false Layout.fillWidth: true - visible: paymentTypeLabel.visible value: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) fiatTimestamp: model.date showFiat: false // might not fit -- 2.54.0 From a8725ec4a95efcaee69e7e77ea70b42133889abc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Apr 2023 11:30:26 +0200 Subject: [PATCH 0416/1428] new version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 958b513..27c7cf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.02.1"); + qapp.setApplicationVersion("2023.04.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From d10db500abb0b95fc7a94ba17f82765ae4c3161c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 12:33:42 +0200 Subject: [PATCH 0417/1428] Add detection of state problem If we are lacking transactions this may cause some data inconsistencies for the UI. Possibly causing a nullptr dereference. This helps with early detection. --- src/Wallet_support.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 3501956..c233ba5 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -279,6 +279,18 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) } info->m_inputs[pair.first] = in; } +#ifndef NDEBUG + // we created inputs that should ALL be there. + // if they are not, we have an internal data inconsistency. + if (info->m_createdByUs) { + for (int i = 0; i < info->m_inputs.length(); ++i) { + if (info->m_inputs.at(i) == nullptr) { + logFatal() << "Transaction" << iter->second.txid << "is created by the user, but no input data found for:" << i; + } + } + } +#endif + // same for outputs for (auto o : wtx.outputs) { auto secretIter = m_walletSecrets.find(o.second.walletSecretId); -- 2.54.0 From 35445e8e5c8862b8835b735e2787dbf87ea43d5c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 13:14:49 +0200 Subject: [PATCH 0418/1428] Reduce scope of variable. --- src/Wallet.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 3c123d5..31f2411 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -401,7 +401,6 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: { auto transactions = WalletPriv::sortTransactions(blockTransactions); std::deque transactionsToSave; - int firstNewTransaction; bool needNewBloom = false; { QMutexLocker locker(&m_lock); @@ -413,7 +412,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: */ const auto insertBeforeData = removeTransactionsAfter(blockHeight - 1); // remove them for this block too! - firstNewTransaction = m_nextWalletTransactionId; + int firstNewTransaction = m_nextWalletTransactionId; for (auto &tx: transactions) { const uint256 txid = tx.createHash(); WalletTransaction wtx; -- 2.54.0 From 9a6b9686b958d9a116aa26b6a36751fc63266d37 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 14:27:54 +0200 Subject: [PATCH 0419/1428] UX; move most complicated to bottom. --- guis/desktop/NewAccountImportAccount.qml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 3165c4a..eba1f5d 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -134,17 +134,6 @@ GridLayout { visible: importAccount.isPrivateKey Layout.columnSpan: 2 } - Label { - text: qsTr("Alternate phrase") + ":" - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - } Label { text: qsTr("Start Height") + ":" } @@ -162,5 +151,16 @@ GridLayout { visible: !importAccount.isPrivateKey color: Pay.checkDerivation(text) ? palette.text : "red" } + Label { + text: qsTr("Alternate phrase") + ":" + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + } } } -- 2.54.0 From 2799ccb151f8961a0d98ea2b6a694f2a77568986 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 16:16:31 +0200 Subject: [PATCH 0420/1428] Make model a bit more robust in an async world. Since recently the wallet can delete stuff, we need the model to be more robust in accepting data not being there. The "async" statement refers to the fact that the wallet acts one messages from the network, which may be in any thread. The signals from the wallet get handled by the WalletHistoryModel in the Qt-UI-thread, asynchronous. --- src/WalletHistoryModel.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 7e13285..4bea509 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -250,7 +250,6 @@ QHash WalletHistoryModel::roleNames() const answer[Comment] = "comment"; answer[PlacementInGroup] = "placementInGroup"; answer[GroupId] = "grouping"; - // answer[SavedFiatRate] = "savedFiatRate"; return answer; } @@ -273,9 +272,12 @@ QString WalletHistoryModel::dateForItem(qreal offset) const void WalletHistoryModel::appendTransactions(int firstNew, int count) { + QMutexLocker locker(&m_wallet->m_lock); const auto oldCount = m_rowsProxy.size(); for (auto i = firstNew; i < firstNew + count; ++i) { auto iter = m_wallet->m_walletTransactions.find(i); + if (iter == m_wallet->m_walletTransactions.end()) + continue; // already removed by wallet again. if (!filterTransaction(iter->second)) continue; m_rowsProxy.push_back(i); @@ -303,6 +305,8 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) void WalletHistoryModel::removeTransaction(int txIndex) { const int index = m_rowsProxy.indexOf(txIndex); + if (index == -1) // probably never got inserted + return; int row = m_rowsProxy.size() - index - 1; m_rowsProxy.removeAt(index); if (m_rowsSilentlyInserted > 0) @@ -313,7 +317,10 @@ void WalletHistoryModel::removeTransaction(int txIndex) void WalletHistoryModel::transactionChanged(int txIndex) { - int row = m_rowsProxy.size() - m_rowsProxy.indexOf(txIndex) - 1; + const int index = m_rowsProxy.indexOf(txIndex); + if (index == -1) // probably never got inserted + return; + int row = m_rowsProxy.size() - index - 1; if (m_rowsSilentlyInserted > 0) row -= m_rowsSilentlyInserted; // update row, the 'minedHeight' went from unset to an actual value @@ -333,6 +340,7 @@ void WalletHistoryModel::createMap() // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. // This is oldest to newest, which is how our model is also structured. + QMutexLocker locker(&m_wallet->m_lock); for (const auto &iter : m_wallet->m_walletTransactions) { if (!filterTransaction(iter.second)) continue; -- 2.54.0 From b15f0f651db4e55ef1aea26d99ff8f2507e96d14 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 16:17:03 +0200 Subject: [PATCH 0421/1428] Fix a copy paste error, make this work more than once. --- src/Wallet_support.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index c233ba5..3d9b2a1 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -413,7 +413,7 @@ Wallet::InsertBeforeData::~InsertBeforeData() logDebug(LOG_WALLET) << " +- combining" << list.size() << "tx. Height:" << blockHeight << "blockId:" << blockId; parent->newTransactions(blockId, blockHeight, list); } - parent->m_inInsertBeforeCallback = true; + parent->m_inInsertBeforeCallback = false; } Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) -- 2.54.0 From d1d8c8ad0acf2c3f8571174079a2701283209097 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 16:17:22 +0200 Subject: [PATCH 0422/1428] Improve comment --- src/Wallet_support.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 3d9b2a1..9c1945a 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -436,9 +436,9 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) } assert (lastToRemove <= m_nextWalletTransactionId); - // we do this in two loops because a map reverse iterator doesn't have a matching 'erase' - // method. - // Using the 'key' in the next loop instead. + // we remove mathcing ones them from most recent to least recent below. + // Notice that we don't do it in the above loop because a map reverse iterator doesn't + // have a matching 'erase' method. while (txIndex >= lastToRemove) { auto i = m_walletTransactions.find(txIndex--); -- 2.54.0 From b1320899e9f0fa55af23c4764fddae8f45d24814 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 19:29:55 +0200 Subject: [PATCH 0423/1428] Optimize the newTransactions to avoid work Don't do any work when the transactions that are being added are all already known. This is common when we send a merkleblock request to some peer after we already have received the answer before. For instance when we double check nothing is being omitted by some other peer. --- src/Wallet.cpp | 18 ++++++++++++++++++ src/Wallet.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 31f2411..ba19cd7 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -404,6 +404,12 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: bool needNewBloom = false; { QMutexLocker locker(&m_lock); + /* We ask multiple peers for transactions, each hit gets a callback to newTransactions. + * There may be nothing new. though. Most likely even. The next method just checks + * the transaction IDs and if we already know all of them, we simply return. + */ + if (anythingNew(blockTransactions) == false) + return; std::set ejectedTransactions; /* * Our UTXO based system requires interpretation of state based on explicit ordering. @@ -1063,6 +1069,18 @@ void Wallet::rebuildBloom() m_bloomScore = 0; } +bool Wallet::anythingNew(const std::deque &transactions) const +{ + for (const auto &tx : transactions) { + auto id = tx.createHash(); + auto iter = m_txidCache.find(id); + if (iter == m_txidCache.end()) { + return true; + } + } + return false; +} + bool Wallet::isSingleAddressWallet() const { return m_singleAddressWallet; diff --git a/src/Wallet.h b/src/Wallet.h index 9ee9b60..a7b7de8 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -410,6 +410,9 @@ private: /// Fill the bloom filter with all the unspent transactions and addresses we handle. void rebuildBloom(); + // returns true if any of the transactions are unknown to the wallet + bool anythingNew(const std::deque &transactions) const; + // helper method called from both newTransaction and newTransactions void updateSignatureTypes(const std::map &txData); -- 2.54.0 From ee49a11a2d276c64680aab226b28bac0e788491c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 20:32:14 +0200 Subject: [PATCH 0424/1428] Fixlets in naming, making cppcheck happy Mostly avoiding reusing names also defined in a bigger scope. --- src/Wallet.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index ba19cd7..3433de9 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -983,7 +983,7 @@ void Wallet::rebuildBloom() int changeUnusedToInclude = filter_changeUnusedToInclude; if (m_walletStoresCashFusions) { // a wallet that uses fusions eats through change addresses like - // cheap candy, lets avoid the need to upload it too often + // cheap candy, let's avoid the need to upload it too often changeUnusedToInclude += 50; } @@ -1965,13 +1965,13 @@ void Wallet::loadWallet() recalculateBalance(); #ifdef DEBUGUTXO - for (auto output : m_unspentOutputs) { - OutputRef ref(output.first); + for (auto outputIter : m_unspentOutputs) { + OutputRef ref(outputIter.first); auto utxo = m_walletTransactions.find(ref.txIndex()); assert(utxo != m_walletTransactions.end()); auto out = utxo->second.outputs.find(ref.outputIndex()); assert(out != utxo->second.outputs.end()); - assert(out->second.value == output.second); + assert(out->second.value == outputIter.second); logFatal(LOG_WALLET) << "Unspent: " << utxo->second.txid << ref.outputIndex() << "\t->" << out->second.value << "sats"; auto locked = m_lockedOutputs.find(ref.encoded()); @@ -1983,14 +1983,14 @@ void Wallet::loadWallet() #ifndef NDEBUG // sanity check: the inputs should resolve to transactions in our list. - for (auto &tx : m_walletTransactions) { - for (auto in : tx.second.inputToWTX) { - auto key = in.second; - int outputIndex = key & 0xFFFF; + for (const auto &tx : m_walletTransactions) { + for (auto input : tx.second.inputToWTX) { + auto key = input.second; + const int outIndex = key & 0xFFFF; key >>= 16; assert(m_walletTransactions.find(key) != m_walletTransactions.end()); auto spendingTx = m_walletTransactions.at(key); - assert(spendingTx.outputs.find(outputIndex) != spendingTx.outputs.end()); + assert(spendingTx.outputs.find(outIndex) != spendingTx.outputs.end()); } } -- 2.54.0 From 309c4256e7c5f34a8d4e569edb7b6f535d068a4f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 22:41:27 +0200 Subject: [PATCH 0425/1428] Special import feature for bigger bloom filters. Fixes: #14 During import we now always send all change addresses to be monitored for activity, even if they are empty. The behavior of not reusing an address after its been emptied is not universal and as such the import should account for this. The privacy issue is really not harmed by this during import, as we basically send all the addresses in a relatively short timespan to the peer anyway. --- src/Wallet.cpp | 27 +++++++++++++++++++++++---- src/Wallet.h | 1 + src/Wallet_p.h | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 3433de9..d21959c 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -823,10 +823,11 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons // So to reuse the derivationPath we store the last-used-index elsewhere. m_hdData->lastMainKey = -1; m_hdData->lastChangeKey = -1; + QMutexLocker locker(&m_lock); m_segment->blockSynched(startHeight); m_segment->blockSynched(startHeight); // yes, twice - QMutexLocker locker(&m_lock); deriveHDKeys(200, 200, startHeight); + m_walletIsImporting = true; } int Wallet::lastTransactionTimestamp() const @@ -1044,7 +1045,16 @@ void Wallet::rebuildBloom() * and have no current balance on them (utxo does not refer to them) */ else if (secretsWithBalance.find(i.first) == secretsWithBalance.end()) { - continue; + /* + * Ok, the above is true and is good for privacy. We want to avoid uploading stuff + * we don't ever expect money on. + * BUT, some pretty big wallet out there are not so nice and are known to reuse + * a change suddenly. + * Sooooo! We still actually include those 'old' keys during an import and + * convince owners of such wallets to switch to our wallet by being great! + */ + if (m_walletIsImporting == false) + continue; } m_hdData->lastIncludedChangeKey = std::max(m_hdData->lastIncludedChangeKey, secret.hdDerivationIndex); } @@ -1335,6 +1345,7 @@ void Wallet::setLastSynchedBlockHeight(int height) } if (height == FloweePay::instance()->headerChainHeight()) { + m_walletIsImporting = false; // start this in my own thread and free of mutex-locks QTimer::singleShot(0, this, SLOT(broadcastUnconfirmed())); } @@ -1458,7 +1469,8 @@ bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) if (startBlockHeight < 10000000) { // if that is false, its a timestamp: headers are not yet synched) - rebuildBloom(); + rebuildBloom(); + m_walletIsImporting = true; } return true; } @@ -1734,8 +1746,10 @@ void Wallet::delayedSave() void Wallet::loadWallet() { std::ifstream in((m_basedir / "wallet.dat").string()); - if (!in.is_open()) + if (!in.is_open()) { + m_walletIsImporting = true; // If we have no history, we need to rebuild it return; + } QMutexLocker locker(&m_lock); auto dataSize = boost::filesystem::file_size(m_basedir / "wallet.dat"); Streaming::BufferPool pool(dataSize); @@ -1891,6 +1905,9 @@ void Wallet::loadWallet() else if (parser.tag() == WalletPriv::LastSynchedBlock) { highestBlockHeight = std::max(parser.intData(), highestBlockHeight); } + else if (parser.tag() == WalletPriv::WalletIsImporting) { + m_walletIsImporting = parser.boolData(); + } else if (parser.tag() == WalletPriv::PaymentRequestType) { pr.pr = new PaymentRequest(this, parser.intData()); pr.saved = true; @@ -2113,6 +2130,8 @@ void Wallet::saveWallet() builder.add(WalletPriv::Separator, true); } builder.add(WalletPriv::LastSynchedBlock, m_segment->lastBlockSynched()); + if (m_walletIsImporting) + builder.add(WalletPriv::WalletIsImporting, true); for (auto prData = m_paymentRequests.begin(); prData != m_paymentRequests.end(); ++prData) { if (!prData->pr->stored()) { diff --git a/src/Wallet.h b/src/Wallet.h index a7b7de8..c6ca912 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -572,6 +572,7 @@ private: // auto-detected, causes us to send bigger gaps for bloom filters. bool m_walletStoresCashFusions = false; + bool m_walletIsImporting = false; // only true for the initial wallet import. // user settings bool m_singleAddressWallet = false; bool m_userOwnedWallet = true; diff --git a/src/Wallet_p.h b/src/Wallet_p.h index 2b37ff4..c1cf41b 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -85,6 +85,7 @@ enum PrivateSaveTags { enum WalletDataSaveTags { LastSynchedBlock = 2, WalletName, // deprecated. We no longer save the name in the wallet.dat (since that one can get encrypted) + WalletIsImporting, // per tx-data TxId = 10, -- 2.54.0 From b6d895e8f5de76b599ada4970cd63b78899fc4ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Apr 2023 22:47:18 +0200 Subject: [PATCH 0426/1428] Fix anythingNew to also check for blockheight This means that an unconfirmed transaction doesn't stop it getting newly added, which is needed to mark it as mined. --- src/Wallet.cpp | 10 ++++++---- src/Wallet.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d21959c..866edef 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -408,7 +408,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: * There may be nothing new. though. Most likely even. The next method just checks * the transaction IDs and if we already know all of them, we simply return. */ - if (anythingNew(blockTransactions) == false) + if (anythingNew(blockHeight, blockTransactions) == false) return; std::set ejectedTransactions; /* @@ -1079,14 +1079,16 @@ void Wallet::rebuildBloom() m_bloomScore = 0; } -bool Wallet::anythingNew(const std::deque &transactions) const +bool Wallet::anythingNew(int blockHeight, const std::deque &transactions) const { for (const auto &tx : transactions) { auto id = tx.createHash(); auto iter = m_txidCache.find(id); - if (iter == m_txidCache.end()) { + if (iter == m_txidCache.end()) + return true; + auto txData = m_walletTransactions.find(iter->second); + if (txData->second.minedBlockHeight != blockHeight) return true; - } } return false; } diff --git a/src/Wallet.h b/src/Wallet.h index c6ca912..44883fe 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -411,7 +411,7 @@ private: void rebuildBloom(); // returns true if any of the transactions are unknown to the wallet - bool anythingNew(const std::deque &transactions) const; + bool anythingNew(int blockHeight, const std::deque &transactions) const; // helper method called from both newTransaction and newTransactions void updateSignatureTypes(const std::map &txData); -- 2.54.0 From d9d92847f60acb5ca54f68ce236b1c09a1f6e99e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 10:33:28 +0200 Subject: [PATCH 0427/1428] Make section names stick to the top of the view. --- guis/mobile/AccountHistory.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index a9aff1e..fcde014 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -66,14 +66,14 @@ ListView { minimumSize: 0.05 visible: size < 0.9 preview: Rectangle { - width: label.width + 20 - height: label.height + 20 + width: dateLabel.width + 20 + height: dateLabel.height + 20 radius: 5 color: palette.light border.width: 1 border.color: palette.highlight Flowee.Label { - id: label + id: dateLabel anchors.centerIn: parent color: palette.dark text: root.model.dateForItem(thumb.position); @@ -171,6 +171,7 @@ ListView { focus: true reuseItems: true section.property: "grouping" + section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart section.delegate: Item { height: label.height + 15 width: root.width -- 2.54.0 From 1ddcc3b05a90a025fc0f5975c0af65f93f1b56ba Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 14:18:01 +0200 Subject: [PATCH 0428/1428] Don't accept unconfirmed tx while doing the import --- src/Wallet.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 866edef..986db30 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -328,6 +328,15 @@ void Wallet::newTransaction(const Tx &tx) bool createdNewKeys = false; { QMutexLocker locker(&m_lock); + if (m_walletIsImporting) { + /* While importing the peers have our bloom filter and as such we could get notifications of + * transactions entering the mempool. Which would be messy as they really should be added + * after all the imported ones. + * So we simply reject them for now. The peers will get a 'mempool' call at the end of the import + * to fetch any such transactions (again). + */ + return; + } firstNewTransaction = m_nextWalletTransactionId; const uint256 txid = tx.createHash(); if (m_txidCache.find(txid) != m_txidCache.end()) // already known -- 2.54.0 From 79b9ec822aa9fde970ae6c8a623e57d9056c5d9f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 14:34:00 +0200 Subject: [PATCH 0429/1428] Avoid rounding. --- src/AccountInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index de0d205..b1994aa 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -120,7 +120,7 @@ QString AccountInfo::timeBehind() const auto weeks = diff / 1008.; if (days > 10) { const int w = static_cast(weeks); - return tr("Behind: %1 weeks, %2 days", "counter on weeks", w).arg(w).arg(static_cast(days + 0.5) % 7); + return tr("Behind: %1 weeks, %2 days", "counter on weeks", w).arg(w).arg(static_cast(days) % 7); } auto hours = diff / 6.; if (hours > 48) -- 2.54.0 From 53166485c3ba02656dd415c40ba317baf20f22c5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 14:52:53 +0200 Subject: [PATCH 0430/1428] Add more background around logo Some phones have rather extreme rounded edges around the image, which cut off too much of the logo to be comfortable. This makes the logo a bit smaller with more blue background that is Ok to be cut off. --- android/res/drawable-hdpi/icon.png | Bin 2918 -> 5413 bytes android/res/drawable-hdpi/logo.png | Bin 705 -> 4846 bytes android/res/drawable-hdpi/logo_port.png | Bin 705 -> 4978 bytes android/res/drawable-ldpi/icon.png | Bin 868 -> 4907 bytes android/res/drawable-ldpi/logo.png | Bin 491 -> 4646 bytes android/res/drawable-ldpi/logo_port.png | Bin 491 -> 4656 bytes android/res/drawable-mdpi/icon.png | Bin 1142 -> 5108 bytes android/res/drawable-mdpi/logo.png | Bin 577 -> 4701 bytes android/res/drawable-mdpi/logo_port.png | Bin 577 -> 4696 bytes android/res/drawable-xhdpi/icon.png | Bin 4252 -> 5636 bytes android/res/drawable-xhdpi/logo.png | Bin 866 -> 4975 bytes android/res/drawable-xhdpi/logo_port.png | Bin 866 -> 4986 bytes android/res/drawable-xxhdpi/icon.png | Bin 3437 -> 6570 bytes android/res/drawable-xxhdpi/logo.png | Bin 1247 -> 5222 bytes android/res/drawable-xxhdpi/logo_port.png | Bin 1247 -> 5240 bytes android/res/drawable-xxxhdpi/icon.png | Bin 5187 -> 7795 bytes android/res/drawable-xxxhdpi/logo.png | Bin 1728 -> 5652 bytes android/res/drawable-xxxhdpi/logo_port.png | Bin 1728 -> 5656 bytes 18 files changed, 0 insertions(+), 0 deletions(-) diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png index cd3aff42c47bdd631518fd9a9089fe75dcc422c7..95f0759e16587b27f98f1cccfd1efc9a8b755027 100644 GIT binary patch literal 5413 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84rT@hh9qO>QU(Twqg5dhB|(Yh3I#>^X_+~x z3MG{VsS2qTnQ06R6}NH&9Qh6_@a+EFW!re9vF-VVt&_hs+txEWU0t`i#n8}5G4cPN z_=gt{s5o9)wDXA!Z_ttg(}jf#%9d%&wN9xjS=)X%LHl4?&*HTG_ODA=de+YJTCPyL zYhzrm(!BNdj{gLr+;fsXFL>nmAaMfwm5*y@bn89WUsJuu={Q$!nEzYZqrW4?X5X9k zM%LiHf=jZ48*>IXyYLo+27^U2Cd^Dd;~B)Y@9}Fn#(npWR!sZ9)82jltnCs40-cY~800?U zeKgbe_JckCGKs2@s&?vf<}aI$^v8tT)IE+ViF`0q(0KAZ^*({_qkLgjGp73VR?Mn6|s#hoJt~B0^C^5lD52B65(;V?$B}j zH@{SiG@ocSPfTVE&6p5t|3m)IyK7oLzEz$NSuT8iWFf!U;k(|2HP+|8Z+vXx-|)b= zVCnY1SJ@1HdsgjT>)PM&FZaQtU28R~4?N{F_>yvB*&8p%SxyTX_RPO%{^n&uU zuh(wvzs#=S9J@5Hq1icMjmOQ`cJJT4eDn6c@!EfN4EsHcjZxN)4{^3rViZPPR-@vbW>1sj#ZZEyztRNmQuF&B-gas<2f8n`@Ot;j4hQnKSxuqjGOvkG!?gBnqkl4h%vQBqQ1rLSLJ zUanVete0Puu5V~*X{m2uq;F)TTa=QfTU?n}l31aeSF8*&0%C?sYH@N=W(tB0+w{vfnBtKRGkS3d~GOOindVHZayTvPd@6HAyo`*0nH7Nz^sBNHjJ}G)OZ_N;O0> z$}_LHBrz{J6=YOJZh>BAW{Q=0N}91jqG_V8u|bl7u1T6jnr>p6xuI^7VX|dvvW10N zN(!#!#G|@0A$-q$8I62u!*TghAQ8&rl#6mYYCDA<5)YLNB z*wPH_nv`TKxBQ~q#1dPj%-qEERQ-aybg%>{z^xnuJZ+VX^b8Op0Xc~!Y57IDwn{#k zd8HKyiIB|P)ZmgtP-q&OSr}Rv8Cn{d7#o@!7@8v#g{2l1XXfXD%rrF6Gd2OsfTG39 zzbG>`uOtzaPHmOozO-^KN=+=uFAB-e&#_ekIZ44t&k*9AirfM#-_(-Cl*E!mm&B4( zTO}g{BV#KA6DvcD5JM9y6H_ZA6Kw+{D+46;`6-!cm4sB=fii~;B+5N9i%as0D#1dK z92J~e2;o7vIhi2U3JMC~OktIn4Dmv7Vp(bm*n4oP8W`owo2}q zxdq^`QqTZLlqRZM$}>_yv0z|itZQJRiy2`05HH&3gUSq;k8SiZ!UUoKsgSYbQhEyJyQIyBQdm83KGlTtPi924e<>GzNy53=C%&82*EJ#>NcB zX$;0Q8H~>`82<-JrWrG&r7@(cBV1I%ru6XGZ|){VVL)v4F5s)7#kZK zrx_d1G&VkCZ2TW&ZJM!hTAFd%OyjgO#%cdSu9#_TJTuLB=1k+6XN+h52RZ7DvGJKS z<1;gj&zvzn^WXSCgYkc3p#c`Ae++CKxUmu zGyV_qP?~WX2+T}NJCm07ALPH8#%VLt(q_&~n|UT}=6{fP&lrOw&&*6ab0+P~|Fr)M zY5$GW{->q=pPBamOxpkdGeKczJQEZ;GiQSQ{~r`KATOoO1UdT5OymEcpaNN(HWOs@ znVD(-K_Lf{PMZk=XJ*d)4+_XL#vp6Y%$#}V%*-?YXZ~lH`QLcv|FoI^XU_b8X6FC@ zXF!o;d`XZ{<3WfpD_jn)tQ+fgTY4s{|}0#|NlXL`v3pGqUr<{xsEpz zCw-qW*-oeP@R55S-V0M4yZ_9d+z{OR{rj(L*Pr$D9S*VRy3#!H@9fFHXHCA>IWg6_ zTi&40(08g&$a4SS6$&bSW*VKYhFw-#odQA~1+&(zesz5Lv!jao6KcD5JpS?K!Pi$N zHZ$X#Ig1dFuEeLv=E)2UjK@4(977@wzrF4objU!0EkW-{*Wr?dJ*x$b{AWM$wtl<8cxkc%b1X-Z)1IhP z6@R|BYd0$SQ!M3Mkdwp`d4H+Z zNyTR!6%rF(J+Wdp7m2ufNy{PfP)U8JsFd1ug&8j$a%Omiv3{CdH~*#M&7cM@mPwt4 zu^O@5A&sZ*S_x?A2C>=h?O>=qp?_AfS?Q-e`ZN}tO!_u`=h_CPi#MM7 zPu$ZWdf~56%ARWtFE%hrd1j<-cVWI9x}fBWxP9zC%)iHFxk$99Fje)Up` z&H8&ec~caa=d!p;PxLTk3zTa)yL?3>Ps##sr~NvKGouWCIvxTQg`Tc{F6*2UngHV+ BG!Xy* delta 2114 zcmZ3g^-OGn@@5VJUgpV4!Xot?Jf1F&ArY-hBW$Z~y9pfKe)~-v%k6|&8!qXdaVrqq zu~6zI$2M+B-is65J!ai#wOb{qFDP2JsBMqmv%ND}4=(BU?0Ups_U-I!bNkb3Pb8A~ z43|mNKPwf!J>#~-Zi(A}jMvW&@%{Aie;oT?c9&QmRgVkdj$I{6`#I)WA8e0(S%2^3 z?!3RJcMBD1-;KI|@74CTNe7!03}2=0T(~|h@?O2Q%z6Fay6<;qvT{9o%)h@g?&rn$ z`RATL-zdJ0yK6-Yo8F~B$@6<;xAX7u|GrvG{MuG~tJn<&E#CV(tR^?;nCAuGn}7c5 zg)Or=vX;h|d<>8MF3Vq0n5|#G=jYMo-wRGJuAlam$uh+H`Si^4?X%yh?+A0+;}r7u zPu(p2|83nOp^KveY>xlz{w!!Fa=BT_tE&Fb$G@EXrc)BWdNSI^>e1yh zamr*`JgTo>{LRIM+vr?!$EB7V=OYqoW%An|wv{`0ba5ygn0VwueND}gWSRAQA{Z}T z+IUgrSagR-M~w+j8wZ!^!44U{1;-{yP5k*b`ux^a?UQEoiJVx=D$}MA6SGjOW$VA( zIIXUXS*%x%FAz^jN_@7$NyX+;*C8gQsdH=%6@PIrxv=EsoBDL;1!-O3>}lWM&dEOE zKf_Hi=*F+AHFM;XlueV`d02E?xEfb_280M`Xk4;5R8^}Ks&#-{_tKY8f#id)c-%@a zFESHSj1yS;$W@SsBU66$w6L=lx6i!tak!}H*|{ZaVO5E(k?WN(?$;&$^Y>l3YRfyf zr{ay{^abBK{v4QnyIY}N(7-`UWy6chb7J1lK7P8CCFQG7h=BCsWtCixN|wg^HeX1Z zZJ8IJ@q&|ozPZok;{l!*%DzW8Jvu6&8`L>LXhoi}rG|q^PI;O5r!>|1 zQ=L6!vsWC6-TcBWEPk=#(L<{rpPlwKv;O+c?KyUC!4sZ)|JXmruYRsy{rq~q7m_;! zCivdfFjnOWqY5(+@F5Ge*OA=_GN~r;q{ic@;A46hgH4)YN&U{K>Lh9QG|46ME&1j z-o5{JF4IyjF4=K=;u6`SdegVtCTBg22%cNde)l@h%iW9BT5FchyncOslB$E~j4%Tx zpUAED8O-8)o*R9fBx^8C?&l`Q7qhC4; zK75&7e_nf4)4e9Mw-YYxJ>;ltw|h~4{`2QkPwDIOw*EM28@w;_aF=%V`ISty0S|o- ze1D|sZ0xHvX`xirVfKZ~qvOw-1_x=G7hgHB=M)3Sb2cL`??a}GV*V+}{eA6odqu+G zW7g}HPHCsiUG&-XveeJWe`fOP%8pk*vifk22pg7xZ6vJg<1|bDdhj zziYRuEf)|wl@_*GV_R5<$5yun<-SEutxKe@<*j{tO{&cC3(x7+`I{F_d-9g|rhnmW zho`zmf>9gQrmvrxt2(8=_1bQ6Zf=WR=6xRm+2ah%+`lniTl1|;dF5F(cBNDMUOSvO z3Ax!LvHcF`y{&InTJ>J}R4cUF{+C9lSbknVYNzRs#7>S=0=o_|W%)Mf zm^7yzxH~P>dClFZUjC8~kD?wNuX|?jR=kZj;Gp0pA)}SC+FAM{3KZNg%;T8rvh z1EE|#UXAIcJ2;muww7Df&>{8s#H1DXitgTTJn@?U@9EYh?yFDodWmi-dd0K8UuPa) zxJ==s)ca3%-r%`a!|b9ncb3ngWbsXBPH-Nz+575cn8nUyt$EI7;Uo63^}G=JbKM}?+d;#i_O(%Y4jmw4Yly?_4?2CM!_y0cciR=E0Mwc^S} z(=s}8y2XCFl2>y>P7&-&@5ZuKT?1waB#vvl5o?m}cKwuqlag%1(_+ zmS1uU9fh?dMPKiWeISr*tuH{95LJ6@_6rn9HAz6yc4#!D} zGd?<#+HRhetygd3_FLvcL~tn2wU;?N{NMaqwphbhDgN*`&&;CUsq>F%Rc_<1Xgjyg zDEsxFnVr_>_phwHut~m7I>{^GFb wTGX*?^Pd{|*TmJ_ieOj&oqE83$4mK{`!e^elr0r!U|?YIboFyt=akR{063le@c;k- diff --git a/android/res/drawable-hdpi/logo.png b/android/res/drawable-hdpi/logo.png index 49f852fe68bc3be63f0fbb68052e7f74228b1531..4e9eb7cb489fb15f24cee45bc7a3a37a99b74501 100644 GIT binary patch literal 4846 zcmeAS@N?(olHy`uVBq!ia0y~yU;twdW(Ed^51)!xGB7Y4s|txI2}&$iC@9KL%gjkt zD5)$+RYQW=boW^ywIe5HEP4C4 zYFRY{MTv)k?cbQHzJ6F5g^*Sq-Z(=paZL)fQSI_5b-f6o2)Jc!` zifh6Ts{HwST0_ps|BAECp%aHVY*@LP&Gt`S{j%uapBosmSfy^?`Xxe|2ys7=g-4jB zBi=_deQ!V5<1dq_8mVfhE@%F-=}3P}xJ}*Tn3Bi`GX;$&&r|Oc=swC9W;J7~PjAI6 zi&a@xPq`!eL(fIe+3MB1_1(#MiF2>DPsc2`Y*G>1$ik^4awWix)hubtt0fU0m+KB4 zw}10XwMg@cR`bMU#?XujvGzaY|Gc}V<>OoB`Hlpi<;HsXMd|v6mX?*7iAWdWaj57 zfXqxx$}cUkRZ`+oP*8vxUXfei>kBtNuNWE%$@#hZ6^RA?&DBSVX1vm}EwU6W)ZQ(cp^)Kpyy<3w{^OCt*dGvma>v@|mV zB%?g@ic1pnl2buORpb`vWoD*WC7GrfCZ-uC>ZVzw8R?p&m?Y^Yrly+e8YGz~8>bkW z85mnyA{pUdl$oBHmzaa>Dv(hrnJHGOhL*`GDF$Y`2C2ywx+bP+#<~{CM&`PvX^G}0 zX{jcumc|xfqf(Ns-13WZ6H9EBGIJBtQ}qk-(!mm-0Jm}s@U&Gj(t|i0EE15DSdx}s zlxwTxlbKgqflwNfnVTA1k_ZY-Lo*9Q3nN2IV*>*d6LSNIMPS`wsYS(^`FS8S4Gr{+ zO~Beg(PHIal$n}Wk_bwtwn~Oz@rv96E9aur#FG4?ko^1{TP2VY3PyT{5a)oTeN#&k zQxZ!OT@p)DZIz4+jEt=eOsotoLJUo;OiZneEVT`ctPGIU=ci<*RT5Hd2g)2akSO=a zEH23}sssx`a#V0?A%q9v=466YD<~*{Glf-RGQ?gQZTVhG_gptu+%j-PBYUrF-|ejO)@r4(ls-%NU}6YGfJ~aNkTQfI6tkVJh3R% zF+DY}#8$~YGq(U7Rtg&6h|)w=U7nE&iUk8BV_gFiU3h>Q8$bh0AL2zDeNdSJ^RbOS zMwmbpAQdupTnZ4eAQv|~E*pJtNe3$RAO?a;9a>^&T+q@A1%**dNDAN4;2I4sl0twa z#iOZfG`L6#0g@DtrY@=l7Z;*Cm714gt5mLJZ^v*&^)>?o!@2;U5La%24)dsfPu)&W zugN~uQ)8ygPO{VRrMPilnAWudd3<<#i`lcjni3)=usU>F(;F0OPi0zp%bLZ03-lfY9ESpomeEZ=wuTN~u>D{>T#O@hody4z^>^Xm|vj5nzYsYt2 zow#-L#HU;5>nEH)f9FQ)gc~;=-QSk;;Pmo`T@xQZeDUZ?>Ej=7U-nOW`SRU|DU&~Z z`0``sNH{`33!oenxIsWM2b+-IVg z&(0B5UD(F?ch<+Ab^Q;_Pyf3#Z_~5RNk!Wo%o3VjnuVD1+1>bmU|eU-Q7 zvr-nj$CZ0ZPrk}y6kZ?X&-5Y8-}@Vqae2KJy}#sdbZV=0wDJel%pBdgpW8hl_z~Wls{$`8|)Z zJ-XztZmwtD1NB!=Rm+S6PBw*HoFAs#xna|HmCmV?QymZe5;<-=afO)S9(TL7+g`7j z-J;?1^y@VZhpM7doA^zwQAHR<1Hpm_M5atyT?-h?__8Hs!NmqPkM53@{To=tS8=W m)LGzuNpaqnj~_p7JjHMQ{;Sh&l~M!HAcCi>pUXO@geCx+jy}8q delta 488 zcmaE-dXRO33ggDf+ya~Y7#JARrll=oU|_H=3Gxg6Pc~p!c*I(6@;L$Vdd7TD7srr@ z*03q z`{%|FLJtzpUHEP4n#&||=iO4?l1S%mD}|IJ4o#SvGJ6h7HbecUT83#tww`+f8rG^b zxPDO;{Vda#<74`3a)I1YB?(T+*p&K{>z`eCa#td5@#NnRci!20Jh)M1YKWC)>70xU zeyg_M-1_~NY1rgVGgtU7-r=IArLpqJoi3CcBcjINIPR?aN$?+-nLh7!GJxWK#Cb6nZ`tCdTs8Ck(Z*WWH<5T~& zl=9s^o}GJSw&}cA$Kz_!@=}6Rc-;RVd(YplZo@V48%yLJrAo&qk55b7UU6QN@5$QB uUptQ~PMX-0qN4nh{p5@pGwSE&tN(B5wJds;%)r3Fz~SlY=d#Wzp$PzK8vy?R diff --git a/android/res/drawable-hdpi/logo_port.png b/android/res/drawable-hdpi/logo_port.png index 49f852fe68bc3be63f0fbb68052e7f74228b1531..74515081f27587e5b0aecbf25a2fae9ba8b2de3f 100644 GIT binary patch literal 4978 zcmeAS@N?(olHy`uVBq!ia0y~yU;twdW(Ed^51)!xGB7Y4sS1fG2}&$iC@9KL%gjkt zD5)$+RY!h8KCYj;@7*b)Pm*Pq#v4vTW7uhZY9fBSaSZ<$rJB1 z*Mu+3{B?VJpxGqDixQ6}y6N_|I4>}H@lkip*=e7}uko(mz#*|^+3PtR_3wB6_$GJs zH^*l+l@~gT3>$g`CcNvknmogP$;{PLPX6|*QT+dH3$vYjSLD^8J==aWFfg`cIy(n= zIy=Ms!@y86r*@*P$6*JVqw$x!ltdk^J1kOG_=!2X%4Can1t?zOT5B~U<`=84)Fcsc z_k&v>JUE_o_2ALSaQ1LVfj5mDwse2q#68T`Jpz-8+>U{#;NBP36W=!?zt(ax8 zD$D9AcVvI)x#&4ty?VF4I~gx=?zQ&mnB|sDDq~w*0sOkU+#lPyVhz}A9%`V@FnHMvNv9ivz!((?3sVj{At6%=mq6x zU$5QTf09c3}TFpGhKw<0qnq9nrC z$0|8LS1&OoKPgqOBDa761Z?ap3KEmEQ%e+*Qqwc@Y?a>c-mj#PnPRIHZt82`Ti~3U zk?B!Ylp0*+7m{3+ootz+WN*i1Q(;w+TacStlBiITo0C^;Rbi_HHrFbz*a{@9ucQE0 zQj%?}6yY17;GAESs$imLqGzD%T9H|1q-4jXU{jQmW)}AAQ`ZCkR4KyTL3o~MK#RtV8!4tvU15!E(JNy z)5TT^WWQBPesX4t6@+P)YHE_4X0Dr@WSpjJVrr49YiVX+p=+9Gl9Xs>W|m}-Xoh5z zXI^nhVqS78$f%0k0=>-46szPk^F%Yl(Zb9`*D~2KDcQs% z*}x>p0BlrBvXxtYQEp<1tx{%gVtT56L0&po0uZG(LYxDV_DwBGOi3(B zbV)2pwN)}QFfz6>FtIYU2r)FVGBLF>G0-+JvNAwYpP!PMR!K;;9Vm0yK%(3uv$!O` zs1hs$$x*?ng%BQuo0ADrt)QR)&J^ z5?dwr%-jNSSSe_LBT5rhb$LcAC>9KijCBo6bP)k&2n{fOh!<`2L1hNa$2R&HVFFQr zRLIzIDL}-6T-@xqZ1lk;9jMfU7ziqLXo;b5K}#zX6h3IUQ7kEX8C z;36pmNK!nSx~LXhT!`*eYF>)1Qn`}7-Em&qTMP^g>jHd2T)720%%l1}bvr%1Ci_%R zjhQk##kMOYuq7pRUgnC0+0Bb8lA0>Lx+*J|*PUG5nP1;oJGm>QyQ^!%geP04pWi(r zwr9r7ol8@Dmo7W7Y)<|1?T6RAKCv;UcjLwryJwW`Del{|=lrqC{$t0k9p7Db;?~U* zpKhJ6pK$*Cog1waZrpfue_PIj)5{-rO?>$9#iJ{wkAJ*<*+1#!%Xc59O#blU%a56p zfBg9M_k6;?=Ntado$~+x|EUiSZD(L$U`+CMcVXyYmGxj?VBjq9h%9Dc;5!1sj8nDw zq!}0(*h@TpUD=xRH zaH8F=$8!Q7u?kJ=TolAF@7q?*diT$z%3J>nR`cJVqV=@t@@vseu7UDJb93}oyHqU= znv{8A_MOG6yIU{0hPF@IdP=gaTU(oLLaFY|;w#6c4xg~Hm;SvmZO*i>@{_V$9VR{d zHp#2&{N;`37)no^yspNr=$)GXK%g@?b$08cs7A4M~V!=1}LEOX(s~S$E zot^sEXyx9v+51;@iqDt0u&y!Ut zzco&auucq_6~qv<%K1TIyh5S%>OJm$^X|{Gxz9T3PzdJ)|7XiMJ^o#IIa%fXq-~uM zpV*Wp@4Pqn^F7J5U5Q8MJTc#J>PXIy%=bRGv$$?4uUs?hiB3n3`kxf{s7H(E`Yvr%c-_U$TwrfMkjd#oXqGi-R7Hz(pXZ~8ygKwGdyVIw( zo!)$$byB?9Zs%>@JQJO3e-vE0x>_V&&As1x?sfjUCoeZ77JbV)arR92nsY8QY7C!T z>(<%)c^)s{pWTY_Hf-xu-oL5tIFeRXHhsDKRj*K^rFZ>4iFdD>=d#Wzp$P!8xQT24 delta 506 zcmeyQc93;~3ggDfoC2Hb85kJSrll=oU|_H=3Gxg6Pc~p!c*I(cfq{Xuz$3Dlfr0M` z2s2LA=96Y%V9fV)aSVxQeS7V;UyFeN+k-U2HtvfNrjuUOyS)?Gd_;A&>D=%2hKh4; zz1%V-XmR$h_>0SGcmkp%x_@r`AoL*d+=bt!uDMJicit`KEs1pAwo*tr;?RVtDYNIW zWHZ!ns%4laWb3&%pkb{_gXSY{@H~mcO~K$PyYRI z=bf#`gBw+*hFEEq&dI31;J0e~&8^>WnTAc?G;@XT;vFt(T1tDx^C!*p*u2AI*``Up z$FH&lX|5^IESjlvXmU-G#qS8IKiNxaHs>wb*7x9m>V#7v>z5|y8*%!3CCRl-IpTNI zFUxy_+dG%p+37;LF(R665hqu&YG^ugInL@!+5b}N3Sj|ycq{|2{IK0ftdODW&&vG@G#>NZ>xzp+H# zQL1!&^7ypG?G@)W`JSx3{I&C_;-rZ^DJsf8*-y@xF{6HNzWV=`Udy6q$qWn(44$rj JF6*2UngH@A1FQf5 diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png index 3fa338907700e8307a0bb9889b1ff6865fc8c1f7..4efde19ac74db745b66378f8b22aec19b8b41f97 100644 GIT binary patch literal 4907 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4rT@h2A3sW#~2tGj#Y(3lmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNTr9a1=VMz_I6Zmz`sz$p@Ls%Z&dz&W{h7PKZ0ZxX9>I zHOK2v1J3%tMqTX})L#YL96E7`!=_b+Ew@fP^zzd`t)<@KFKn)4@?JL#{B<_<<6GOV z{mjpJB~MCDuw?kfxUf>;oaC$C$|Fs$ViF8H^`G3IS9XEjYJ23hl;&?!85kH_GM$|R zJe{3kK4M^~m{U8^*5j~)%+dJET}q;k)*TipEBwS9U1hRGy8;xiaILkP5%Y`HS89@o zxckAa4;~y(x_awa~weRt3ImUhWj#ff;2gU&uc6sFVU5Sl*LLsUy?pcbzVX_BbqxDGi;l7wUzo+fz*~_S5>XQ2 z>tmIipR1RclAn~SSCL!500K7l6$OdO*{LN8NvY|XdA3ULckfqH$V{%1*XSQL?w=vZ=7D$SufCElE_U$j!+swyLmI0-I}l#h zD=EpgRf_NpP;kyKN>wn?Gto29b*;!OGg7kSQm`pXNwW%aaf2FBl#*tvlu=SrV5P5L zUS6(OZmgGIl&)`RX=$l%V5Dzkq+67drdwQ@SCUwvn^&w1G6G_TOKNd)QD#9&W`3Rm z$jro~{L&IzB_%Ee1qG<#6}bhzzHsC7ilL#9oS&;-kyxN_sAr&`o2!qkqqxMitOUP~ z;*iRMRQ;gT;{4L0-DH8i(0)-_2oHq}i`NlejAF-}Ugury0cN=h_F zGRiZrxFj(zITd77MQ(v!W@d_&nL(1dMY5TJu8FaEvaX4drKzr^v7wo6VydOFL0Vd( ziKTH8k`ewzndzB%i8;uw0vVN(nPQceY+;(1oS39*Y;JC%YhsaGdD3kRlguF9V`I~a4W|EPg^A;Jp+VDKu%&wT7FTk zt&&e>UTFnFA|x|6HMk@Z6q<%+7KRo^Mg~R(28JdkCPoNFVW~yMnfZAjGYt*&j7`8Y zplGr3FUm~KD@g>UQ(GlNuy{poft7PnYGO%#QAmD%j;#{NNeV`Kh7jj~qpaO z5?vBYQf-xt42+De3{0#HEkX=UtW3~4`Lvw)S)GY#sw{{P*51Pgrx8t4X)APA}It& zQaqZvMuUr_5FknMXzHR`aB(5JQ>l3=wo2tn_I8h#9P(peVB8$w6XMD((7`X#X=*v! z!+(ic=u{z@E)nteD8CvvyLvU%79YRG#^y75g*qkW`nr1$zj*QK-@kwFet((pWUG>C zpN4wN-}4Fk=ViwQR{i_^`RUUSO)Uq+qQGMlMmIQ{kOFHfI- zRMnh(_{ja6H($+IbTX=cp@dqut$D+orozRGPu{)vX503gUcO8I{P|l}xsyk*gHO1# zsdc}N{k&PTPtTZjYQ?Hc-MvSSpLl3uG0W9+(Uz?@uk4-i_u-u1_vc8-b{d#WKYHxJ z)%EGW&L&97w90FCt-m{`b>+0sh*j&>U%PhZ*uRgL|Gd8>CD$XZ*^|3&g@9;Rb?u(( z*I!A=_wfpL=qY!~Nq1=JPyO-px4QNeQPGb56699IxHfmfBXAWSfabaulMhq$%--^t5#nYm+nzl=zKMC(#q`q zZ-0Kgdi8nr+AHqfi&f-0^E`W^Y`gh|I=BTpc!fJ!4sV|GZ11e6dqiZr)paI+`txo3 z{+oS|wg}61Y3fcnc<8=O{B*bc*=~8W_{2ITWP2;CcgH5IRTjIjgMop8G0EHAg`tC0 z)`Nk8fwRCPvY3H^?+6GpPSxgT0=w%i_(OH!=^!Ry(o&t*KqTkV~kw<<_l( zI{Ga~%^cW^4WCR4IV6(7#eI7T(_8`7<|PYMZ1+kmb8z0)Bc zXJfNbb7_?@TA;zOOHpRog6(_n8gghZQ{cK#+sGm`dGh4Q1#ff|Zp+6uZkRA3KxxWb w){;5D6G8+8BqTI=Qcu5l`O?ye$C`oR!UWIPpO1)Uf<_=bUHx3vIVCg!06nidmjD0& delta 400 zcmZ3j_JnPM@@7tf1B~^Y1s;*b3=DinK$vl=HlH*D17ouszTf zn-jp{cOj|qk={|xpgVIF4&Gg=8?eQpBE`(N%FBDkOVu|=Pu~1<-=~cELGa5zf0#Eg zDRwMck-%}#*hFW}5y6RpC0pdQ*^(sNI-4tOgkKz4lA`MGnHu>`z5c-T%$G&;CZ7Dq zc4JKr3)`LxmOCpWerjnvuvoXD*Xw`+#$(8#V8>D)& z7V<^zHOp|gz~QSFT-iGGkhbirO{WEc~y9u5GEl)*%`mb$by5yLcF6XPZ@iked$01{ zy?68KKd>~#{r};zEB(&fr}9nV?T_EiEs>w3QhO+Vwf?Ug2_^FO7i$<87#KWV{ajeq IIVCg!03rmp5C8xG diff --git a/android/res/drawable-ldpi/logo.png b/android/res/drawable-ldpi/logo.png index 3e3da5feac3597702b43648dce233886b6739c5f..e80df4932149e0614c5e1d19b5e5aadcd0107c46 100644 GIT binary patch literal 4646 zcmeAS@N?(olHy`uVBq!ia0y~yV9*3%4rT@hhWYzG9ARK!I9e4FQ4*9`u24{vpO%@E zs!&o{kgAYck(tK8P;o0az>)8;0?+QxUAB!!G*!+oj8=ckUj2`0N$BpgLdTArXj$_1 zZ`HDB_Q@)dtHiWbt&bepvGan>MYiB+2bpsoUfep5uX!)aeZ&5!NB`oMD<|Zgy;{U| zsaoRoRfEo&zg%7I7p#-S`#CmoF!?ttEjXM1G%4zQe8l#e)^k&>)=sptI<-H2rQP1+ zkKQ*_GAmj$8%aCFdt~SxnYo~DVw+eFgZf z>Ff;i5CcQSoZ5-D9)}%dj>cc^QWABv?yyK%;V0(kDw8eR6`**9YpvCcm|v{EQj>bCe9M=VxE;Z z`(=vLnP*`~--WjrG#D(JF=1xv8P6cDeUD$uG48u}v|`%-o%ZhYXKj}d5a@h-#vu0* z@1vQ%w;$~Bmq}EORJBu=Gk@81q(3IyrtWb}N#ui>g2t2QsrLzVALR?PnlaU!mE=c4Cq_3GXF?qs~gx!2mKW0qSssfcZ4;Zzd265z&ambB&7k_eB>b%&1I zzxkzFr1?avd15kSXvTzC`ycXu-d)r3@vZWF$a3N9BMbS(4&U`Itg$}#edA*j|Aq&~ z1xvU8y~<|r+p}u#TG#%Df4L7H?OLl@ec&md!IzX1%ief7&T?AFuxI{7^QR35qZgE) zeZ6*T|7CUs=h&rr4b9F8YdmhgwtN5X<(s$njo1FGW7zLmbd<&T!Yl>`-ipkSh>{3j zAFJg2T)o7U{G?R9irfMQ5U{bYC`e4sPAySLN=?tqvsHS(d%u!GW{Ry+xT&v!Z-H}a zMy5wqQEG6NUr2IQcCuxPlD!?5O@&oOZb5EpNuokUZcbjYRfVk**j%f;Vk?lazLEl1 zNlCV?QiN}Sf^&XRs)C80iJpP3Yei<6k&+#kf=y9MnpKdC8`OxRlr&qVjFOT9D}DX) z@^Za$W4-*MbbUihOG|wNBYh(y-J+B<-Qvo;lEez#ykcdL5fC$6Qj3#|G7CyF^Yauy zW+o=(mzLNnDRC(%C_oLb$Sv^og&Ut&3=M_k{9OHt!~%UoJp=vRTzzC6#U-v~CHQp| zhg24%>IbD3=a&{Gr@EG<=9MTT8*I!UtlmqroO0s@xPHJvyUP-aOp`Ia%mF}Lt z0dO6lAV|;5EdcAP$SpuoS(2HC2rLxefMmelL3T(*ZUNj}6xA@lgB63r$jT)@xfJ9) zPZwJyko{IE`N^3nR$!)~p-EC=s!5`*g^`Jou8DD~rEX%PVWMuLQIcu0sex&#sc|Zj zQJ#6lC5d^-sUV{&atrh_GgGV*(~^u*Eew-%&5X?@u03i~PlUS0LUzBUB z@ zTCDtwGE?(P5<%(IR>=@7UXfd10Azrl62bCExAKU0-gb73eQXyl< zr2r8Na&fccve5^Zbf8iXVj!s0p(TdK1ud;mP#Cp@r0^XLuF>EkDFjGTJes;jgNvjP zAW89P>Y`e3aUr@>sd*{3O65xSc1xl%?=vtktPAi7ape~1Fpui@)a~^2n(R|OHD=1} z6x*(pz?PKMd6_E~W;ZXYNNTF|>Z+_rsUApYRvN`q3w;x{f`ozYZ-i;ei?4D7!r?_v=p7X~l`;Q&Fc6@i$iCZ^Me7beM ze!}_lcW$&!xN+mr{cSl9PA`AhHSyuY7mu!#KK}9cW&fm?FW-HbGWo-YFF$5Z{_*42 z-}4Foo^SX+cgp|&|EE4Uw4H&0ficP3-G!lpRn~)nfq}EYBeIx*f$sNq^FBxh{fr*mm>XJ3*0+5|Buhvq^HXoS7Rjod|}Fq>&a@Y7LRub`b%>)lulBK-mxgq<=_oh)ubwK#tmy< z|B{uwd?lZ z(`>%CE|;55+9zZ7MPvQ`TS=);nu_O@t_^>1;{3MU`#mw2ZXDDqNOjyK;eGVLRDG5C zu`65Hl{xv>e-CpN7dcaY@7c<4EA{?{#VyOMN)MX$eCM2LzdlLNs5W@6`*rh1m0&N= dAN~7v0i`k9_Cjv*Gk zZ!h|CH7E$MKDfCpLo}rOR!QrMn(zA~G{gGrn;jhk@{N_|TlBe{U&^XAH!1qO)iwsx zV&Mr>4pcZfOkrrB#L~Q|g?a9}wNK3Dw7t9@O=8*EEx@+vddi83*H~_cFOdwd_`OK^ z^p|cM&Umxfm_yUExTi0(*q^yNNL|0zFVdQ&MBb@00M)O=l}o! diff --git a/android/res/drawable-ldpi/logo_port.png b/android/res/drawable-ldpi/logo_port.png index 3e3da5feac3597702b43648dce233886b6739c5f..a9ad1fdd2df984ba8d2bed1cefa67d06eabdcf5c 100644 GIT binary patch literal 4656 zcmeAS@N?(olHy`uVBq!ia0y~yV9*3%4rT@hhWYzG9ARK!I8qf7Q4*9`u24{vpO%@E zs!&o{kgAYck(tK8P;o0aAe!&60?+UBnq@6}xK0+nxaPV0VBda*%&U2uTMP}26gTXR z_uo-!p!p(bZH1;pK=^a9Lj{X?)vg?5e)IC;R=)Owfs<~e%B_9!cdwuOfjdzvo{FS= zH@UX@;jEwEM5pk3zVC3hF*;%FQ6V*jF}IGpJKDBB@$Lt%+1yrZXIfbuy1)6_jC;wQ z%MLOfy@Z#H~URnTo=n;a3dWMSy5gmZpL3jd#NVYUmt68UiBcl+H842&(A&dvdz z&dxCZFfdfishw!+ao9oTX#C|aB~eG~4vUl(eqxTUGTEYC0g6|+)>_So`NirhHAzI= z{ovLI4~{2YJ$N)SoITu8;15G#X<<;wM4|uFMWT*W6x``A|1MYlo?&5N;w;fF=2>~O zU#2*nc@}o`U3iN@gTbO16K1BK@eJbH_xQCOK@0GL_U})XgqnIdY?e|QNA#%8B=|FD`r`& z%CdUO9oZjxE_%*ZuimZiPR2`|d#!ysX1QgPir7XLP9>2m0dA~jNn2hmiSW2wcj&nN zn_sF$noqQvCnht7W=x2+{~`b9-8C&A-zv|CEEm2$vXEcw@Lli18tZf4H$FD;Z+Ku_ zuyp(1t850pJ*)Pvb?tBXm;2z+uCET@GGd*)v>f7);`dO`Ww z*K4=-UuIWuj$NA9(CnPB#^dH|yZ7&2zIl7!c;>0WrfRwK%ybv!En1KTiQ< zW@1u)X^E|p5|@I40@Uz|+yY-;xbb<#&`?Ou&(*I;EYLU9Gtke?)koG*T;f_*f?r2* zNM%8)eo$(0erZv1s%u$lUWqcY5h=-V{RO2(IbiRkBKUR~>F((p z0M`Kug7nPX09DSpPZRu1!kHiCtH}Ar={p7nI)&_nwS_R=~|j4n&=uNSsI(Am>8uRm?t3_ z<(XGpl9-pA3Nor9w?Hp5GsP+;*)+v8(ZpQW)FRPP*TgW*NY^6S&_vfLDa|Y~HPJjJ z%{&pw2>+tY^vt}(9AsC4j7rH&u}U&CO)@i1Nz%16H8RyTF|n}JO*BhN(oM89Pc=15 zG%~idfVd_l*~%@yC^xahRw*+#F+EkkATJ$k2PnX;90NRUm5lTZ5F!CNi6v?IMY*<0 zKACx?6$put%-q!Al0;Bw8k$)cS{NA_m>Cu`;v>F*LC&PdElPff8^f+km(mP{;3 z!OSGpAUVa{P}j)9(oEOH(9lxX!Z^uT*CHt~IXTt9!qhAkk`&;k7w4yylqVLYI;N-Q zmDnn|XXX}w!%9H|98sF6ZYj@51;v7ak+H6Ui7q0*jG+Oh5AmXnKB&xq`PfDuBTOI) zkO~<)E(M5Kkc*ogmyJHSqyv?D5CcJ_4lOY>E@)|mg2JdJB!%y2aE%5RNg+Uz;?dMK z8eAlW07;5RQy0~Oiwn`6O3h2LRVr7qx7)VWXF3A|!@2;U5La%24)dsfPu)&WugN~u zQ)8ygPO{VRrMPilnAWudd3<<#i`lcjni3)=usU>F(;F0OP zi0zp%bLZ03-lfY9ESpomeEZ=wuTN~u>D{>T#O@hody4z^>^Xm|vj5nzYsYt2ow#-L z#HU;5>nEH)f9FQ)gc~;=-QSk;;Pmo`T@xQZeDUZ?>Ej=7U-nOW`SRU|DU&~Z`0``s zuWU_r-kUxxPLJWwy&3Nxf8}C+pyzveL)_yEcXz!& zFW2u9432>!nR~gGs;D!+csws!YwCLQT~_&?Sml$SPamte$b0Uj{i`Aa` svf#gJWwwWAYSG>Pb^)gU9}j@|t;q3ggDfoC2Ht7#JARrll=oU|_H=3Gxg6Pc~p!c*I(6@)-e%`k9_Cjv*Gk zZ!h|CH7E$MKDfCpLo}rOR!QrMn(zA~G{gGrn;jhk@{N_|TlBe{U&^XAH!1qO)iwsx zV&Mr>4pcZfOkrrB#L~Q|g?a9}wNK3Dw7t9@O=8*EEx@+vddi83*H~_cFOdwd_`OK^ z^p|cM&Umxfm_yUExTi0(*q^yNNL|0zFVdQ&MBb@01IG}@Bjb+ diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png index 638e09a618533fd08864556c222afdfb2b078ba8..d85ee4a3caf301abe6e85dc8656bd8b2d80ce6df 100644 GIT binary patch literal 5108 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4rT@hhJ-tuTNxM_j#h<4lmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNTr9a1=VMz_I6dmmOnd(gVw7p6S1u?e?!w@xAqqM|;zmt|f23 z)+~u;PWLoA8?{EHsH3y+t4rpBvU4hPtpz6C*Z*3=@kX`AYOBltxby7?EmoypKI!pZ zQ78PM%Ac>NHRPOTci2WAK5>Y{rbULWw5Gf3WzD}sn=Xo4DVatc+UoDJzkFtG_3J0^ z8!DaIESZhi9Jp0N{Et{V?=}1$v)~+0w`Yyw|8HBA_cGcxL|zx3R<6##z}S-M>>S|f z>FAt<{W}U#z}TlSIVb z4{m+%;CRy2gGVF7*~1+L{xB4l76z3}6#741BfQS8WW2<=*V?CJmRmNdh;3xyR1&!o;Kpi}wB^;32#?EkhmPC7 z`K4N<`9!OEVlrcB#)Me=AM$_RUDNXMt@3=xa^dSE3;D$k-}Nr6u|D^G<6{&5h6lz4 zOSk{M%4YD}vuf{J*Zzioxep%gTB})o;3=QMmy{FB-gr6Aa$3l+XZ}U=rws?A7nGlU zy>@H=Wp)MU*rj<5&CUsHJZ`?Wd;jj`o45Ck*Z!+x*zZ|%l*RbMECvSNip-FRk_cZP ztK|G#y~LFKq*T3%+yVv=u(7WwNKDR7Em25HP0!4;ReHaBzmh^`img((sjq==fpcm` zrbks#YH*cbNODznvSo^ry&acLg;hmvL2hbEqC!P(PF}H9g{>0UT&uidE0D0hk^)#s zNw%$0gl~X?bAC~(f{C7qo`J4wMP`|ik{y?VO;JjkRgjAt)QF;#G+U*Nl9B=|ef{$C za=mh6z5JqdeM3u2OML?)eIp~?qLeh<;>x^|#0uTKVr7sK5Hnm-i<65o3raHc^Atd4 zCMM;Vme?vOaVaP$Kn<_RE%5b)8=qGU4Ta?VT>Xl~0)0b01O41wePkWQC9Y*9_;nPA zR2HP_2c;J0mlh?bx|XHpl_(<{k&+D8Ur<_<1NKfzvVLk#YHn&?NwL16o*{~r?w-B@ za2=o^NYBhI0PCvAEkIFOl9`4GEEL~>WWe4*c1T5T0o+^^)iA$<6@$ab$|XO!6y!Wl z7h5He{Z=XY$(bouV5VtGqOp-#nyIddfr+KAiD^ojZlbBBp>CQ-k^%##z%49#`TEE1D-O_I#bbuCR2O?8t@j8iNu42;rD zQ_Yc#@Gr_t&&*5AL3S0$sFchUD-$EjWCP30oQqNuOY)0C^7C_Sl|W8XFw!%GI0q!{n_7~Xl30@H zl30>zt7K$gWNc+%Vr6I%VrXJzVqs-uscm3nWq_nUKP5A*l8|aUQ0B0KM7c+1aY=qr zC0GcOqk>ZlAv_2-CljPvK|ukWDXbEcAzmm>EK5xRdjl?&oROH9o|wRBq_j6FV0UZDNig)bxcpq zE3s8_&&(|Vhn0c`IHELB-BO;B3W@~-BV%0y6J5*z(}#G`MjupWz|?LT+)F`J&1vzQiqlp8W*&*LP25F5|YAqG`L2Ci=+@BN%3gv8VxRz zLVzU2qp6E(!NrB>PNn9h*eaDP+1sUxvDh&%Flh()gt&4GbcjlIJG#tIO5K=VywSU2 zj*NCstbc7&Zdqeaxs-GpuV8y&;rhgs4K@yQ1;x57ZDucCa_;TBFaQ4i`~Uy{v!CxK zJlU$}F=@l3yuarYew|Iw)M(}xZvXf5?W@G;yDu!!%_G>ccHQM!vrh$vF28l_Rd4^1)cke!snaFY zx_JcKb7HCr3f4`Ya{S@LcYpr;v3H)&E7)=B^rIg?epgoS^}JC&)+RuZp+|)PW*ulkRe=q0%yjYOBU}@i@Ez(-O2M*jheE8nKe}8}f{;i}oDLHNZuU~)k zjHVqv^5FOHKdBiT4j;L%Ak)zw-FLWZ!rI(^KeH|!w~5{XiwcUi`2;LglIghDIqCo0 zDZgh=&T{MF74G=-=i9msS9+s*Ur(GQB+_x?=1WUw@&YGkH~Z|L<9o zRb)HXti2?o*zaZBwLZ6>N1)@!k6*j@+;;a~oZ-^_YQm(yb0+_sIax}ggGZ<%uzr4P z&!MK)1F;Efl{F?AsCL$D-n8h&!39tEORDrN-Ei*BuTQUjd{oz+lD~dM-^0z~D&3nl zUyBV)_hDdQU`+CMcVXyYmGxj?VBjq9h%9Dc;5!1sj8nDwq!}0(*h@TpUD=R*Pg8fYgfIxa8c>-t_LluZeRGg*R;&|t1kUHW>>4Nh~5N|Fy8kORnpcG zdutMVl(TheTX|(B=}%x2nX}P1#Qi|QWP^jnR_RR3gqwCVXuUqp*r&32OC{48A*Ox& zY91D!nZ2)nsGO-CV`i(fLy*;;X~ce4m>s9C-ps6iGz{L zaa+RtNls1k_N>u9=$<%B@r2*}6wcxsUqWtt@q1pW{deo%qBTlKSy?rdBxaQT>?q+b u>E^IV(fkwmgH6aOmhIM4`494+nYFf8Pcpe*t-!#*z~JfX=d#Wzp$PzDMzmr8 delta 565 zcmeyO{*7aTG82>SW-bAJrg|;Csfwn3iY9$#mNQK)X9|dRCCzzql7WGNv%n*=n1O-s z2naJy)#j6CU|_uO>EamT(V3jEKsq5MDJ_we*-W8-E;EP4PKh0kChYwB^7gZe7#$T8 z1p^HY1lT{Rsx&%y&2nUO@jlhXar&gT!t8Y`RG3&QOc)wBb!qwRomIbU14kmS(jlo? z0$-zUDJdPv5D>h0k#*(u%i@Y*%^i#$&5B2C54jyvTb7W}w&dU)CKjEwYZSY3awH|* zygBH@?ymkMpgFUFF*aR%gHbpeo4LX1yUeCDE}9w`R5ZQ)@G0#B2mtSMh zpeW$OE_mgD08g02Apt2imWP7%Ev7C_rY?pq#~B=RWDs(qLuJjVxgC0 zj-x5Fle2GbY%OzJo0~vJ*0siE^Tk@)u5J7F?OUIp32*F!CpUP#Yiz4Hc=x`oSGaJ! z-~Vt+Cl3ej^>X@yD+)HnV$7S`ZeDhjdUodM!o+)jSOeZ4 zbk2OZ=&1KI9oNraHa%6?ko0ub)ej7AN1Xr93ac^fUCCJQb2;$vtx|^#bBZsako{?s+3>g&E0n2!x{=q3=DD)9-d_p T?#*IgU|{fc^>bP0l+XkKMQPtX diff --git a/android/res/drawable-mdpi/logo.png b/android/res/drawable-mdpi/logo.png index 90abfc81fc9202526dc50e86e2c1d3128301ab77..72c00eef21ee144f7dee54d57a3506faf9be1b63 100644 GIT binary patch literal 4701 zcmeAS@N?(olHy`uVBq!ia0y~yU@!+^4rT@hhWV^YB@7GUY>?h?J}hi_&~?VOj5P_O?d5nE~ql9=^TnDlJ&PP3bvR5q0vHt$({?%-zLLQ&PT* zTw8s?>zVA{&JU+sH1gB><>K`J!h*|@78xG<0a0$);=Ax+_FhUY$FS&lE{?+H&(NxEw7eDcwDYKblm>U zFV!N=CtA%DlNm!ZCdAtRkpJ`UnwF1mmFGj23tt~u$S-#Iu6JRL^||jGADj3$JTNX; zy8Z7}HiO@uReRUE_BZ^?eeh`4TFvSMPx%bKq?}mx#>;V*(?W(l^Dmk|Z8#Xcp#1FX zwOjiyvnx2qF3oFbc1~F1ar3p^`*$zjyuEL{_Fo;te$S$#EXEgRF);8}WQIhPMELqx zCFkerC8p#jrRr7W7BGN-jeSKyVsdtBi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0 zJ*tXQgRA^PlB=?lEmM^2?YL|ztSWK~a#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SK zvTcz9|8 z>y;bpKhp88yV>qrKIT=SLT%@R_NvxD}#)HnBkIIoLrPyP?DLSrvNfD zF)6>a#8yd(OF=;aYIsF%fv+#z_`G6hC?x0S>Q^Kd=o{)8=;!9@BkL$GaV;ysucJ7m zvLIDID784hv?w{%wJbHSL>bwLlw`R6g3_WKuy<0D^;2_Fb5rw5iuDck3{k9f_w)^b z>i`8odS-3`SXV`E0gB3!%rrz`q4)+Q1NIKGLn?9$;O3&JhWQ<=7#v1cF8Rr&Am@3y z*eZeSw@S%R&P=faGmVl`6U_|GOm&kC%*}L7QjAS?lakFWbkh=(O-)QK&5R9F%#e)o z%quQQ%u7xM8C8*6pqH7MVr7wHoMK>Mn53I(kY=cBVrpQjn`mTUs%x5slI^r0JSiSeWQ0nwg~OTBaEp8(A78 zC7TRm#jwOi$G>$V&%HfCAjgF~HMS$w`2X13hCCunZ_# zto(~IQ}ap^LFv?1$q+1Fky~KpT$Gwvl3x^(pPyr^1agvsk)9#MIUs4@)RM%M#F9jp z#FA87B_jhPV=Ds_D?^JALlY|#3o8>-Z381K10?nNDVb@NgjCysGKUQ$$~`iROY(~< z!9tK66`WcK;X$}LnIP2)3JTy%VU?H+@j`K8S!xQ{8*r)QjKsY3)D&AKXmW*V$;6Tr zQVdOv(=3gRbxl%|Om$6C%#Cy{%oELY%?(oxQ&SAh6DKY9$l0twa z#iOZY=uV~PrPwN!E7{w*%+bEUz`(FBz$e6&TcE={s^3$$)6;9RPxaK8DYH{- zyHWyMQc~w-u2`7eyr?3nsnV;fvT}Ld$<>|t^_{hoyF$9Vx;9LBvUU3T-7{i)X3X5V zG_`l>vIEQJ)GyzDc+Kk*8*_R$ZalGjM%kX?zCC-+AFJ#?cI?{m-Bl-U-8}K>*7^Dg z=g;4{(K_MAjYs#lEZ1$J>|vlU}}j_hHK94Yh7ML)4+aJX&H|6fVg?4jBOuH;Rhv(mfq{X& z#M9T6{W*&ekAZG>x>`}wE zOaK2Lw^@FnW!k4gOWyas&M%qY6kKE2@Z?+4W|cxsKDj%`MH*yzGY`E{+wSon5{|cDr@Lr%ij; ze3LFVjAogu`SR8Vx3nxx>19)*iZ_UD2)fpCsM6qBL#OMS@H0%uWT)(%lqRY+w`dW^ zsa4EtJD1^kISDaJY;&5``G+F@AoWo*Im5Xj6W^iJa>KRijv#e zvGq}_idTN*y4k+%h#3r|7D}L3%;{?a*E;1IjhOHXT=JB zEtiO#e)V5f<;7Q(&l8-|?!-LXvp>4vrTDYRxykQmEH8Y1d24K`-okVX-FNoZ8^tZ6 j_CJ}C^0OlH6Z>cGqR{-a>C=r8K)rTPS3j3^P6TS%Y*S`wy!Fv(8*rTy=Br?A7T#7cL%*P`#8X`bx~`W9OkW z8xvo>Y0kJZv(i}adds_;63g$~8Lc^6UwGM2@5h;=SD!{4{(JYC&f&F_ON;MXiB)gk zzuxLg@w>8?$>PTXwBpZmh5Jp=ka6klk|( zmPbd12Tj|omi+7J(~N&d-iEwseCyg)%=!K1l;{&L+`e0zr(LLIIyhr}ly(0JH3kL- O22WQuKbLh*2~7aJM$L@? diff --git a/android/res/drawable-mdpi/logo_port.png b/android/res/drawable-mdpi/logo_port.png index 90abfc81fc9202526dc50e86e2c1d3128301ab77..7147865fb05cf7cf24a8139db367907e57576ffe 100644 GIT binary patch literal 4696 zcmeAS@N?(olHy`uVBq!ia0y~yU@!+^4rT@hhWV^YB@7Gwk%)cVwJtcacfil$1OjcAg8 zk!!0T%=-5&bqc@d{0`;HB@>rV{2-#id+U#w*!HS_hju*_HDk6~`_fA7(EZ)lZrn@m zEN`h47UB`RXxZ>ZV1igD+vEt7OD95Wu6X+iasS-DT=oNFpkw6258dCFGB7ZM}a>Kg{6f-B@>1IPZx8Ubmm#u(Rbl31`P&_W=xowdd4$|Yv1G7a*X@#9j%!5f2Y0s{8`&21Oz%CpE1aN z#QSKb@9hVB{AChVBUSCx<;-6;9qEq=x2by^Qxf@Lrl9fUdFp)v-ADPttY%F0>8+S$ zu`0{zDR*Rl=(*@QTfKU>zB?H&aqhMD>6qn~O)6p=SvZwMt^~NTnk8*{wIssha^0ch z_HTZv7HK}wYMz+P7@9F5*8Ye5pLf@^e0-}sAF^Ed`p80lvBP)03u~;;ec$-l#J}Nz zalz8+=edD$N>KOKW79C|VzA%e{fwv+vB%&n3 z*T*V3KUXg?B|j-uuOhdA0R(L9D+&^mvr|hHl2X$%^K6yg@7}MZkeOnu6mIHk;9KCF znvv;IRg@ZBspanW~5}trC?K(l4cd;;s!OMC?(BSDWjyMz)D}g zyu4hm+*mKaC|%#s($Z4jz)0W7NVg~@O}Dr*uOzWTH?LS3WCX+vm(=3qqRfJl%=|nB zkeP`|`K2YcN=jS`3JOreD{>2bec{IE6+=TIIX_pwBC$ZVR8Vx_yM zZvb2eCAp#4b};8u0Azrl62bCExAKU0-gb74}3!;#* z<5GZ#1-ZD{aoOmDOFB@g2Qd&->d+EH=4C?}XLR`58I?SW`J#{-hy(arqPmP%} zJH@suC9owWbzbI*h1t!EDw3Kiy}BwZm)D(K-I-tCSv$Eaq`RwY!-OYWr=Qh~|6Q6FK zub*)K{GA)E6K>pibbnjUgVW0&c1?Wv@WrDmrH_BSec3x0c6VXuV3qY?U|`@Z@Q5sCVBk9f!i-b3`J@>b z7}!fZeO=j~vk37Rvb|G$yq1B1(c9C-F~sBe+iS7e%?2WD51i*XFSNRqJE_+#Wy7?a zC$rYh{`tQ?N8OO|^TKJdrANQ3&3JP0naWnyh@x6W&eV+_9pe0!&s?|msHOO@x*o3l ztug6Q>c3u1Mz4!S=VmK?*tF*8LC*G~$B90CzEL6_si(lk@kg?WGbuuu3>-1GLviRx5?rwyk(PZszo=CfYz+v4Z> z^pjnh%$0LYcP$t9o!uFcza-M;aj{&TLA&JfJGsYC5B#c4kNaNp zZkK=3t_YbuQFHx^Z*NsTu`4fqSICxMKcb#T?cADhH}lF?$v;Q0a{iE&X}$d`_Us+= zp0e+|KWAoN{j7K7g3hPC(Rw%cXBVuGpK+)vcyIE&Wpm%(`?Nbo_uaJE74>u4pQrXG fo}NEl^}oUX50d#UdszRvgZk^9u6{1-oD!Mv0i`uCnLjv*eM zZ!gDkHyMbqC2Zu_HNiviSHlOrT`M-|eEav`ep=owo7;YV-3LDGwK9_bUdq$7P?dQ? zi01t7{4*B?xf=FtU8->TS%Y*S`wy!Fv(8*rTy=Br?A7T#7cL%*P`#8X`bx~`W9OkW z8xvo>Y0kJZv(i}adds_;63g$~8Lc^6UwGM2@5h;=SD!{4{(JYC&f&F_ON;MXiB)gk zzuxLg@w>8?$>PTXwBpZmh5Jp=ka6klk|( zmPbd12Tj|omi+7J(~N&d-iEwseCyg)%=!K1l;{&L+`e0zr(LLIIyhr}ly(0JH3kL- O22WQuKbLh*2~7aEug!`8 diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png index 82d813464caf62b1bda0b4bc3f8176dfba6cf660..6190243e43e9b5a4145aec48a1e036cbfb3614c2 100644 GIT binary patch literal 5636 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4rT@hhO2JvAc-SYArU1(iRB6fMfqu&IjIUI zl?AB^sTG-N3=9>waszJj9#-J_^Ib%i(Ow}<=F+kt^A|sC4|Go2re0VlW7U&cy?aM%qdKv~BsS z6~bNr->9p7i@%D8Tw=&!k5n!hwzWsYSLr|3UlYCS(DQ|U**p3=FXiWQZ~K0t!rj#@ z)i)t@Hgkr(*R2T>Gp-g+bf0(e_1s$Zh!gv5-!9;H&W}8^yH5Nz0|R4Arn7T^r?WH6 zKMV{Nb808rdK`9;IU0YtOG(txy2B!6g`b$Ct4y|NSAgObuC-P(Vt%puN=*_GcR#rG z!Gq&TR}UVI3}+8_6!^nXSXvlVGEwONbdjhd6$N+t%fHK&zh_t&m^e$ci+NVw?3XD{ zXP$)}eHY$h&|t7=#)O%vXFP+r_C0(Sv=84ISp&1im?SIJsd3R0A$G6J!Au*Uk__l=KD{2Lw^7cAZW z_bQvgZ_lc|YhC*r{^dS+v}>(q^?|2+247N6EPLbSILm1v!=CvU&7U?Lj9yTF_VwDW z{g>GloMV^fH8eXXtns+{+V1_kmv7$QH(vX%j$yxN(NPxT3$qv)cq=kPB1$5BeXNr6 zbM+Ea@{>~aDsl@LK)}Ynq98FjJGDe1DK$Ma&sORE?)^#%nJKnP;ikR@z6H*y8JQkc zMXAA6ej&+K*~ykEO7?bKHWgMCxdpkYC5Z|ZxjA{oRu#5NU~{eVimgDx`br95B_-Lm zN)f&R3eNdOsR|}~CVB?Ct`(VOMoM;E3N}S4X;wilZcrnNQqpXdGD=Dctn~HE%ggo3 zjrH=2()A53EiLs8jP#9+bc<5bbc-wVN)jt{^NN*0MnKGPNi9w;$}A|!%+FH*nVFcB zUs__Tq{OA5pa3jTz^4nQ4ZKUDarb&IjOm+c_qdAhI)o5R=Ruo2EcWI zf*?IJw*aiGBDVlVWl3flBCt?=1CjxI2iYMNxdm`@QB=eH4ps~fBP*Bu?zsRo7?Cgv8p7HNh_x+W>67P^Uv=4QG{sTL;5iI#?GhDIhxMtSBH zmn7yTr-F>C$Su&z%uKN|HBU`7NJ>lAO-f5n(KRtNx71BaHB8nuO-Z&)G%!j{voJP9 zGQz(oGd(jeF$dXIAfr+;Q>=_lEz?X>jFNQCjLa-_P0S2UbrTIN4Rt}zNU|`uNH(xY z1RIr-Y~_|;l$%&$tCX3Wn4YR%ke3dY00p>}V}Pfvl98SPLL?w3u_P_ODA!iWCo`|K z0wEESnVTA1k_ZY-Lo*9Q3nL>VLqh{o3v+Xbx54Ixr4|)u=I4RTG&ImNhRA@T#mc`Z zGc~Uy5tL4CmEgX#axO|uEXgkl$6k8=|a)oKh#F7-!l1)=A zQVooB(+n)lbxkbNjCB(e(@b;?Et3pQ(~^yo3{%idFV0UZDNig)bxcpqE3s8_&&(|V zhn0c`IHELB-BO;B3W@~-BV%0y6J11r8A1b0AL2zDeNdSJ^RbOSMwmbpAQdupTnZ4e zAQv|~E*pJtNe3$RAO?a;9a>^&T+q@A1%**dNDAN4;2I4sl0twa#iOZfG`L6#0g@Dt zrY@=l7Z;*Cm714gt5mLJZ?|q!!Ey!$W`+Qt5LZx-i@}(IA&r4yCIiD628RD2p0P25 zaTy<)0p9mF~fh5J;uhy z#%adJGmVYU7#sfwS(|2ToR(&sHq$umjB(n3kSk^y8_!HLo;lNa<{9Ie|3QvAV{CjT z&G^hr<1=TB&-^$3&tUxD*!X{%@&B2||IZlz|DOi(l5rZy(KFLP&iW7X0m!DbG>}A#D5yXdr_BTz zeP(9be^AJQq|;`Cz?qpd|APYZj4{aCGc#wNIWzOj|C#?8X8t#x`9E#u|CuxYpPBjp z{~1su8J__~!^|_Ffc_5(bx_!)fwY2x<3A`8(u~i5yfpI+$kG2naWc~wHmL_pZ@>f{usZhrC3XFuOvp3keqz`($mO>_%)r2R1cVu21sKW7o*G2-66_snGm2Bu}6E{-7?_ukHQEShZ~(q{X0VvR(YZ72T(nI)_B zswen`PdpGXaSBJl+?G=fGjAL_s>yV68pouC!b1Nf&o>DE`tfz%ycFY~Q-qzW{`h#D zpY^1c`2$cSW{P)iK z>K2RNL2~yQ9^~jqZ)el_^WesAt_Lf8n{G1}aIKiaz^ZgEnxTjH0xLtp!f?io%z6wB z0{fUA{JPG-AZ&h)x%qWGJA>1e2Mn_xBr$kcFvw4tlK<*TLnQM9_I&9Sod)TRJ-L#{ zB#%rjVq~aXx?tj_a<-s`qc$}_AJ_kPDh_4X|KhaF4u6piU#~ETg}+{EIKw`y zDvXImU0Q!;^9r^zp$rF4J`0gzK6Y>}(}SFqldOLHb@*NX>#|hBt>^psQw(>+2Rd9a z>-M=A=4i*iXVQjGw;1LaoNcjgZHsDIe79`(Eajg*ZYA-NIfmk!}nX3qClT zSp8s#G&%QV`_yHgE6)ctG?yFvk$CXY_STDp5`%sI ea>)fd|1o`g;J=2e;MhnNZe*#Q~FQe(mmCTrcyS z&F*l|qn#q$ecmTR|CMfh7S`{iSLL)Oc5SR)?8>?80`?!@SZ5!r{d&6S>z7rLi`OkW ztX03}aMc~#x9iNpXJ^~3uZnHj@$K^CZ-u{~m#xkhkGTG?uzS7S_T6{$_R4jw3(@K5 zJlu9C;cbD$HIp}d>a{lZImcd~JzSys{=2hY{f5t%>+04Vd7!t?Z~py)o2T;xg0GlK zhZ>$>)SIw=_Q{o$XK*yWjZWxo9m|Nr{Rn=Rk_e=m-&+_A=F$BN5STrc*l zn7%vn&t>EGx&^GRygVNtm1Z`^nb+Itty{XhX|slQcIw~mhew+}zFuLLlhXCNpFwL` zV%!J$N1PAq@8?`9to*8BCwS&a!zGquu`+(?{nLB({zk3cai1qLF#GXQre;4*!Daoo zw{JdOzM5mn;td{>H}=Z^|I)LublUpI&i7wMMmqj;K6a>UwchG?@!LNgwH8lobY3AP zk}xsmSKXh>?;P30ze{v8Ww42@Y)(1B9V@0=arM2$!9=-*=_U216Z+~5x6JvxNGvDM z%EHBSLI9w@nCCv7~ z&us@D7_6V*x1!2d*xToR`gLF739e0tuLtT(xFGJMU$P;5)(1z$Gh5QiF3uE<5AhD^ zPGjIJJ9m3>Pt{k}^Y=rvMCzAc)4d?d-5sJN|MAAD;_cBqd2S&I4;T&p?u*XWn6@Tr z>FJ&+QJu|K-xM8ma15z+S;3{08@s>wYgCitqXdiDUw^J+|wQ~;Td9~xutIP{4xR0_(H5)ssrV1u;1=lP%!Je^U*6M`%V+EBl zOdHQFJb6`?-`+84m1|sX*PGpG|yNZZuV8a=lhGOMSV}^u@i4Ypr(0%(v*x&{wkv3E2TuS=% zS-XB%lywzkpJIOHuEN*4z-JmYFI?9*-}`YyqwZ@>K=W0HC8f_q zUrw^C-_EHg+Gk#CIVbCj&k~MDSrWS3S5KR&`%k=Z@V%;!1dE7ZRYm!%M7deF|NeXa z?|E4K_qncX44DfPHnPdp&QiGNt+KlQiMjU5C(H#CxZ7OU@SdH?qMl-|(7KC(A%N%Dj^v$aWkzJzUQ=MgCRNtdE|4zp{7VfA!hv zutG(kgl2*+(|OjH3T+8Y(UT(@E+o!%7u#00@kryJYu;kJ_FY%8x))x|W8~WOY1W=; zt;_bM77MbjF^)Q4TGS@36l(cpUh9+ivks<{{+P~UExL1lkNsP(;@{Wn{$D@;x1RNB zuIZGh4MyU8jp4d!^_!y_WLAm22Q*bOQs623zpD z(@ZaywmmDdY<&2Hvx~KSOW*1ixpRyH8x$s&vi_VBuGjjav%^VKtvKYF z<*S`Rz6Ye{EMWWSS7&>?;YghAzYE9Ti$cl}K}eAwG_riVi6thjb} z&zj9IFCMaakcQZ|9iK6?Z>8uWJ#OTl(zj>12*I3|(B3TTXPg z+?nn>>j+cOiNh7;?y@>3A2V=YNP2aM@!!h#+8ee6^*b+}ut<013Fd2yPS|+2TG;X4 zEO~aOsk!dCM8dRuS!0bQCkzx;CHKedQCylbZF>D>k$C;rS+Q#_eTkKMlAzNhyImld zXZ^utg%2jOtzL9iJ^t+h8ObM^+c#Ze$ztN}YuDscNh*Ev|3K66Rg4oNBvytrENa=Q z)p1cO_UEa&`{fpG{kevt;b8ai#wx3oK?mCMwZnpRgYB<=sXDpHviT=GPWc< z%DTUqY**Rr0i(V-!> zci11yGWyK;HA>lX@iF(XRKwNrq8i?AN7vi`7dmOP_Xgj^E&s!=%(&O$J2QCZ2F1%O z!nOJm9cOLdu}f24CWFyiFIq-gR$DPO)^zk_`usOJ%H0z6Up32b{M*lZCVe5B zzHZ0C6vizYDc63!3SPfN;>)a%H9yRCi+b0_EfEd-%a~faW!I|3G1W2$)XIdfEl)Mu z?eok>>8&;Q4#tx*-uk5y3pX^qbid4b^uVW-6q|*5{b6w%PCwu}+VuF4>$~qv(aqQA zt|(MZbu#B=e$4wi@^wc<&xiU|VV~d6WIgU|8oS|>>W22MO9OmMxl-nydV8wpkU#g8 zq)C@H&D$iVXFC6Q``yDoY);*G?!ElyZb%Tbk;wVPipp#C6)V>yK9tb6G2#vhy8C2{ zq)N#n2DC^-oKCEhYJFmcTi~71uZ=vvecHcW4IG=P*at32>Wy>b+g%lv;DH`sK4nC3y!= zeP>>A=H&NPz24`RwX!~1lKIwTYGeO4`8~0I9~M=2$@wteYRgj>oP2GYO^{-D!2i1* zQY{m+mVUh+l5$MCJbbQO^H+y^TI%TwR8MD2y8I>X?5Wj}cO?UMgw4_RPK`3-?mOZT z%<|ITPrN>CLs+-*x21Z&I{uV6i>+LFv(}$Mu<%;z-opo~cG#%eviNMrQdu!lvfP z24Ss3;yZ3m+M}N$rP*|_+2Vw5f@{w&1{H0a+b61?E#jM^-uq}v?WydCNqOHi+3s27 z6{R$Nl46&3z4QCf@$$3vUVLYcusDSuu%3B`)2UfY=T%^BG5?}%clJN26E{zvTq^YO zZtaY^G~<&lkA*LbbQf(uP;PN-&KmjTi;FuB9QK(p(IoYF#S2TR{DX{g7`xdEyhJaQ9M+K;NaZma*HwJ!DQ zQNOEOwzJJLWi42&;u|XQy>iyA>v8|Tuugm*y|e81foBhwG}~1D;hieo6c+@Uf zn5Y8%%iI4Oyo=%exU8kMbPMkT3;Q)KzIrb6cvijNw#aN^4a4oX)AZk&MReS_@k03k z_mVj&J57Fm5IYgK;Wl6KLcWE1n|2ASHq@6-F`F+sZ*KmB$L^ChW^fqyM~R2*3E&KV z!r0L!d2G>11>d~JjUDd6D?@CpHsv2)vQy>BJ|&}-VeNZ_J3=?F>K769moAy~);4nO z2~D5Hnwoni&%aUpi|+nCm~r(1yQeO;K@+#6!+b55^mKd?NbHu~${ z|7yFk>zlk!Ea|;*EYIdR-oZQ_fl2{!)^D(o3@8AE@^s`b!^hL-OG};_-*@sW7Z0zo3l3`Z9201 zWkgqG0I%R>j^F+&8SCe~ellIX`NZztbp;mlvjeyvh4e-iJxZ1g*Zf}C)WERfx}vQ} zN(a;KyjzdH?_IV0?=f>LnQSeNvZbrT*>VhwEK*J#{#KE7E{6Z{7H_^=S#}CH3v1_@ zUZ^lM{mhe;&EK6ni+%Na?#(G*tg9rq+eNl4{m*EyYGIA{2bvW$s_Kc%d`IEb*`<}fNShO_3{8;ej>*sCc*&eSFTRM57 zU2Md9C&BO6<(2<)tX-UO@R{4uUV-)lc>$~YgGwy>H=N^CE0M|CvMc+&#-00{ZhYI8 z@?YVYY)=Bui42Cf4FS3W$!$@J-y;Hggg2EZasS-DobwmcgH2H$>zKu485kH_GM$|R zJe{3kK4M^~m{U8^*5j~)%+dJET}q;k)*TipEBwS9U1hRGy8;xiaILkP5%Y`HS89@o zxckAa4;~y(x_awa~weRt3ImUhWj#ff;2gU&uc6sFVU5Sl*LLsUy?pcbzVX_BbqxDGi;l7wUzo+fz*~_S5>XQ2 z>tmIipR1RclAn~SSCL!500K7l6$OdO*{LN8NvY|XdA3ULckfqH$V{%1*XSQL?w=vZ=7D$SufCElE_U$j!+swyLmI0-I}l#h zD=EpgRf_NpP;kyKN>wn?Gto29b*;!OGg7kSQm`pXNwW%aaf2FBl#*tvlu=SrV5P5L zUS6(OZmgGIl&)`RX=$l%V5Dzkq+67drdwQ@SCUwvn^&w1G6G_TOKNd)QD#9&W`3Rm z$jro~{L&IzB_%Ee1qG<#6}bhzzHsC7ilL#9oS&;-kyxN_sAr&`o2!qkqqxMitOUP~ z;*iRMRQ;gT;{4L0onH8nFc)HN|lPSv$AGECMrv`9=bFf=nyN;0xQ zGRiZrxFj(zITd77MQ(v!W@d_&Nur5~iJ@t-uBB;;k*)~zDka&GdD3kRlguF9V`I~a4W|EPg^A;Jp+VDKu%&wT7FTk zt&&e>UTFnFA|x|6HMk@Z6q<%+7KRo^MuwK=24?04=1983Qj3Z+^YcJv8XD*sn}D^0 zqQ%O;C^I#$BoUNOZI$4@v~n&=O)SYT3dzsUu~h;&Nx?|Z5Mp3OZh@6=YDr>BVo9P) zVo9p4l97Rtv6X>|m7zt5p^24=rIo3fwtUX(k3{X%=aQ#>t7MsHPX^r_2rzSKfaycLXrm7*GhjZp(Z>iAhytWS z#*Rw?A{ON0X2)ft4=(9Ir5?mUP^m*p42=s~TA`pYY6(flo+YX>FfgnO@Ck9{7U(dK>i5*`^z@qSQ$00i z%Ip-|u9U!*l+<~dD;8!qFRDmts`TortXy7qa&>2ZeP`|Du8{7ot_>5OY@L37_l(${ z88de-P3>K}?7*@)^~<*(Ui13I#+=@b8&B+>QMRYJZ_l3d$13}e9lLgXch!koH&1-J zb-sSW`SW*fv`)BjeV8)&!-p?FW={U` zi(^Q}y|*_Ki)SlHuorMypMSGxcZ&Gqbz9Sm z?ym8@yR6Nu=D)t#Y*9xk#@2Ub*Ps7CH|CQ;fZ--M;+7x$jos>0Jsi6ty7P8Vm^`g>(+b$m8mHIH z53%~JQF!`fOkz!RSlg++7o=<^uD<@v+_N>BGtW9VB}~(8vSrEPt#kjHEZ=fUS^ABW z;O#$l-=Fj@?5KV8bIuy|8k4H%MIVcH$3Bvr-ClX;&0em81C>wS)qTA4a_4Ki8;#vZ z*`-hSmxx$AXkg~Ec+l|s#2uw)kr}s_iu%7iKC^W@6w`F>wJ zPMJ=cHZdxM@7XtQ@!7lo%uW6q;dp0v7+>nD`J2qd%(q6=$wbvY{Zvrc`;y0Tj)3g2L;?s{4W$6?>Odr5b;j!P#-r^tUea<|Le#+%{vsAYB zqstCebKkQoyKn4E+&g(w-&w|Ir#@6ZKObuT^g`LZ&Yrz9tlm$btNhju&b);Mw>9P%6tA5n z5%cVP@2=wM*Uin%U!U>R@6Pj^&Ao7r&9$?c+tVbLuM1h3FZzYE?}1y_GW`_BIp5AX z`hL#OF1#1BxOTSDnwinFpB;m;KG#awBKOTLr;%&f+tMmdKI;Vst E0B`19ZvX%Q diff --git a/android/res/drawable-xhdpi/logo_port.png b/android/res/drawable-xhdpi/logo_port.png index ceb8f63c56cdde86e5c3e789e7b3b23d831cc461..aaa98762d79ce46f1b561f096a087f57a753351d 100644 GIT binary patch literal 4986 zcmeAS@N?(olHy`uVBq!ia0y~yV8{bu4rT@hhL5e+PBJhs9IXn8C<#g|S12gTPs_|n zRVb+}NL5I!$V_8ksJN9I;3#xhfn(3-E<4AFqz{(MeA9n5+wEU5!}Hd=9_>wMxGwyw zvwe9@Va<}$Sv#N1k%`*k@%6wZhqq@ut;?U-n(TY_Qs7AN2kFO|mao$1@i!NS_D`EU z@vifYbxs#QY}a4>gJZ4o#|MILsyzzM4-8hAx@m5k=YHcEi?A7=>8(Rs%cs=c-}UR8 zT=#$GiLyOUcwS^Myln`u4M=aRRQw(hXyskA2>${Wj66aoPpN?5>*`y-2k%d!9PS4$#1F4rA8 zZvW<&YLVs>t>%fzjG-A5V(ov(|9N*!%g49M^C8QHua7L`7dw2{yRgRk-1m)-P5c`k z7#A$v{`V@I!Eev1y=z_j8~)`!c(iM+X7z!md;|sGG7#8IXksPAt^OIGtXA({qFrr3YjUkO5vuy2EGN( zsTr9bRYj@6RemAKRoTgwDN6QsTs9R}6}bhusU?XD6}dTi#a0!zN?>!X@`|lM!um=I zU?nBlwn`Dc0SeCfMX3rVdM0`Xx~>(OWkyPNTnaWtDQQ+gE^bgGic->Sl`=|73as?? z%gf94%8m8%i_-NCEiEne4UF`SjC6}q(sYX}^GXscbn}XpK}JB#a7isrF3Kz@$;{7F z0GXMXlwVq6tE9xGpr8OXydt;2*B5SlUNJNjlJj%*D-sLz4fPE4b942PbrhGlmX+Yw zQ5;fPkg6Y)TAW{6l$`2XmYP?hjBG?oGF*Q_X;BW?J1NQfsX3{+sd**E`i6RjC|0_A z`Ub#tfPx@BGq(V&t0K1mMP*558X~Yzd;^jJdk5Je6}bg)b5T^o{0>$O4kIg<{Nz%Q z^E_Q_l|c4erQ|1PrdWZQW=0m4i7CeBx)vsusk$b~W~sWC2FYf+#)+xsiI!=WNlD3R zNJe?)6_+IDC8vUns>m(S%gju%GBPtUOingS(ls_PO3^h*HA&L7OiMM?wMa=ZGfqoM zOfpV2K{CR>C^J1XFEIz%RUo5MGE=O~P0h><6AjXIjg68MbxkbHjdd-OlT&m}49$}) z(o9TE6Ae?qMx`WMx#bt-CYIPLW#%TPr|K8vrGq6v0dD0O;AyL5q-TH-3CKw-Ny{(F zwN>)T%qy)xNQ7kOrUsWJfUQ(GlNu#Sq{0xRdD)WnkfqLBRj99t!jlN5~f3?a?|N&BXjB&H;m zB)TM)q}nPO85kK`8JJiZT7(#ySeaN_nHXss7+D!0sn1WzOsgcM+76UCY#>qYky%`l zUsMSeg5;>+)Itak!p+G9sa8->0A~uT#AJvUiWAFHAt4NxO3p~kOHWO)Re~m0n3hZ| zNg*jI(byu{B3U=h*eqGs#LV1WHz_gIQa3Rc&5J6Mnkv1zDl3=Qom}0SU*B0fxhtf*t82rACtIhV-#sI? zXU5E(OH+H7E<3PnPW|%jhu6G5u`#E22 zZk?~6aQ^(A8?6&=+<0_i%O7@4eE9IiqbsG4f4qI!Kk4PmcORxq{_x?;kC~Hy z{P^|ve8Ruy8~)Fo^8f$;sSgfqXJBApO!9VjVd!9$^b| z85kJYOFVsD*`Kor@fd2|`PuKsz`&I2>EalYaqsQTw|TP-MA#CpuUnMT)1Mmi|HF>S z3*TS&|9aK#{G=1hnRTSnrdFK)=9pHg`{HH)nWvLuYfJakEI!5LKGALIj$?DJmvh8! zaY$m`Y5byj^|S)RS;96-@TEJ{KOcOcVBpgjOOvb9(n;{_jmR^ZYUK!vhw)q zNq@5Y(hl7#om{xKdDS-E{GuzLS4g`rzEq+by_}52_d>0!?>VeXDR_6>^I54xy3N(H9XA&~^ef>=w>a~4=hQ`W z!llpaA z^Z72%na=sf)vsRg_Md%Qo*diMargR9AvQbZ19u`8z0BJDa{a}IyEPK#KRz_b6bTpY zPdSr6T`YR0piJOBe#ZEe!Zw+r6N>FJMJN6qkNKQ@{!m4H%r(tp1s|NJ@ElAqklXn4B%xvXv1#dL{u+7srr{ z#KC^W@6w`F>wJ zPMJ=cHZdxM@7XtQ@!7lo%uW6q;dp0v7+>nD`J2qd%(q6=$wbvY{Zvrc`;y0Tj)3g2L;?s{4W$6?>Odr5b;j!P#-r^tUea<|Le#+%{vsAYB zqstCebKkQoyKn4E+&g(w-&w|Ir#@6ZKObuT^g`LZ&Yrz9tlm$btNhju&b);Mw>9P%6tA5n z5%cVP@2=wM*Uin%U!U>R@6Pj^&Ao7r&9$?c+tVbLuM1h3FZzYE?}1y_GW`_BIp5AX z`hL#OF1#1BxOTSDnwinFpB;m;KG#awBKOTLr;%&f+tMmdKI;Vst E0EZ=AcmMzZ diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png index bf643d445168bd57a147f99c6f644943640ab51c..e86054c7f5798681ee1362474bc1facb0f4fd4dc 100644 GIT binary patch literal 6570 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H983%h3>hk=?->{vj#Y(3lmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNTr9a1=VMz_I6ZmmMP`uif!2FMaASJTDKO7_asTzpi3aQ0runsQZ-qHeB+u9A30XGb(1Nr1g)8^X*}oyLruDqD?V5*HYLD*U5zBc$ z@nc_C&a5edp}Bku{%T}08y;EGcg&>1ZEd5cPgCok+n4!%G5Gw8yzXSnahidFu_e>l zIl$A|8RjDfhKf106Ky>XJIEZ3zucuH>S*0zk+Q;1%+XaQTeK@c@e0>ks~IuBSbe1? ziHN%&-1^|b@uaHGhdT=VVJIvu3@Vu@^nbcY)RBsUJN@O~<;vePEDTJXCECS2 zD{uD86sI%K!j8TRZ!u^vSTtk8%+xcUL0tPDzm{X%ckgJ$wEsKp-RIBRE+HV$`S^@M z?jznuGktGA*yAsgs2Ztir!Hsyvgt^FOt?+ms?r5eeV0l$0q&_ z4~z?zZvT6g&EU6Z)!wzP{SE(eA3WN%Rsd^Q;1q>iyV_#8_n4FzjqL7rDo|$K>^nUk#C56lsTcvPQUjyF) z=hTc$kE){7;3~h6MhJ1(0FtBTx$+|-gpg^Jvqyke^gTP3i$R(Zu%AYpwa z1+bEmY+I!W-v9;Y{GwC^6Fn0>16|jO%rYY-J1zyAqLehNAQv~N5k)C!wn`Z#B?VUc z`sL;2dgaD?`9nIMXEJ)Q4N-fSWElN&xElbTSQARc*B^j>2ptL9l?46Wk{nVV)+|<01VtqqBLli6B zJ$(bV;PfTFS_GYt_~D82#7fW3q4kc!*_xVb2*VSWcI28WTAOMY@G z$a$VFwn`xTty1!nGgGXF-9!t^G;?D!a}z^L zGbE!t^NLFn^O93RMpfh%=w)W6SS6XJnWrWjr|Oy-m>TJt7@MZ)CR&;q>ROl>q$Vet z8(Nwe8zLFuUzC}inU|P@>?)8^DVZr&29`!=4Q#d=BCDm zDXEqwX=!ODV53r!t=#g9auZ8zl`?Y^(^K^e^3uT)pa8dW4Dhs7GSV|Zhy>&$mZaqu z<=QIwWagDtAS6OEb5ny$5<#J9Xl7w(VPs@vVq|7%WNB!GP!yJ0RGgWg2Qt&pK+hQB z1W>eC`4?rT=9MIZ(y6Ty+?Q6)MX8A;`9&f5`8l>qASWpp=@~+tQ;}O><(pcPn37nM z=#p5HYO7>qU}S7%U}9xx5n^a!Wolq$Vx(hg?KP%Ic28S5ID=wb$#KE#VQ`k*oc=3^Uuj4**HKq_SH zxD+5_K`w4~TsHdPk`7erK@0?yI<&;lxS*vK3JRl^kQBb7!8ICOB!vJ;ibqq|XmF7f z0wgIOO~R;gUc-fknm7AFG(180FpWHAE+-w_aIoT|+y&A`CGUgGKN z%Kn^1h{s6l`;|Mr3=9lxN#5=*4F5rJ!QSPQ85lSNJzX3_DsH{KTOA@CF30}iW8eV} zmW;JwYqu_s%I#jareEvbw$NL-8~-j|o*vt+p&*yB`n22|IdOrbSHp}dCroHRB^{N> z`L^rS;jRz?P3@;4azTApcWF;}ASB-)6vyfLO)Go%@^v$a?pEI@g z|N8Uv-<}@Z-NutOCT%S*&v~KFFR$0{y~rl);Q5ZObBkkHau6Zvsd1=n@MgK1|Uo=$oC_iwY?;*&LnOdIBN{ffJG zt#1DZ*M+9PJhK}s?fu+bpO=+?pYfr~xUqNY!^FE67nfd6|0K)sX5-3~($dra`2zJg z%))l<&dK8#j9PJQt^d?8#exa&2M)m)rMVCU*db=ryn0bFDn$k z&gi^!{rdWklSFPe{H^<&xVP>KQ^G5w%y7#zCgnA(*TTh9bNI|>8wB^W?pyHmDesYF zn{5Z@%{m*tI^SUR#|IBKc$F^Rv?*{>)T{{sJJNQSyp>OlxT!b$ss09`8-2dNA8&uN z*yGJYTTfS4*RA2}R#aF2o;i2l!Gt>!v(9NrylO3fr(+PbPRK6g-JOeKB6H^Mcj(v^ z+~OP3=j^%|AA7bieXyHqWhvYuC z3Aq^c^2cv&`u59kR!`jCTdk1`U%e8$rl)Vs=zLRVT4()4*7cJwGe+;4Zx>1dv} zan}FTOL2=&9N3zCDrh?EZHZZZoMFMmg7sN>}JB<^YAJyJax%cz)bc2H}i-gy4Z#&|5faBhU z3mj@U+;4w+!=iJ?^q@tIk@g773{vB6bD_J4#Mow#dT#I_tq@=bzl)Rmv{> z=2NQZ-lOqs37wr@XKu+@f8!5lF8-{dtu6gAyw2V}$Td4&vmx?__#O61-I=*3I&VyJ z%`agw;8vR%Ze!*3IJ{6+R%(~@a)vWTGrx)KU$J7^G%>BDLe9JQ6>dtLx!nHcqw3#3 z9`W{*Uca7@5Lfr)x8lpWbGMjZ;p5>ku6W&W_JGZfB^i4^98YE__ew{M?6^K$uGne+N7n-)&X zHCVE>a;n30#rheqodATHExxcwko=sbKTu#%@!jp?{AGs3s-FS}P|F1FXEWxYu_igbw zQZsdu?}mn*DpHFK4hCqIb8`s&G^y^2BOm5vw`Dp(0ck-h?BV*4@{oK8m4Zr0*JLl&c8yg*U zYvqZxH?_k()qE2l-@Uc1a?hci_s#2OYdC(tc#Pfe#irN6C!Q!SYTcast>S2|(Mz+5 zN-PI|=c|2E+wu9%-ZgzG`uql*{zYGFihfHu)_mBzXMfZCgL};l3!7>?&v8CGBJIh@ zbASOA>2Q53bgJuLH{WP)Rqnt0t0V2^hraim^8ep559g`UrJSy=>zKs8s7wB8^U}Or zYGhp)ae4pcg)NMsY@9z|oDBX2xd&*6gGaLyFYzYjvU>`k6Csc}DKuYuB#%&HU2C70&p$V&Y_uH5C;bYVG9i zegBuJ5vKMrhLwjYL4bkTfCJPB0ku3B4H%klXx`{yWy)Z6XKGesK$T*UU^xJ4tzhbj a{l~0)K>hu^20kUw0Jx{CpUXO@geCwt1Y=hK literal 3437 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H9Bd2>4A0#j?Ob| z85nrwJzX3_D(1YsTOBYpRgV3````0^Ti(fE)YQl^K!o4!5MNL@GG{rH+|YtEjG`KXt&b845=V^&x7^(TBCo@yz~x&3?JZ}v9Hj`q(s z&kKInru}FxzW2HMp5(qg)1Oz~d(NRY|GTKbBbAM7%Nt5^L=+CSFuBYKXy6d$5=eAn zRPwo!yO()C7te*H8!F0q++})SS9#@}xR+d5Q9ak~VEp_o2Tnh!S>LvXp}IfoqxZg5 zN8e8KSzW%XSL>jB{9BX7-?qIe;hlF!NTJ@|w9Q_q<-!F9p1!ZHIXMPRO*V-}FCRWl z&AO9U9jV5p`!wd3&WUwS-{v{BZ#RD-a;Ey9RTayuxp~3U8u!dr{Gob(@zJ-_mOZh) z+jb#aCTwkQ_Mg40R*8vp`4?-?ezmEl#^%A}$Bp;)HXHt#_~O|0{CjJhqJC+viI~~CNXX9p>$3v>fcYA> zYBr+Hp2Eo&*2i;{SntJqbs`+rKN5yB}7mZDR;z`l3SZn@0b$$1?fP2Eh1&5-! ze!+Pn8Lj5$ldBNp z7VlgBVN0^2BBRG`W7*xo^N;N?yu0H{iWO69yX41@w>Eb4zdxRu-tjuqBf>y)I->UM7EhTm1#L{4{evdI@{jcOB%-eGf z9hBv`54~RB+oEx4Zdmx@iz`0f+uhmmCO4yS_iwfp`8}Nf?;44!1)pa1crGd?*784R z){RGI-+3=w+U|DXcDLJw-A0ZA1`ienoS9$!|MmSe<}WUr7jt@KHIaUrD8d^N<<1fr9VDe zb67*?)vkwytgeik&V?jj*&#dQ&vX5W6AR_atmiBLTwh|vJ5T2RmlKu#LC^Nxk`A0` zyTf#q23J=0Ys2Jg<})@wFwl$Jb#$#+=Dl)uFN3;6d23?7GplsoKT_=|V37PPU`2iT zxh$Ru+qZLFp7;26V#OmDIW?a@OfP?X@3^MQzwM9was%yDX)o`ee=ke*TK#!_c;fdx z*CZCq$gp>D`B9wd5h(KH`TZknrpQ{031`e$RI=cpD`-y~@+G{QWT(h8f1zTvl_1KTFmx-?Bw2U(fPbO#6rM zprnKBtLqz7Hr~JI?poS@@PdJtBGZ8bjeBQwGdUcaXd@zewP2;|kK22VWCBAulZeov<eo!7AKD zUW~gAt9AT1nauDfdgF!z?@w-O67rR8_*QKzA-K%BV1s<|+)bOb7(^DhO;S(_zb-3U zwfC}VN~Gh~RSXWl=bke9I#VKJ>zluIN00I{F3Mh@cVvP1#0dgNmtMQN+MFSvVb_!s z9{D_ycP8)bQ}K-0{7s0#fp@Cl2RWV$$z4lyxfZNqzhF1*zWUkU&+p6U$}}Z>yxp0Z z<#?Yr=|{1E(RyE#JfE=6tG}&8o3b+@OAqm~-{Y#0y*jD;cITM7(YA4VnEXU5-G0QWhMUeX$uS-k}kCSul!t0yu?_ZzO)Z|rDudx2)>pcDde*T1)6})+n+$Xc5(?yZeuRbuRS_WTlgXIm%1W#3)b$1#5R z{=H%Sdhsh+*W*sjIvb`FJCCn>X{P(eXl9c&>KopQD*y3mIy`yf<3(3Ic*B)bi*3v1 zyp^%N`&VdgJ)izPbE&^QiZb7y9sKsrx_R%*qi6R$-?n?hJh=nz!CyE&2t=Dsju&8g zVagQde@%I#x$du*c~hru-&k~Y-4~mw%U*g@Mc68NG zn+CRoR{~rUR%>=OZB8%VSn%;7d*175482_sulrOqWM5maRxWE>#d_Xm_uYlLcZC+@ z&HFR!;tJM1wry?+F^X6Q*n#Z^+O!2YF?^i0ZvyLVmy&i4!@!+eRC*s$Pt`>PWY@4_3ac8?}%)a_J51xMe zeI>m*-7LCL$5=l5dRoGNMmv6o9jXp5pQpxXY+M}AzfE0fZ5gY){4$2E(^Nby>K+-1 z=~y*sTTWfE{`vN4lcSkRZgcJVv6Iz0PE_#G^E)fooLiJ|b6r)+{TDWutzujQMXp?p z42X&8Y;Nb?n0;OHPsNHus<}Jgn*FO`-C%IFXMLK~!qDyiLbkn|z&bzm($Qk?HDS{0 zVl>^NZbjB?+$mscnl@$X=iiH_KHITXUgK?3Q|AA5hcBh*wzXK7S!u7Dvf&xK_BHvp zHr_Ah-U@yBC4KF()fP*0f7ImW+Gb^+U9o13V_ux)w>R32Z=U@(++MCySMBNOwvJVw zfBVC)hE^rt^Zrc<%bvLL?GDz{msLY%-rX}Gx~+`aeJ3J{ zm&o~l6%`OuICT5Mh2@!BwNGeIF0^nz*1K!l4eLu%xpq@Fes5xO*!9`jN=oXQnvaG< z3ny!b!9oT>HOw}&hC@r|uW2Fe&fCv9FD|VMIh?g|vD>Q~%JZZgk8qs#V5zeH?`yV6 z=kbJPTyKA_=Bo(4KW)v4eRKJ@p8aWg+N`_9>gLwZelfnP9Tg`;n&jmE`CXroD!K1f zPG9xae-|}-aNCO4Nn}c1F-;9i$=k~}_0~@v&oaraPuKoGcK_Rvq=_HCP80s{(ax|3|c8&^5>Dmq9w09?dCJiWxQec zUff1)hR7C^)qBHAGOnLH+?ts1#%G1K`-+slcZC+hZxla9Kgu+H-ET3Mt&lhTX;R90 zAD!70@j?+F_wqnH)Oot27jcU9yRo%X9y{Qs?lVc$j5DD$Et`dthRj4heY&HE?54ZVPRn6EYU9JS$VTx zrZ}B>7IySqc#A=U!J-)xW~QF;4C31N__ZA4zI#V2rv2Y(?>>Llb_oH2&c|mAav$+N zn(2G{!5)8^MAb-DJ9RnpmrY0dW5R9f9>37rs8SkYDWZUGKsg>vP{XJ~r`hcwk(x zbo<|{YzDtQtM;yS?Qi&(`{2>8wVKrjp7I%dNjb6XjhEvrr-clA=3g{_+Hf#>LHXI& zYq$1aW>;{IU7FX>?3}R1Ne@6s4qD1-ZCEjVMYRtPXT0R zVp4u-iLH_nmx6)<)bNVj0$*Ra@p;A2P)N?t)vrh_&^OdG(9g})N7hkX;#yXMUq^9B zWkITbP-=00X;E^jYguYui88VgDammC1*JtfVDF?P>!;?V=BDPA6zd!68KPL}?&%u< z*8vKG^vv7>u&#>S0u+@cnQ4f?Lh%hq2J9VVhg9Siz|BQb4f8u#F*uB@T=J7kLC*7Z zu~h=uZY5suSXfx38Ydf=Ss)qZ znO9trn3tRiGO8lCKrb^h#VXC*#MIcp*g)6N!Z=OW#5^fYH_<%NLO01cDLK(FF)1-I z)fCAH|Dw$F%)G=LWLJTVO36&IGEcTNNHI@J(M>Z?GSxLnPBhg`G%`unO))YwPO>mF zFfmL{1{;-}V}Pfvl98SPLL?w3u_P_ODA!iW zCo`|K0wEESnVTA1k_ZY-Lo*9Q3nL>VQ*&b@OA}Kwgrcz2qTQ)f zSos%arskC-g3_t2k|9{UBDcWGxhOTUB)=#mKR?G-3FIUNBRxZib3oF*sU?Xii6x0H zi6yDFN=61o##RO*k38}UNWeyuilzU_rm*f{! zf`uSCDmb+e!h>*gGC`^p6coUj!YVNt;)UYGveXo?H{epq8Hsu6sVTNf(Bul!l8GfL zSXfw?nVBS8=$aUt8S9!Dn;7X@8m1)a8W|fKTBfF?CYcyP0ts${ ziLH`*W^MsEtQ0iB5v7Uhmhy~LP%Ic28S5ID=wb$#KE#VQ`k*oc=3^Uuj4**HKq_SH zxD+5_K`w4~TsHdPk`7erK@0?yI<&;lxS*vK3JRl^kQBb7!8ICOB!vJ;ibqq|XmF7f z0wgIOO~R;gUc-p=y-IZXxzhIIixA+Fp49p+K}p1Pf$UXy*Qr^ZZ~ zonqUS64;WGIxlm@!tCZn6-iB%UR{-y%j-_A?#!?6texBy(%sdyVZxKG)6ego5!*9k z=FX+5y-SxJST?7A`S!zWUZ2>Q)4OrwiQO~G_7wN+*>nC_W&g2b*N*S5I&tgfiBGrA z*H1Wq{?3io2{&#$y1y;w!Rh4>yCyz-_~Ox((#JpEzU-g$^5we^Qzn1-@a4zM$v=Ml z`g=a%-}4Rs=T79*`@R)_{IqfR{W81xeYf3t1tpVT=}a{2^}H!zr8;R+ zM~Ba3cjHxEajMHEb@)tl$vpn*Yrk8#&`g^@yToT@#Hj|(Zj-!jdhw6fBBLdiQbp-Rx7bv8HG7^Yc9!ma3AXf|j!<@hmr;8X4hvYk&SaQ{A;oj#V!)-?C?tmw$@q z`-Lj|U)JW{bH91&!|U9V*d?zP&A(dtbgSr8v9m7Q^=&4+T(L1?{@>-nrzR@7U0rzj zVnlG5-shIxK^zOq9D7%~Ju})q^`N(=i{YDv$9AUkt#^H0p=dKTIrj9{s_C=#YW>i1 zwaPdvTmEK8d1n4n4L0@SoQbmg-G6I8eULNrHmmucmDl1eWmcKy-~AY|P2$wSh^vz> z>(+8<^#yHob37+g5|g^f(zW{JtFtvv6Siobn=|jsS{;kW3!dpk#-yH#tj$VZ8XNP{ z#P~1!rL0@66+#Hw)4h2|Nvbe=PT@(Q4^)w>EDvN}ZbLt^Hd( z#(M6{m;FQlHmOCC)8<=|V-i0W? znLRm&GCm%ha`Q%CnAo~Kd+y53`+hrGa$Ssg?uB(4}w z5t_H_+(N_VDR#Gog-`u;_$d+j^1Dw~jHN@_?Hyj{)6cIv)!ei4PNiLoOt>8TsoFE! z*0gjioU=7P>%X{p%<&(-CaFg&jP`x0xw#?EXhuZ!JK0&6;xna=R{uSkD_h?an7f7b zeQNLatVMq#m+ZduV%a(8d$P0F>F59ddiHs5y6B!wlTD-Ja?HbLn+CyZgGzd**RL<-iIMgxsmPntij)%bo=* g!E*wX@yY(VzI6Is=i8H<*+IiMp00i_>zopr0DbWH!2kdN delta 1053 zcmaE+ai4R73ggDfoC2Hb85kJSrll=oU|_H=3Gxg6Pc~p!c*I(cfq{Xuz$3Dlfr0M` z2s2LA=96Y%VD9yFaSW-L^Y+fgyjun$Y!78Cx?$q0Htop6oV9%~J$_Cq$ej-vT^IX5?d;~Om%f(lrC6&uHLGRLymF8Gr~a*XC^)}GmMOJw4{sXmL3KZ&?Ef6~57FE{S-S2c`PGd&?U zWoH@d(b5xgQ@m7jS8o!38hKqMEbF1B`k~IBQS;W#(tg<^c1ZsGs&uaA3yXHvU-XHz zy8dUrzt;1QqB}DZO+vk7_3EuVvp>wyzrJ>pOtM+e!?kB;8gKgbbcM0%{dvb9`_y`` zUd}h=PSo>?IdSv0=jC~)id+=`y}xqO%2U_Q=B`~LU90@JLu()X8dPOv-X*G zszb)&eZ^A2OHrO>`)V`1b{Qr6-7JegIdxL~5uF{(X(#%ooR=+jsoBTrHPh{jL&~o9 zob@W|uRG$FN-N1~oUCU1RXq9R#fe3?VrG6@t|C71>2rSnD7izy_oAo&`8OqIKacmx z*vE;QJ3n4pI_XCEj@)pjRv&$*%5uR|t{GlMwUVsMo?L&W_gpSowf?^In-J;qkEeN6 zB`fgxc-AZNYkw-3@ghG{vutI9NCuk?EL#cM@DtOc+H)Z+(moh)@?iebBFTM zE$i#<>{}6juKcLt>eqZzlwSs3eqC4ETeLxU)z7?zSNi@6)wJ)a+wS%=V$L)7j#IB6 z*$7?UYLvO`&q~GaqzB$J!{ZtzO{wyo7WpeEY`ePZ)2<-9PxVt@hDGW|1_hm1bfYqG zo?TwndYu}L^Yc0-HEw#ISorD9!MTl-_SVhO+`7D5QOo?6asaccwePN=SE|w)Hy57h zoK!R0bj!q!dwTin=ajF!bKyO!_bJ2Hn2jFUs{i}8?zpvJ*@<^y(uay&11*27)&99? z&6J-}Rrw2^YDHw^C#TN4R=@8?z~Vo9tR1_&ONCQ+`OVr>9avs&77;cfdZ$ig=47?< zU5~Gp?T$TFp7vLJHk+5m!WJPA@hNEPc0TWG^&5Wbw}Az45WdBwi=Up_vc{$VVZDcg j0E;68P55tF&!{|0`mn&qDfVp)3=9mOu6{1-oD!MDmB9&8~b_VHl86)cN z^Y3AsA+p6MEPCyW3(Iw{s3b)RB|J_QKO>kHG)0C9= zBF|P|F!}p7ZHoNK7ag{dmsTvDSimub&Gy)#TRB$!2J0(Yk2X3M?@H>c-2eK??dY!m z%4b$jSg^UnjoGNLp~U~n%p-pkb1OeuywbJ)*lP1Oll>3lg@;k5Zt-^h3=E7dna<7u zp3crN4>2%Q%&DDd>v7mY=4kxoE+tV%>kf;Q6@Frlt}@x8T>*+$xYkX?( z-2LFz2M>-XT|Ia-GMqi!QQ!|lVQFDd$wZ<5(?z0=R21ClFaIuA{+?lBVB##%F6LQz zvtOn-op}~^^j&z1L4(1f853rvp79Lg+V}Xi9OJ%wM=PfN-)ZkYf7W&h0fElPXAE*5 z@jjaAd;7s2f0;zpNL4#^IrEoINBU#JZR#G!ltez5DQG--o_e1^_fftus~J;$dMjpG ztje-_${pDsdMYl}zdft=u66Bi_?P?O(XO?c)d!yP8GK1OvFweP<1D9z414BZG=JJ~FnU4x z+1G2g_FraKaE@J?*U;>ou*T!&YrFUFUcPyI-+1l6I)?q8MMqhTFU(?K;H}6Ei71Ki z^|4CM&(%vz$xlkvtH>>200A5Oih{)C?9>v4q}24xJX@vryZ0+8WTx0Eg`4^s_!c;) zW@LI)6{QAO`Gq7`WhYyvDB0U_*;H6nnkaM zm6T-LDnjVC87bLuDcBUHq*(>IxIv95N=dU-$|xx*u+rBr zFE7_CH`dE9O4m2Ew6xSWFw!?N(k)6!(=D#dD@m--%_~+0838fFCAB!YD6^m>Ge1uO zWM*PgerbuVk`kAKf&$d=irfNUU%2sk#n4bl&d=4aNG#Ad)HBe}&DBTNQC#9$R)Swg zaY$uBs(w&vaeir0a;j@tYF>#lvJolCaQy|PMLA&aq$KO7=A`DP=9Lud8|oRNSn2NR z8vxe<3WD^^+ybz!irfMel_i;Jh`>Ve4M+y;9b|`8Y5}QCh1z3nI!5Or#2jQ-fs9JYOtCVuNHtF}NKMwYOffLlH8C=?)J-xqGuKT`O-i*i zHZe9bPO$(Rm6B}bmS2>cSYoS`nVXoNs$Y&Ba9>n2bk${}UlC=Dy zTw5id%)HVHgwl}A+|=NbL{MlNnpqfH7#SIvSz4GInpl`26osW06=&w>fy^{C&@(mx z%YdTA%D*TxHLoNQlum7x48h_Rxdm3vMX8A;`9&f5`8l>qAO|QI=@~+t1CsVlElEsC zEJ<`pEJ?LhGBPkSwlXlWGPDRWG_f)@v@)>NHZZa>KvJKdl9^UXNVOd(bJ#$l+#|EN zB)_N!KsB19)z2d2~w?~pa9MkR*A_FFBB)1rKW(r0hdb7NX$!5O|eyiCRdo2 zOe{$uDKRb4BGoKK*EGe#P}ju3+)_8m%pytG+$`DB%+lD>z$g_GtZ>td^V3So6N^$E z(^KrJw0Azrl62bCExAKU0-gb73e zQXylEkDFjGTJes;j zgNvjPAW89P>Y`e3aUr@>sd*{3O65xScI&76$}=!9tPAi7ape~1Fpui@)a~^2n(R|O zHD=1}6x*(pz?PKMd6_E~W;ZXYNNTF|>Z+_rsUApYRvN`q3w;x{f`ozYZ-i;ei?4D7!r?_v=p7X~l`;Q&Fc6@i$iCZ^M ze7beMe!}_lcW$&!xN+mr{cSl9PA`AhHSyuY7mu!#KK}9cW&fm?FW-HbGWo-YFF$5Z z{_*42-}4Foo^SX+cgp|&|EE4Uw4H&0ficP3-G!lpRn~)nfq}EYBeIx*f$s9bsc|98FN<(XzyK=c?DQV$NMW z?Y-{((K#}G-R`gdMxKh4ikl(2dfvb1--6C9pFaKX59Md4AMU95Qm||tmsV=!>6exd zr~Lj9dW=a?Tz0Qlz*D=kIxU6QgpP*1o4fP7=w*SUVM6Qw?E5ye=8%ThBD2pgC%Qb= z=-mDNoM`Vdl{gEfY^gxckk;&z9bQG5XD1yCv-)zj$t_%H``$~J|1nRwa;tatnx*{# zQE`s9-dk_F^vXK@@bj0mH=Wp|Y>0gpW*(P#w!r7EUOE#oy=t)ZScF(0Qfgti$@0*LM~B!_WBr{Q0F+EOc@7Asu1=ryt9{ zPcq#c)a72E9Uj>m9$4b_O=@NGFRkA6Cab4+{m$4lU8)IM8yM$t;;z}AO^N$XEnOp% z<-g-wQge5zJ1xF*m&Xre{a*YH51$$7=O>k?)%A|siHb|tNi9a+AjNgZPec%uktqE)OpUB zReHW>Zx(~phtguKv+ zv1#dgfkF7srr_ zIdAV=%)4bE!uFtNjp$W3^BKo>FiZ@U>X_!S!6f_7KmBc6ddwaa`w87HJoD@Sbd??Q z+mfyQu4p(3usF_1`!exV`{l{|Ui?&%`TNR8+<#KZ`cpNFi;Am0$=iNEbaDBn8z#PL z(~dmMS=;y01)YeinW?kvs&iNEBCm6>hFquO5dND*J{09U3?^DNqVYOx0l?@(3RhqijPU#YEMnF zUf%I#?U^07L?-T=>a+OxlZbosC+)lRa^oIwo6^Yd!xcx-%ouB-Be* zuim;d`@uWd3B%Ad-Tzht=@upu-R~W0_pLhJRPp$Xr<$P1_L_M#V6E|;rUY>WV z$VKtr`zt4{Jaz4C?%E~NwaR~6jxc1dUc!E4#(&l{YoBTLr#fUT-d8LYycFeGwy!qB zYnM^7-_5f4lT#-h(b>VAcA{^}dD&u@nthyJGu^&8r0iw34jG$!fM= z#gk87oLF=#X6Co$D&iBLKIiw3k~sRh7Tssa8Zresb!( zYx`~lEdH~{+Of;KR5*2)->f~=f#v095n&Uecj`oDPF5@5_4sPp?$}f1X@9k6vw3MO zY!LzxpMs`t=kvZ+zu~8T8(07b;agm~_~|LFEo)r*AJ%&~2(UOp(1ib%^^D51qz`|b TV&BHVz`)??>gTe~DWM4feYY4@ diff --git a/android/res/drawable-xxxhdpi/icon.png b/android/res/drawable-xxxhdpi/icon.png index ba5e223b8dfdb104f6fe41169b95f9d8fb53225d..2de449d0c29fa3449fded8046f61ca68540cf6dd 100644 GIT binary patch literal 7795 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE6983%h40rea4q#wlI93%BQ4*9`u24{vpO%@E zs!&o{kgAYck(tK8P;o0az)|S10>_@;U3QEtqVtnyl`OM=@p-q_NfqDnW2ubku7|!= z?mx6xc@vxO(W0|68P-N6Hyg~#Xr5W};lSnS6Z&5ZOq9HuG>o^t`TpHXp3T@~MTwD7 z`J<<|R=oQ6E%^z*=X?#PHF+U-Js#z3klylB_te!>Keb~6ItB*DmP}{o z08eLUn2#73D(2KqwDmabAagYSa+i{*qjiTx$_hU*M^~9_(XIf+D_m=>X2kqr^_7|= zBJO^0>w^c!ldc{-8X3+W?kMnwp|G?tsAQth|LG!8M=A>L^p}5^D}T?hFfehJXczOW zyxA{PoX$K8JNhoX#h}4p(ToW*Q_pw?aqWBjT8?qwy`vS={_nJRpFeB6gn&Tj<1+@i zk9Z%=^u7IHkH1W!YNV>2x}5pTrX&3^;Wl-TV@e_)%oH@9JWst(p!+CanAMD_KD`yQ zELLS%J>`z<4?Pz>XRBB5)^{i4CCFLwB@cVUh7x$hevoA@_8 zFfLfS{qI#agWsN2d)K=5H~h%n4&1-0OPFUk{^R?akcQ4<(y>GnsUme4K&!VF&#usKWFz{AnhD4M^ z`1)8S=jZArrsOB3>Q&?xFo1xKeMLcHa&~HoLQ-maW}dCm``!DM6f#q6mBLMZ4SWln zQ!_F>s)|yBtNcQetFn_VQ&bX_Yl%Z!xlxD;%PQqrt~T-=~W6s4ruDrJnJX9Ei1vV zqd26pAXPsowK%`DC^^-&EH$r08QF-GWVrr<(xM!&cT$q|Q*%;tQ}arS^$qn5QLJ?L z^bLUP00lvMW^MskS4D0CiprAAG(=#b_y!~c_71W`Dsl_p=Ax*E`5mkn97a|y`N^dq z=Xtu=DuL{`O36>oOtAtp6HP4)6HSbhb&b+ejde{@lT36I&C*hJlT9sB6O+v?Elmwl zkc{%oD=taQOHKtDRgqhumzkMjWp133WNMI{sGDMxXsT{S)Y=nPNW_o5`Vh*yaKt`oxrdXL;q@-r$oa7iL4G!4xx3@wa|j19~UjLl3f%n^#hQj3Z+^YcJv8XD*sn}B6N z(PHIal$n}Wk_bwtwn~Oz@rv96E9aur#FG4?ko^1{TP2W_6pZu?AaV=D-;w)Eg>mEk zDFjGTJes|mp z@hZ#g2huYvk7_@D@XC9mc7*Et6RVCIZ`&GkT4b*1H~upH8HI0R&WRT7d44WdtzBMn z<+KZd`TMp`4&h!Y^U*^{a6z7J#&*X`lE&pVA6+A2Vt#GSUfSF18@1MN`Q^z;KjK$r z>{+T|^(aFFNzJ<$w4++rMY;lOsj3*R0nB&EEFr@z3~wj0($?UNW1l-xd)+ z@5^h&$%oaaI;?V9ef8dsz=dJY^X>^b8uUu(s^`vLTied=;-GhTaua94! zaqj7VZ+4a|MYbV#l->qkc-N9X@2jHd4M#@Huc>i0C-n^s_U-tQ7Ciadg)SzCv)n7U zyn1zeV!QwUx0Uhf;mepA7g>4FlUn}c)oWoX-<^%Vj0?oPBO^mYN?yIrXW3Giog(MJ ze0NFq|H=>32}^fTnt+`nV6Zmx~*#Gn|$*|w4H*{t*(xM`#UcuJcwmj@zr(p)m2w5ndK67q%Uj` z{IueQNV5ZL!;NoijE&Cu&tA7_+h!k!zwBPgealQUw=$%NIeb4g(^z~?jASs+Ss(5b zOXY=S<{VgjoX3H!@s{GN z2BbdKZN9+T@T2GSl1&-A?=50?S+~`{FEnP)3d0!-7zE}A#ngO0yS=PS_5y=>W=xD# zrZJ;OPJ@T1=gHT%`vvtCq^p1J{r){9{Q9gHKeT6O?J>RoBP+?WBg#-qkMVS9Rl zLHn8DH%0YPX>1O2Uraa~-*V}7q)J78M8v0$k0V)Gub+7MnpO5xmdVr2Y3yBs0#_rQ z+~QI$Xmrf}^y$@>Ej667rb$!gyi(e@Y2of~27VP=pU$~<*GE`pLgBupvZue8EZeN> zTRQbi?Tni{H0R8*S5=+bFNB zkn=cl_A-Ve*#VCdQe0+zyIIWC+$?;u^637y)vNP&+v^F-F3^ouIuSAPe50vpW^wWQ zGc~pCwep+KP8N|~aQ|#ZMdqxok``$xsY)p>UhhZs(H*ZYdT&Tpo<3vEn(i9Dl?xYk zn%F42pWgk|;JHh*A@i>7Sw~mu3vaPrxNKRUf9_=NM_XQM?Q4*k(;|6s;_9!H%AJ3F zsmscKf8vSZUp9lZCCP#f@0Ly%-kX}uBJcd9 zf`UI=&foRgo`2~sk25?g+L_k|9WAOm^!;Xf`>vAZj2(~H)?42?tlY3g_0B7M!;d`k zH_k9G^zztb@}b$%D@&O16k~#Qa^nC}@Xm&6SK(6^u&EPXE|Hyhza8&KD3H z`}b0+;U-SK4mc`7K3`K$u7T!vBPWk#Z=3VyFXR(s@7o&4- zH%BNbblsXawY2fDd%<(R!^?aHpRuH*I_p>G^KY-5HgRHB;MY1Gi3w9%EFP4-w%2%J zUCgw|%2PtE>vr#&Ug^WRZx}pg_{>;M=XQpae|8tj z)7~*}HFw~)VBB@r(LHgGeCif^2f6hXPgU0LE_@dpwsX$Lw3klBeVk`Xcs9hdryHKj(ZY~t(98wwa+ zy=X`~|L}*&zGcha@%A%pW>z{};U9J}y;AH*jn28mJx3XLS<7ooyaT|H~l!uGGr zZPg9u@6otx;_OxzYco^Q@W7Aa;2Ob}8w@w^-QC&!rQ+E+UY?u9F%$kadUxP`z1?wgawl@`*QuzgZr>8JLH5*%8@(oJZzTmXD?C;U?}@0(ZugRZl%Jvb ztJC zeOk5OK-sa7X;a~X<$Ozx&9%1fj$U@zW>>=hDfOD_JB=5yx~#LC^ykI3_iC>fcb;9l zJ8M^L)Y45E2g@==)faF!&+Os#s{PRX_0Oy)rKct`_uOTj$aJbV?Q53h*8MX)&cD38 zbIx=5C^m&t)A-`oUD$ZBSpTB-o@%BG^QXPOuAO$4PvBG9vKtH&*PXdj==0Z4G}>0h zJhV>UZz|ITd41NrlqDAr<)tcGIV|~JT%^0ZJN)yJH345<@;d0gKfI;>zvsjKYu$4i z;$+W?oUGmXQc>}6wYfM$R^p@2ox=9K(t5&w;MEARelq9{9&>C>De6DEqJ+b&_YyVm>uZ$>xUlB=(-yqfd# zbNR=aFLMqyw0&P*yzN&3%a-HzxqCOQPiB`rCTwM%7gFKb$5ZEXElv7<7oWh+Uq8-F zera*nL*=^N&6vpZak7E}JJVX*)%Sl`6feDgiqPZD2I=lr)yC7^G~Rp5Uuq~_;V$Xx zd+XArQyV@!S$Y1a)y`RoQ3%v+Q|_F17BW(MpX1$K5M0I` zF*PyyPgcUO#m|?t-wU4;@p$eQznOddeqI&&d1?95vb{a^U3V=RUL4%G$@pBzk~NvD zZr?3gZaw==35$Y`4vPW<6J8`_t8n_)Cedy-m5DO_oZ7ye+O}uaH0?6)cNH(M>aT3v zIcxP%-q1BgOIMY=bgyRU;9hXGki8~i-ufr+*ZcDct1n>XZa#HtRYd(;-`KyWrdBg{ za4+zG&y*xB$b9Q4V}L9JqW}~|3M{Z&7sSA%@NWLTW^HB%1{NBiD`^dnBAGv3JyN$W zFZ?&NB;#5pg>M@dsdzUzUsG@0=v{Q7JQ z8<+i>C9XeQ^Z3oVlI`NjOTQE?YVV(5Z#XN~%}%krB$0swTdxSY^VFiQV4cq3RKegR zs=&~~Ai(6n!Jx>&pa^Ayxgd5EREnVmp$wVL!JxS8d_63_*8t*cliYI14-?iy0XBj({-ZRBb+K z1_t4;o-U3d6?5L+t*jA!y7&0U^Ji|JKUpL+X+lejX^Vq_j-y!fmWV*XTE&dL>$lw~ zYQ6n!Tbce^@0HhHt=;>mEW|@XJ-C( zXIYY?cxKKVgZ|9A8Wh%NnA%G~n8TZkbs6BqurFV7*L zSN4(Glg^}X!FxP3++Kf_xES#Mw8Xr%6DOtAcqbdK$PQeY`RZk%PkK#bT8?y1UfPy{pL_b{zT>a$cKQY`?7zCP*2UmE|NW4wM_(@V^@|Qe z!^5`$MVDsS6@1FHfBlAM;cKf#2lcsg6SH3Ls@T71=B{00eDZlu%IjA+Toy`Z@te=K zV8@BmzQt{ZxlxR+i#K_Al|3r6EVO#NVpYt%FX3|i+zjvA)88y~URaVO_pqR4-8znL z@zSCX4-Cz6{xt00Q`NHg_!--dx&C*UACzg|v9a!Eoj=nf^CicmNq1JPQnLP&dM(dG6c{&95_s z8dfh`Vwis~;@KHF&%8XVvfXEUvW}#Bg^F(4eEG-s*2y!?Oy^IJyLn!JzMi^($-d~6 z3I?%zhCN5Smj#$;v2VYfS@Q9a(;Po`Rn=xmn+k^Tp0f|npU;1Gb-$@ZRC^+$hQzZc ztK(;$(Jl7=@OyGV=+PC6T95wxYnQh#lV`G5v4mZY?CHA70~PCaY^L3_o*TIM(}tT*9H({g z_a8rcXXj(aA4h|4-`u%dbnSHKLsfgPpPM=NIJ0-ULE4#wn?GIe?7Zvu?Ck6V&h|ef zg4Ow7lv?h+Rh&O}{`uzb=Tok%*b^77b7X0emF$$MqMN?lGiu{6jrqZyUU-plMs2Ma ze_g$pvfGm{tDF>jQa0b5VU()U|1U#~^{}2Di@0b*zpsDt%f0gxK0N5@UgyEF*+Qml z-Oe-<{yX(=UF)|WXOu8Ia`5{8D7V7Gd3Rn4avp!VrTA~@;))*!GsVB|m18~dc;m;L z)?PF1_B!ZQxE}gc5p$>Rc7)iyz=q2gH#dJ_{hl`6`{}o6_YXH&Rn_B51e5}KWnMf# z*D5^2U|)K9+c{~5&{j|3KX-N($S|`nlf2}hcxK5et)oYOo|$hx>&q8z`3>i#8TyZ( zdv0evZPKKJZKeSnn|H?W*?r9qH>iL2fV-^qoODBWdRoEdIfb9TO?y#(>GI^rn&&Q) zCY`fa`njk2+xK^KJ-xi{?>`~b{qo8+v%uiXKB1w;rlxCM78{0bms49b`}3kBoVi~E znRsizU)r{?q@wDmLh@-J+pbBU%J0ar@3dSgf9}=9#_O-&ecs*b>iYA}{t}z`-_Mp6 z=M|r0xU+f5go}$G-~W2CR5tcHXX~PnkdV!FhpTnsH}>~NTRpxv?R0P7p-JkKdG)rk zR($W7uNODt_?iCxlv7unjM!9?H?J(@Z@HWIc=C3?XS2P{<`%EH5Xn4Y=EFzltkY-R z+-Ce@m!FB$;%(b{b))$|U-I4^@%4OE)7xiq4_htkKEE*pVI$*f8$a|hrQJ7}wrJ5C z{&dF9`)j{U5z*JTt5+`0uYS8`{(Mu0`+vW&7#a821BvpU&bfT`EhgV?#QC=_kUiVZ+7`k&YzHp&nHcJ z^5}KEuI=me^f`>r?pVx8Jj`}v>3%Uj_lWK982bO$Xy>n6XS&z!;&-$B%w?O3n5DOy z6nvkwl1J9+!WXO1(8OC)5`E@wn%z5F%K!hH#s4MjuAJ>z-9Bl`lVdrtv58@8ZXB!q zzCQJ9)Xkr3m#m9>eEu|l!S~wyP37}{rq{Rzo;d0G@K*M@In!TlVVDv#@#Km_K9}`_ zoECDd-?ORNUH-Pq&v|AhJI`&MlM(X#Yp8qW1NPOH6(4rI{0b`EbYxxP-o2bVZ*ujn zMF%3umeLnY!1qwnR)+)z>bUmvadWV0R23)2Hc zS|=)B*3`BAYddW+qlU!0)L)^@zH?8zUoYj8UG==3fB(sKpMLnaXbD~ZG`0Q5<6>(L z7As%ggAI%)dnD#H{#90QoXmLQ{P*C98JST}FWi0q|EKWl$ld#nUa4~V|M`u4`KGeB z)yu9u{9L)C=#s;yBLCRO^LQHUbx(c~oS%K|&C{9pXaDx@`nU2`#gyZ6_gzX$C$Ie4 zqa$%(!Jgo|HKGb<-zVI9;uE~vJ~Wiq=EGs$WmENk?bnTtu6Soz{C(n@H6fS&EDyU9 z$av}UU7vkMx36@~udn0Y{r$-MhwU%s&#TC?Z|4i09afjMs^8G{WX0aQ=XZuMY`^VT zw(b5Fuh*}mE56lh`D(|<*v*<{I`8jax67xt-wZj{e_G+}{lA-fxbA*ly=1xi%&?c& za;I+D7I(;M@u8r3@sYmC$*VJ;K7YRa;n%YA@1d?Q<^I0>IOFpdbD6$RSJr>Kd_#j# z!1nS@$+X|!ye^;m{(G-_{HLH;zo|E`r_KzEEG<=TZ0LC^ZY$2y_Vc*3dxzWENMLB?#<}FwO7i$OH-6FBz(I7N7>AzQyiOSFYb-5gVI0Yxdd5*n3Zl=E=NUzo_K=`V2PT%PXwzIc~T+CwS(Ln$j)p zD^?r--?nq*VLNLpr;?HrN7wJDc(}ps!jW~KW#^wd{x)oPd`DpCg6w1A%d!=}%#iu( z`l!$ z%6YC%L7}X>@9w!h1mOtdJ&J3@VzPN(LK}BQzaZk6lO8yN0 z36c4a7u`lMCjqVfG-ZZlV|7BV$u z^{YOApzoE#$(@p_w}0#w@mj!K^PlCLblkQbe`*Rp2CSQDdF_hTs_dwY94k}1v+FL{ z<}Z@Gys&(_=IT@3+_P;oXSUAY%@t~zUw?6n#O|`I!I3x1KBX7&D*U>0K*v1(&z}%3 z?#;fj(-$pOJ@HL;tBKiMsYUwyhg{~LI{UHU`gE@yd0~ov%XgkulU<+p^g!dT%XMn$ z3>VWk8O7fDx>QK4X4jQ1iTvsUw4}qA&L=7C-)qW+=I<$Gs@q?= z*v>yA`*p(QXI@MPFNY^jWa!wTE&D$0W$pE#Ff*b4<9j$~`d=uW+trm8w#KIBSh^5{ zV0e6t&6MhQ^UgLfKI1R7Qxxj_bS(Pmmhw42>#Fv1Hmq{!;=aSTVP!(Ns0gc5;M^H} zZxuP(e(#$4DE?;7glW%`E*&x1_oX%H)G4>4rM(Fa&Qp2+T<<@9{#WE7_5<&m5+%8s zFNdtt57CraG3i;b$5)LTAuF=_1YcabSz>YQwy5E%S;--0&Msp2UI#mWy2~Ro8O{ zvx%pxyZFRe-{Y^}?w(#&HeLSN_46_|&!2NjGVJGmCUQHXW@>ZcPpxxudt}zG;;Nh9 zn|D98o~_*Oxq?B(I`LwPboaWl2~UFbUvey3 z#l;$F_HOsV)@AJxJH4F}RN48%KAze9J2St3Uit2pN})9j#qO*BJUFtUA;a$oD7Ck~OAp*6ij#k4~2E`51Q9wN_v89n)DE+4*Tt zeuTzwew5}`-nxZl=l{m*6%YTPlX+97$XUpC!n5~$;LXI~A2BPtIw$jWTV&_*7QL_4 z|9oe@7U#D3KhEb{EacMEX5aq%bA`(Cl14rDUF$C2ka%#gJ?{B={SZOR|8M^ldv!1z z2@dSz4So4E-dlymx4ZC9h4JaSjnkfL_Y3fSuiMf6l<9)prHL0E|Aw2a+L-Qdy8q8r zw|~E0DK;JSu0AwhfRV%X@pkX9?Z+;tGX4!x5lVf#>w87b&9ZxYPVBaQK1qNvgX4(C ziJtSN2mZ~HFywUDfBB9~!qKj^f4+VHeRK2m9fj{W1sl4odRHDkTkoxLK{VNSsUG7C z_eFudd#n44?*Dzip*Y>t_v|sf-LnN4BeuFfHV!_!{`p?1-d_ve{Qc|d?LA?t_V!J0 zPTmcXd1bKl^(+C#io*5h%KKa;W~bkabm)G$#(Kp?rO#i~wnX?WU#xs&TW*|q5cj0_cNwJq&=8y}lKwA;Jxwaki(6_xAVqECEy)XJzdQKQj8-Makh$5+|b zm9MTpuj{?&(J9=ZYq+exqu_G|J705q&bE?QI`^d#^S74N3w5r0T~gL&S+{4VZ}h5) z?{^p5{GH8QxYv8S0b|Fp2c4#ZpDv54@oh{t&Y1S#+=g@aPbu%&;H@lSxWuEdb5UUL zt5<5LzF+(J?Uk06R@TiOEBmHh`(eMpX9feqn-$k8-^@?kXUwg}vn~Ep$jvC9=X1L> z`u7Pg-E?z-TUWPO_tA{}_e+*7YyI>|EAHRxLMiEH8QWW@e#^7y$24>bX)z@7{woYn zU)CO3r0XM*w13IF3*EO5*Zlv=;U2BGXRU#o!3{@)8=l$O?7qH^TH4Y#_H1qBojr@A zRcXuC(!l8H7UD^qq6{zi4zBuM(r4PfBfITbZuqW(oBDbzrfa3nui^c8+0@YK*gdm% z*GpvO=4?8C!6^5XUCybM54TpWsb0~!?)8x*!z4pl-^&+;zWi5w$iyIUIk8-}=3%Rb z{@adC8M`J=sU|U*ZBo`S4ErLyCMEiJwN-(*S9lG6I*Zf&XZkS ze}3D<35-8hT)*Fb{XYNXcbuPJc4{3q3i%T!`n+9Peaj5L)Z`x9P{V6A3xaPK{`=JO zV?_)90Z!k!AC6cX{`xKGt|C@x?iDeM#esAjdpUW^_V7Sq9*MY$>J8n?GIl?F`pum6 z9b?aDUc)_iHt2e_D)W4)@l+_Zs-7Zon`!Ejs-Ae8)!GsZ?Q-te^!*LpXg+(TrZTVo z_S2GSvv;5TP_*_4pTMQXEhUei^KiZtG@02Wvpnng+F2Vo7I2lR)^&Hs^gWRx!=dDO zjuJhw-LFD&@{T<(mx!y_5xjV-SxUvuzh}4QPR)oq^P%YQLk&?;)3)i^s-S_ed)Ipf n7OnRX_aHs|$#CMW|I7z&vPjKT48G04z`)??>gTe~DWM4f!@v(- diff --git a/android/res/drawable-xxxhdpi/logo.png b/android/res/drawable-xxxhdpi/logo.png index 753b4a63ebd3e5c2b512f7dee31f1d918c98cae6..bed938d5c188388dafc9ebc58efd687a2e800898 100644 GIT binary patch literal 5652 zcmeAS@N?(olHy`uVBq!ia0y~yV7LRq9Lx+13_30Ln;94wj#h<4lmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNTr9xXpXmfa6bkSKootd~(M#gI7wI{7G_NwB)Vl9K&>3i5vIU ztKVUpA+kj$EPCyW3(I1Akm-RDZDS*V_yXj4heY&HE?54ZVPRn6EYU9JS$VTx zrZ}B>7IySqc#A=U!J-)xW~QF;4C31N__ZA4zI#V2rv2Y(?>>Llb_oH2&c|mAav$+N zn(2G{!5)8^MAb-DJ9RnpmrY0dW5R9f9>37rs8SkYDWZUGKsg>vP{XJ~r`hcwk(x zbo<|{YzDtQtM;yS?Qi&(`{2>8wVKrjp7I%dNjb6XjhEvrr-clA=3g{_+Hf#>LHXI& zYq$1aW>;{IU7FX>?3}R1Ne@6s4qD1-ZCEjVMYRtPXT0R zVp4u-iLH_nmx6)<)bNVj0$*Ra@p;A2P)N?t)vrh_&^OdG(9g})N7hkX;#yXMUq^9B zWkITbP-=00X;E^jYguYui88VgDammC1*JtfVDF?P>!;?V=BDPA6zd!68KPL}?&%u< z*8vKG^vv7>u&#>S0u+@cnQ4f?Lh%hq2J9VVhg9Siz|BQb4f8u#F*uB@T=J7kLC*7Z zu~h=uZ}V}Pfvl98SPLL?w3u_P_ODA!iW zCo`|K0wEESnVTA1k_ZY-Lo*9Q3nL?ABLia#15-;R-C?Ok#hLkeATtdO^o&iw+CkA` zef~0*@OA=EOOA=iY zOHyr>j0}v7tqe@83@t(oO{`3ftPBja4L~H^IN$t~%(O~Es_j6T!v+%N9+|}@`9+mr zAxMr2PA!D+Al#fxkZJ`51#qUYN=$}$p*XQDH3e)hTq-#uF)uwe#a0QLTwz)=u_Ohf z#N@;jW1~dfL{J(sF-2si0UeFf!IPFww;fFnx#@ZS+B92F%Ac`WRsXQGis)*l{U9 z#DZMh?6_?7!6hB2)PooZDs^ayp>aV=D-;w)Eg>mEkDFjGT zJesg>hwyd}AvM?25|5W}s4l$s!0x~9iRYg++u7^? zRo5MRrNnoa!*j~CDf5%1zfaocq4aypj*K6xcO3jWKlu5y%0geu@6EqMYZlr@h49&( z*X%2v75S(&^7PJhndiMzPZ_VXjA!WEeCWqw_ajH2F_vrD9NwOy_*;d4$G?-xZrMWT z#9JQgm@n_n+NTrq|G~^vriMV&`x|R1ZG-^}Byi zU(Ayr!RaU4EuX&OUe;&*@yYa^q1w9wZY^K(@{d*o`}KP9%7-g$OFK(cWVGk_@?ZZx zWyQ&9F}IGb{K{QYsIr*t_O1YCiM76wYKdsxt%3- z;N&c|*3-KCv|nG4_cL>uS-N3ul|N8?^s}V zS%Smg+4;Q1!@nlgMy_{GU2RN{QQ4#4y)#|#kl5nUAL&pWjrY9^NEeJ3aqEdHQLRn5xpSfMf5J2kuH^YS3^_Q|cus#0&;=Lcjs&+iI&f-6N<(uMFTeHbK zu0H%>)xK3Muj0dH*6RINo_$|lusuI_Z{AaxaCftUlyaFgcbUf_+fVsDFq_LzerMNF zP5sus$0y=atnGxC@0b*EaBcsioY+TG%)@*h%v0I1*EweGzUpe3?b9E)z4N|oF?a6E z$2J8X0(NmxuhoC| zeYD8aK3Kc;zQ~23|EZ4)?|6N>ur~K`MCv2o$DQw{?-0Fz`(2&s%8Ao0)veBH2Tr}T zc1_uwnTof*RODIjG2FfOs3L#1cJcmbgI48uwjJt8*&DoGE1CYB%{uuq&y(DnwTfcP zBqO4yR|-l0)9ii8skG_d{K~rzl@0BFi~i#>{I4xF{Y>m!T5klN~RIn_*JW}Z0-@nTfbP0l+XkKbD_bE delta 1512 zcmbQDbAWe(%4AM~U6U^eh}5(0@N{tushIP2ZhieNGl6#F2+5mHyDM}Pr6u#WJ-qZt zS7gtv_y2o)xYj@6Tf(2b zJ?;DIMQ2!_mg%VVy~?aF$}6kfsoUAiW!1Ae>!95U(W6yKE2mwV`l@rz+_?S!*8Wu9 zel=S<=2&*{#D6Dpo1brJy=7o3{$g@W;E~?D$Bs$8oEx)I$~5TpY^KiXMXuLWHt=Lu znazK?ZSTArxh~6ZXpYojb zyZ2zvwAJ$&O;=2bJ1uG|T72V4-TJ4OUOiqqv1sa%#~N9){HC9pdFf@KNoR1TdQ9hx zMGHNr)%QfJIWH|TS?pD`W7^lBwM`kD_T|riR;s-H^W!2G>qnn#|5RA+&d+@olqW4C z_9tEU{=t3kT4RguExfahbK})DS6=N9n|@Pojm6a6uZs28UwJj7TXpgsy{A9?em+Sx zsGXx@dvT?VxQf_u?e|;n-Z@=ZcKp0g+vGLAMse|dmCE{x^{wlsUVH27*~cfM|8>$I zz9jKHt)ed`&vwQI9rn`FE=;uC#a!Jt{fcSdHn$ZgVgsEwwjWl#BCZghu;Ajv#Z5

(hn4B)ePtRa1J}R1u%Kf>@=nP|9g}3PI?JaSw(R9zZyQzQ zqQ9+Q!^JKC;8a~V@4osK(|@bEvUsolnaQbZxybkJVbzamci;XGo~*p+;k)xL{tuj9 zH_Vdx{PJx5xgC?9t~l9y`;_JDry@@~IyPp9tdDwk+FUq(PQ}}uQ|_d%eLjzAr`et? zOSPyr-Q!=16Hgl|Pw{(F_xJdb`DLue$*29Etm{3!A%E?ur)jK?rwc#tT=2Ah$IOZo zMLB$`DVAElx9(R+u(Zp+dGbJ{d*6AB+2LF=ew86lSh86Up89$+^{De9%LvON_fx@P zMj@6}cYkPx%dMD`<7?w%dF*ZFOI;7ks;&QL`0)kw*)o3L)U08%ldXEc(`4Z{x2*I2 zW@(x4n06>4^DHSdHJMFmXWw)ZeFYA;4ok_EM5s@ciepmChOWv-N-e;=&p7QKo zR^PMehvS~(KT}T6i&?k*%=w!&1={I5UgR$fdz#$+G|xT%A#dvdDzYR;#2v+jIa-?ZY=%sV&V10HGG1{yS^#aUtHGhx*DVMy>rUxzY|v9bkmJ(sKXvWx7Y6*RI$t!s_Q|cRg8A_&K#bp1c0ji{_jMKG(eM0+*<6 zPb$+o{UncbyXEU;D#!nOEh^ZQHSaS=nvGV`$_F0pCTA1g@TzbcJTHn=vf85ID{xhP zzsu)>>qU`DwzqO;-V!#kSY8w&ws*g>v$q}=iRjly3;rFUvixwyYR$TrXntfb7Chn87!j} zmNPubX`I6}VKXaK@WZO7_x|TS+Pk~`&$soR`|sE;Y1f-J_MdDTz8-cUYvX@Dp=%mB|+E3Q)Ylwbp7z%r917sYxQ@ z?gzI%cyK)F>cOLt;q2j#0)H3^OACWaCJOzZE)sR5qTo({`FFYU_Y4aI6K9EbG0)1I z{W8Vr%(Jkg@4{OQ8VnZAm@qT-jAsznzQ?cS828;fS~2baPJ8$Hv$jhJ2y{L^W03oZ z_t8w>+Yk2m%Ot8ss@kc`nZIm0(jOCUQ};NgB=W&bLF38u)cXXwkMf0C&6w)bTQSRG zRhHFL?#TYobJ26Qdi8F7cQRh$+-vRAG0QERRKzy2a4Lyh32xnWzVWe%f5QXg zf~DL4US%`*?OC;Vt!sb7zuX6pcCFQ{KJb*!;7iJhWpBJ3XE`lo*fam4`O}7j(F@AY zzFxbv|1!IRbL`T*hGyr4H6Ax#+r5AH^3B`(#%ur8G3@s&I?7^vVHN`eZ$)NEL`j6N zk5zJhu3lnFep0GlMQ#BD2-w(H6eK2Rr%!KNrB%__*n4QfPDN}8=wMoCG5mA-y? zdAVM>v0i>ry1t>MrKP@sk-m|UZc$2_ZgFK^Nn(X=Ua>OB2#6Ujsl~}fnFS@8`FRQ; zGZT~YOG|8(l(-ZW6rhGzhK53Ney)B+Vu8M)o`HUDu0FDk;u6=g68t)f zLn;eW^@CE2^Gl18Q(ena^GcMFjYvs`>n|uR$^m;PC0Rc;Cp9-UucTPtP|py>N_S7+ z0JsiN5Ts}37JzkCKKr&$OAUmWYw*YP~ifWkO!HU6QWaW~dTnciY zr;Du;$bPGo{N&6OD=^bIIoT*J%`j2d$jsPK*CZv$Qa8!S#6s6RHQCfC*(@b7$-oTB zD9^m&lEl2^RFF{>xdnQenJHH0rlx5IX-Q_fsb;Ckx+VsRM!HGnhH1J+rk05*DT&6W z#)hd#M)((Hrf23Q<{-NYWK>FKid9mYMOw0Xiji(wa$<_ENs?)*u7#ypny!&Ws*#1c zk)>r)k}24zlw>Qn{G!~%5?iIr+{E-${erx7ummW;tsDb9ZIz7l3=kp#If*4{`9-<5 zNzt7K$gWNc+%Vr6I%VrXJzYGh?-s%>CoWq_nUKP5A*l8|aUQ0B0KM7c+1aY=qr zC0GcOqk>ZlAv_2-CljPvK|ukWDXbEcAzmm>EK5xRdjl?&oROH9o|hRUsY6Q)jSE^@p`b8o2}$8Q8eF5nMN$Zmq=fIsl)#ph)OndJ7G^gusz_?8^y;dtTwZr_b!UEkXYJ&!knXOo4HKShoqm4zjM$zT zGj}dc?OnR;z_K~@%eNn1^ZLZboZgKaPwbviwx_sn&z|$gD*KNeyLNnc)rng-Pkg#{ zzJ9{_^LK8vPPlR7(fw^X4^A(C*fsIt!xxXPls^9P_GSO1moMLam@@gphc7>7PX6)Z z*WdFA|DJF7KX=Oi|No~xIJBLCfq^l}+uensgH_gpfq{Xuz$3Dlfr0M`2s2LA=96Y% zU|=ut^mS!_&LYHP!vCsky$=HeE4QbMV@SoVw|DOr&9-1*O;C!Oe(px5PtqGdU+*0p z%QmL@rsS^qUcXWG?YSx;6@y=nS>NVF8yU@4RZ(dacI9YNKtmlyAE(;n|LduFmF9Nt zc>3Jt`5o?8l7gp{&d#p1+xPGDdVAHMRolw>?e>YpiQG(z67Rg+WOQz4#^L;@mCe8Z zZCAN}-nES1t~>r{@wrttio&7Elf-&0jvKzaf1yTj_j27Lt+-UFnBSIH)e3#Ir4|1? zxaGfS=iyu3+-fcMF~1{ypS`%%z1fXp{|>XShgkJj?%-fhpV7QcbG;eA#lh^;%JFlG z1-aI(-E$^uvd$FwC(P)Z{Zz#jAF~)|&fw&hR?li)^*Vpb zoXbU%m&!lcDb?D&ucT;lTIJD-`u1h^a!YDi%|p$eRmmOts+0Z2biP{Ih1naDQi`u1 z-Rm=H^*sHZ7v>&Xd3udo{K;QCrcDoeI`hJjPqpHgBTgC|-1>ac8k@7bH?*!=YrfJg zGi^tQM*8V0<(*kQi=&#m75&yewp#AI;%eh%k>%H(TCENG=(=>Ge2k08XRlR;6SS9E zv3_jrPI8UDl;PETcHVU^ztUjCeX*zVwZAUkZ&t8zPvW&{ZUs`mkGRhKBAve2f7OF+ zRchT9mHc-5{!iP+Fz?6FlgIB}?AH7=Z)Km&VJ5v!R2o)IHzNeg)?J=kMC=6TTyUbLStG13Qo8=J4!a_=rn- zZ|VvQDV2AdQ;z<9_xXjWWm@vvBB48PGv<^inN>}a)L6G^(;8tlz05T}VTX^L6kq$| zM)A(dZ8hyl%bCicyBOTV_>W(HFH@eK)G>L_5;676ZJNL5uerQ(2Y0~}`&;?-SXSR zA5`61nmea4m!bSi-(ofYyRRSit?n#H%NNPGcBe>a&P(lxzdO{|pPiJ?6R-dJ_*;=3 z%rhC>d+(dSJHA$Q2m5ztM(s2Ax!xQb@^{#8ir3~gXy^R9$6j!NkzIx$@!kGNZ1k~{a#s`P$z9zs>EC+UW~Rv@lT@4p fSRC=7Jx}E)B)@++sqp+|(0H4ttDnm{r-UW|_T0*~ delta 1512 zcmbQCbAWe(%4AM~U6U^eh}5(0@N{tushIP2ZhieNGl6#F2+5mHyDM}Pr6u#WJ-qZt zS7gtv_y2o)xYj@6Tf(2b zJ?;DIMQ2!_mg%VVy~?aF$}6kfsoUAiW!1Ae>!95U(W6yKE2mwV`l@rz+_?S!*8Wu9 zel=S<=2&*{#D6Dpo1brJy=7o3{$g@W;E~?D$Bs$8oEx)I$~5TpY^KiXMXuLWHt=Lu znazK?ZSTArxh~6ZXpYojb zyZ2zvwAJ$&O;=2bJ1uG|T72V4-TJ4OUOiqqv1sa%#~N9){HC9pdFf@KNoR1TdQ9hx zMGHNr)%QfJIWH|TS?pD`W7^lBwM`kD_T|riR;s-H^W!2G>qnn#|5RA+&d+@olqW4C z_9tEU{=t3kT4RguExfahbK})DS6=N9n|@Pojm6a6uZs28UwJj7TXpgsy{A9?em+Sx zsGXx@dvT?VxQf_u?e|;n-Z@=ZcKp0g+vGLAMse|dmCE{x^{wlsUVH27*~cfM|8>$I zz9jKHt)ed`&vwQI9rn`FE=;uC#a!Jt{fcSdHn$ZgVgsEwwjWl#BCZghu;Ajv#Z5

(hn4B)ePtRa1J}R1u%Kf>@=nP|9g}3PI?JaSw(R9zZyQzQ zqQ9+Q!^JKC;8a~V@4osK(|@bEvUsolnaQbZxybkJVbzamci;XGo~*p+;k)xL{tuj9 zH_Vdx{PJx5xgC?9t~l9y`;_JDry@@~IyPp9tdDwk+FUq(PQ}}uQ|_d%eLjzAr`et? zOSPyr-Q!=16Hgl|Pw{(F_xJdb`DLue$*29Etm{3!A%E?ur)jK?rwc#tT=2Ah$IOZo zMLB$`DVAElx9(R+u(Zp+dGbJ{d*6AB+2LF=ew86lSh86Up89$+^{De9%LvON_fx@P zMj@6}cYkPx%dMD`<7?w%dF*ZFOI;7ks;&QL`0)kw*)o3L)U08%ldXEc(`4Z{x2*I2 zW@(x4n06>4^DHSdHJMFmXWw)ZeFYA;4ok_EM5s@ciepmChOWv-N-e;=&p7QKo zR^PMehvS~(KT}T6i&?k*%=w!&1={I5UgR$fdz#$+G|xT%A#dvdDzYR;#2v+jIa-?ZY=%sV&V10HGG1{yS^#aUtHGhx*DVMy>rUxzY|v9bkmJ(sKXvWx7Y6*RI$t!s_Q|cRg8A_&K#bp1c0ji{_jMKG(eM0+*<6 zPb$+o{UncbyXEU;D#!nOEh^ZQHSaS=nvGV`$_F0pCTA1g@TzbcJTHn=vf85ID{xhP zzsu)>>qU`DwzqO;-V!#kSY8w&ws*g>v$q}=iRjly3;rFUvixwyYR$TrXntfb7Chn87!j} zmNPubX`I6}VKXaK@WZO7_x|TS+Pk~`&$soR`|sE;Y1 Date: Tue, 18 Apr 2023 19:29:43 +0200 Subject: [PATCH 0431/1428] Fix a new wallet sometimes not seeing new transactions --- src/Wallet.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 986db30..0a42fa1 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -816,7 +816,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons const auto mnemonicBytes = mnemonic.toUtf8(); const auto pwdBytes = pwd.toUtf8(); // convert to constBuf containers. - Streaming::BufferPool pool(mnemonicBytes.size() + pwdBytes.size()); + auto &pool = Streaming::pool(mnemonicBytes.size() + pwdBytes.size()); pool.write(mnemonicBytes.constData(), mnemonicBytes.size()); auto mnemonicBuf = pool.commit(); pool.write(pwdBytes.constData(), pwdBytes.size()); @@ -833,8 +833,10 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons m_hdData->lastMainKey = -1; m_hdData->lastChangeKey = -1; QMutexLocker locker(&m_lock); - m_segment->blockSynched(startHeight); - m_segment->blockSynched(startHeight); // yes, twice + if (startHeight < 1000000) { // when its a blockheight and not a timestamp. + m_segment->blockSynched(startHeight); + m_segment->blockSynched(startHeight); // yes, twice + } deriveHDKeys(200, 200, startHeight); m_walletIsImporting = true; } @@ -1388,11 +1390,10 @@ void Wallet::headerSyncComplete() // broadcast to peers our bloom filter, which would have skipped all // time-based blocks before. rebuildBloom(); - - // make the wallet iniital sync also show something sane. - if (m_segment) - m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); } + // make the wallet iniital sync also show something sane. + if (m_segment) + m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); } void Wallet::broadcastUnconfirmed() -- 2.54.0 From bd8ded906b5d8dff7200eb6201d4b98f6d2eeedc Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 21:09:45 +0200 Subject: [PATCH 0432/1428] Move away from std::rand() Make sure everyone uses the randomness from openssl (via Flowee libraries). --- src/FloweePay.cpp | 2 +- src/Wallet.cpp | 1 - src/Wallet_spending.cpp | 4 +++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 7a157ec..8857567 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -532,7 +532,7 @@ Wallet *FloweePay::createWallet(const QString &name) quint16 id; while (true) { - id = rand(); + id = GetRandInt(0xFFFF); QString dir = QString("/wallet-%1").arg(id); if (!QFileInfo::exists(m_basedir + dir)) break; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 0a42fa1..6e1d427 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index e8f07c9..6fb6ba0 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -19,6 +19,8 @@ #include "Wallet_p.h" #include "FloweePay.h" +#include + // #define DEBUG_UTXO_SELECTION #ifdef DEBUG_UTXO_SELECTION # define logUtxo logCritical @@ -238,7 +240,7 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz auto outputs = unspentOutputs; do { Q_ASSERT(!outputs.empty()); - const int index = static_cast(rand() % outputs.size()); + const int index = GetRandInt(outputs.size()); Q_ASSERT(outputs.size() > index); const auto &out = outputs[index]; current.outputs.push_back(out.outputRef); -- 2.54.0 From 686f4ab1af2a62a51b16dfa3614da706d01ded6b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 21:21:01 +0200 Subject: [PATCH 0433/1428] Fix wallet not showing in some cases. This handles the special usecase where we replace the only existing wallet with another one. --- src/PaymentRequest.cpp | 3 +-- src/PortfolioDataProvider.cpp | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 0f8ac58..1d85efa 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -332,6 +332,5 @@ void PaymentRequest::setUseLegacyAddress(bool on) void PaymentRequest::switchAccount(AccountInfo *ai) { - assert(ai); - setWallet(ai->wallet()); + if (ai) setWallet(ai->wallet()); } diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 0f221c5..37996f2 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -36,7 +36,8 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) for (auto &w : wallets) { addWalletAccount(w); } - emit accountsChanged(); // not strictly needed, but safe to have. + selectDefaultWallet(); + emit accountsChanged(); }); } -- 2.54.0 From 644556c74a50cc2f637f0775b9d6452634f6f6a3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 21:22:21 +0200 Subject: [PATCH 0434/1428] Avoid referencing a const container as non-const That may cause the CoW container to be copied for no good reason. --- src/PortfolioDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 37996f2..fa07443 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -58,7 +58,7 @@ QList PortfolioDataProvider::accounts() const } // if the only wallet(s) are not user owned, share those with the GUI. if (answer.isEmpty() && !m_accountInfos.isEmpty()) { - for (auto *account : m_accountInfos) { + for (auto * const account : m_accountInfos) { if (account->isArchived()) answer.append(account); } -- 2.54.0 From 47b4e1ea0ac412bd6d9381c204f43cc041d039c0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 21:54:43 +0200 Subject: [PATCH 0435/1428] Synchronize the errorRed colors This adds a errorRedBg 'background' color for a popup with normal white text on top of it. --- guis/desktop/SendTransactionPane.qml | 4 ++-- guis/desktop/main.qml | 3 ++- guis/mobile/main.qml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 8a1df0b..b6ccc18 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -377,7 +377,7 @@ Item { anchors.margins: -7 color: palette.window border.width: 2 - border.color: "red" + border.color: mainWindow.errorRed radius: 10 } Flowee.ArrowPoint { @@ -385,7 +385,7 @@ Item { anchors.bottom: warningColumn.top anchors.bottomMargin: 4 rotation: -90 - color: "red" + color: mainWindow.errorRed } Column { diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 876f13d..6414ec6 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -57,7 +57,8 @@ ApplicationWindow { property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" - property color errorRed: Pay.useDarkSkin ? "#ff6568" : "red" + property color errorRed: Pay.useDarkSkin ? "#ff6568" : "#940000" + property color errorRedBg: Pay.useDarkSkin ? "#671314" : "#9f1d1f" Item { id: mainScreen diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index b86012f..c27b085 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -53,7 +53,8 @@ ApplicationWindow { property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" property color floweeGreen: "#90e4b5" - property color errorRed: Pay.useDarkSkin ? "#ff6568" : "red" + property color errorRed: Pay.useDarkSkin ? "#ff6568" : "#940000" + property color errorRedBg: Pay.useDarkSkin ? "#671314" : "#9f1d1f" StackView { id: thePile -- 2.54.0 From 33463427846691b9ea58c2c02d47b69d3beaa5c7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 18 Apr 2023 22:09:45 +0200 Subject: [PATCH 0436/1428] Add 'max funds' feature to 'build transaction' Allow the selection of all funds in a wallet to be sent to a destination. This also refreshes the UI layout a little. --- guis/mobile/AccountSelectorWidget.qml | 12 +++--- guis/mobile/PayToOthers.qml | 57 ++++++++++++++++++--------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 049c72b..5273751 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -24,12 +24,14 @@ Rectangle { x: -10 width: parent.width + 20 + // if set to true, tapping on the account has no effect. + property bool stickyAccount: false // list of actions to put in a menu when clicking on the 'balance' side. property var balanceActions: [ ] height: { if (portfolio.singleAccountSetup) - return currentWalletValue.height + 10 + return currentWalletValue.height + 20 return currentWalletValue.height + currentWalletLabel.height + 25 } color: palette.alternateBase @@ -37,13 +39,13 @@ Rectangle { Flowee.HamburgerMenu { x: 10 anchors.verticalCenter: currentWalletLabel.verticalCenter - visible: portfolio.accounts.length > 1 + visible: root.stickyAccount === false && portfolio.accounts.length > 1 } Flowee.Label { id: currentWalletLabel y: 10 - x: 20 + x: root.stickyAccount ? 10 : 20 width: parent.width - 30 text: payment.account.name visible: !portfolio.singleAccountSetup @@ -64,8 +66,8 @@ Rectangle { anchors.fill: parent onClicked: (mouse) => { if (mouse.x < parent.width / 2) { - if (portfolio.accounts.length > 1) - accountSelector.open(); + if (root.stickyAccount === false && portfolio.accounts.length > 1) + accountSelector.open(); } else if (balanceActions.length > 0) { while (priceMenu.count > 0) { priceMenu.takeItem(0); diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index e80ffcd..2f097c5 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -264,6 +264,14 @@ Page { id: destinationEditPage Page { headerText: qsTr("Edit Destination") + + property QtObject sendAllAction: QQC2.Action { + checkable: true + checked: paymentDetail.maxSelected + text: qsTr("Send All", "all money in wallet") + onTriggered: paymentDetail.maxSelected = checked + } + Flowee.Label { id: destinationLabel text: qsTr("Bitcoin Cash Address") + ":" @@ -272,8 +280,10 @@ Page { Flowee.MultilineTextField { id: destination anchors.top: destinationLabel.bottom + anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right + height: Math.max(destinationLabel.height * 3, implicitHeight) focus: true property var addressType: Pay.identifyString(text); text: paymentDetail.address @@ -313,9 +323,35 @@ Page { width: parent.width addressType: destination.addressType } + PriceInputWidget { + id: priceInput + width: parent.width + anchors.top: addressInfo.bottom + anchors.topMargin: 10 + paymentBackend: paymentDetail + fiatFollowsSats: paymentDetail.fiatFollows + onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats + } + + AccountSelectorWidget { + id: walletNameBackground + anchors.bottom: numericKeyboard.top + anchors.bottomMargin: 10 + stickyAccount: true + + balanceActions: [ sendAllAction ] + } + + NumericKeyboardWidget { + id: numericKeyboard + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 + width: parent.width + enabled: !paymentDetail.maxSelected + } Rectangle { - color: mainWindow.errorRed + color: mainWindow.errorRedBg radius: 15 width: parent.width height: warningColumn.height + 20 @@ -357,23 +393,6 @@ Page { } } } - // TODO Max button. - - PriceInputWidget { - id: priceInput - width: parent.width - anchors.bottom: numericKeyboard.top - paymentBackend: paymentDetail - fiatFollowsSats: paymentDetail.fiatFollows - onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats - } - NumericKeyboardWidget { - id: numericKeyboard - anchors.bottom: parent.bottom - anchors.bottomMargin: 15 - width: parent.width - enabled: !paymentDetail.maxSelected - } } } } @@ -492,7 +511,7 @@ Page { id: leftBackground opacity: 0 anchors.fill: parent - color: mainWindow.errorRed + color: mainWindow.errorRedBg } Rectangle { -- 2.54.0 From 34292dbe96faa23a7ffdb67929234d5951fd4f33 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 13:35:35 +0200 Subject: [PATCH 0437/1428] Remove unused parameter. --- src/NetDataProvider.cpp | 2 +- src/NetDataProvider.h | 2 +- src/main_utils.cpp | 2 +- src/main_utils_android.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 4ddc6e8..a62cde5 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -24,7 +24,7 @@ #include #include -NetDataProvider::NetDataProvider(int initialBlockHeight, QObject *parent) +NetDataProvider::NetDataProvider(QObject *parent) : QObject(parent) { connect (this, SIGNAL(peerDeleted(int)), this, SLOT(deleteNetPeer(int)), Qt::QueuedConnection); // Make this thread-safe diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index f78b151..c8f3efb 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -32,7 +32,7 @@ class NetDataProvider : public QObject, public P2PNetInterface Q_OBJECT Q_PROPERTY(QList peers READ peers NOTIFY peerListChanged) public: - explicit NetDataProvider(int initialBlockHeight, QObject *parent = nullptr); + explicit NetDataProvider(QObject *parent = nullptr); // P2PNetInterface void newPeer(int peerId, const std::string &userAgent, int startHeight, PeerAddress address) override; diff --git a/src/main_utils.cpp b/src/main_utils.cpp index cbc8fca..4647d7c 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -134,7 +134,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c { FloweePay *app = FloweePay::instance(); - NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); + NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); netData->startRefreshTimer(); diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 2aeda2c..401b386 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -97,7 +97,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) { FloweePay *app = FloweePay::instance(); - NetDataProvider *netData = new NetDataProvider(app->p2pNet()->blockHeight(), &engine); + NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); netData->startRefreshTimer(); -- 2.54.0 From 0559a3b79d9e1a727a939acf67a00f812a636a98 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 13:36:34 +0200 Subject: [PATCH 0438/1428] Fix constness cppcheck issue Avoid using a non-const iterator on a const container. --- src/NetDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index a62cde5..b267fb2 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -104,7 +104,7 @@ void NetDataProvider::punishmentChanged(int peerId) QList NetDataProvider::peers() const { QList answer; - for (auto *p : m_peers) { + for (auto * const p : m_peers) { answer.append(p); } return answer; -- 2.54.0 From ec065fe32a48c65a4f2e6c036ef37695de693464 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 13:53:50 +0200 Subject: [PATCH 0439/1428] fix whitespace indent --- src/NotificationManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 54e7eb3..1b899ca 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -155,8 +155,8 @@ void NotificationManager::walletUpdated() if (m_openingNewFundsNotification) { // we are currently (async) opening a notification, wait until its actually open. - QTimer::singleShot(50, this, SLOT(walletUpdated())); - return; + QTimer::singleShot(50, this, SLOT(walletUpdated())); + return; } int64_t deposited = 0; -- 2.54.0 From e9d425dc355750ca8259145993b6088d13ee80b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 13:54:49 +0200 Subject: [PATCH 0440/1428] Notice a TODO for this to-be-rewritten class The console output shows a warning about this code, so better note down the source of this issue. --- src/PaymentRequest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 1d85efa..a9ec168 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -183,6 +183,7 @@ void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight) if (m_paymentState == Unpaid && m_amountSeen >= m_amountRequested) { if (blockHeight == -1) { setPaymentState(PaymentSeen); + // TODO, the usage of this timer won't work due to Qt threading issues QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() { if (m_paymentState == PaymentSeen) setPaymentState(PaymentSeenOk); -- 2.54.0 From 22d3cc1c5f3e8315d78a8cd88d775bee9d9e38a1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 13:56:04 +0200 Subject: [PATCH 0441/1428] Fix accidental refactor. Moved this yesterday, should not have. This method is called every single time the app is behind and reaches tip. --- src/Wallet.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 6e1d427..4a7b3de 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1363,6 +1363,7 @@ void Wallet::setLastSynchedBlockHeight(int height) } } +// called every app-start we reach the tip void Wallet::headerSyncComplete() { /* @@ -1389,10 +1390,10 @@ void Wallet::headerSyncComplete() // broadcast to peers our bloom filter, which would have skipped all // time-based blocks before. rebuildBloom(); + // make the wallet initial sync also show something sane. + if (m_segment) + m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); } - // make the wallet iniital sync also show something sane. - if (m_segment) - m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); } void Wallet::broadcastUnconfirmed() -- 2.54.0 From 5d2671b772e8cb15add957598cf376169feef46d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 15:04:39 +0200 Subject: [PATCH 0442/1428] Handle fast tapping on the keyboard This solves the UX issue where tapping a certain button fast in sequence, we only acted on every other one. Turns out, half were registered as 'double clicks'. --- guis/mobile/NumericKeyboardWidget.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 9eada77..63fa29c 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -60,7 +60,7 @@ Flow { MouseArea { anchors.fill: parent - onClicked: { + function doSomething() { let editor = priceInput.editor; if (index < 9) // these are digits var shake = !editor.insertNumber("" + (index + 1)); @@ -73,6 +73,8 @@ Flow { if (shake) priceInput.shake(); } + onClicked: doSomething(); + onDoubleClicked: doSomething(); } } } -- 2.54.0 From 534ea13b7b647e0438cc1a36576ea370dd4ff7e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Apr 2023 15:28:10 +0200 Subject: [PATCH 0443/1428] Handle waking up too. --- src/FloweePay.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8857567..0276c12 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -95,12 +95,27 @@ FloweePay::FloweePay() auto guiApp = qobject_cast(QCoreApplication::instance()); assert(guiApp); connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { - if (state == Qt::ApplicationInactive) { - logInfo() << "App went Inactive. Start saving data"; + if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { + logInfo() << "App no longer active. Start saving data"; saveAll(); p2pNet()->saveData(); saveData(); } + else if (state == Qt::ApplicationActive) { + /* + * We are brought back to the foreground. + * + * On different iterations of Android the behavior can differ quite substantially + * when it comes to being allowed to continue using resources while not being active. + * As such there is no real way to know what being inactive has done to our network + * connections. They may have been kept alive for whatever time we were + * not active, they may all have been removed or timed out already. + * + * What we'll do is to start the actions again which will check up on our connections + * and create new ones or download blocks if we need to. + */ + p2pNet()->start(); + } }); #endif -- 2.54.0 From 4384d73d1070e299a6705a0179444c28923313e1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 20 Apr 2023 10:04:13 +0200 Subject: [PATCH 0444/1428] increase version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index bb68b34..6679f44 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,5 +1,5 @@ - + diff --git a/src/main.cpp b/src/main.cpp index 27c7cf9..9124da7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.04.0"); + qapp.setApplicationVersion("2023.04.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 924774eeca2011d8fb92e96540f82bbadde122bc Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Apr 2023 12:24:23 +0200 Subject: [PATCH 0445/1428] Honor offline requests for this new feature. --- src/FloweePay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 0276c12..ef97010 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -114,7 +114,8 @@ FloweePay::FloweePay() * What we'll do is to start the actions again which will check up on our connections * and create new ones or download blocks if we need to. */ - p2pNet()->start(); + if (!m_offline) + p2pNet()->start(); } }); #endif -- 2.54.0 From c89f90d253e528c3e6de47069984578e7851c2e8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Apr 2023 15:36:17 +0200 Subject: [PATCH 0446/1428] FIx startup race condition Ah, seems that the signal 'ApplicationActive' is also sent at the app startup, which this code didn't handle too well. Now we ignore this signal until we are property started. --- src/FloweePay.cpp | 3 ++- src/FloweePay.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index ef97010..db0fe3c 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -114,7 +114,7 @@ FloweePay::FloweePay() * What we'll do is to start the actions again which will check up on our connections * and create new ones or download blocks if we need to. */ - if (!m_offline) + if (!m_offline && m_loadingCompleted) p2pNet()->start(); } }); @@ -314,6 +314,7 @@ void FloweePay::loadingCompleted() if (!m_offline) m_prices->start(); } + m_loadingCompleted = true; emit loadComplete(); } diff --git a/src/FloweePay.h b/src/FloweePay.h index 96ca652..08cbb22 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -315,6 +315,7 @@ private: int m_windowWidth = 500; int m_windowHeight = 500; int m_fontScaling = 100; + bool m_loadingCompleted = false; // 'init()' completed bool m_darkSkin = true; bool m_createStartWallet = false; bool m_hideBalance = false; -- 2.54.0 From cd62fbcfe5d3991ef48aa4cbb7501da13a099038 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Apr 2023 14:18:57 +0200 Subject: [PATCH 0447/1428] Move the info popup above the rest We no longer show the feedback text below the address label and additionally the tapping of the address label will also cause the copying to be started. --- guis/Flowee/QRWidget.qml | 75 ++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 109019d..07e0874 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -26,6 +26,12 @@ Item { implicitHeight: qrImage.width + addressLine.height opacity: root.request == null || root.request.state === PaymentRequest.Unpaid ? 1: 0 + function handleOnClicked() { + Pay.copyToClipboard(root.request.qr) + // invert the feedback so a second tap removes the feedback again. + clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 + } + Image { id: qrImage source: root.request == null ? "" : "image://qr/" + root.request.qr @@ -36,40 +42,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: { - Pay.copyToClipboard(root.request.qr) - // invert the feedback so a second tap removes the feedback again. - clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 - } - } - - Rectangle { - id: clipboardFeedback - opacity: 0 - width: feedbackText.width + 20 - height: feedbackText.height + 14 - radius: 10 - color: Pay.useDarkSkin ? "#333" : "#ddd" - anchors.bottom: parent.bottom - anchors.bottomMargin: -8 - anchors.horizontalCenter: parent.horizontalCenter - - Label { - id: feedbackText - x: 10 - y: 10 - text: qsTr("Copied to clipboard") - wrapMode: Text.WordWrap - } - - Behavior on opacity { OpacityAnimator {} } - - /// after 8 seconds, remove feedback. - Timer { - interval: 8000 - running: clipboardFeedback.opacity >= 1 - onTriggered: clipboardFeedback.opacity = 0 - } + onClicked: root.handleOnClicked() } } Rectangle { @@ -100,6 +73,40 @@ Item { font.pixelSize: 20 // max fontSizeMode: Text.HorizontalFit // fit in width } + + MouseArea { + anchors.fill: parent + onClicked: root.handleOnClicked() + } + } + + Rectangle { + id: clipboardFeedback + opacity: 0 + width: feedbackText.width + 20 + height: feedbackText.height + 14 + radius: 10 + color: Pay.useDarkSkin ? "#333" : "#ddd" + anchors.bottom: qrImage.bottom + anchors.bottomMargin: -8 + anchors.horizontalCenter: qrImage.horizontalCenter + + Label { + id: feedbackText + x: 10 + y: 7 + text: qsTr("Copied to clipboard") + wrapMode: Text.WordWrap + } + + Behavior on opacity { OpacityAnimator {} } + + /// after 8 seconds, remove feedback. + Timer { + interval: 8000 + running: clipboardFeedback.opacity >= 1 + onTriggered: clipboardFeedback.opacity = 0 + } } Behavior on opacity { OpacityAnimator {} } -- 2.54.0 From 6ac8005ea78891c1a3f0c44e77bf09b659bc6470 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 21:11:33 +0200 Subject: [PATCH 0448/1428] Remove dupliacte option. --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a62c0a..3138fac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,6 @@ project(flowee_lib) option(local_qml "Allow local QML loading" OFF) -option(networkLog "Include network-logging client" OFF) option(NetworkLogClient "Include the network based logging in the executables" OFF) add_definitions(-DLOG_DEFAULT_SECTION=10000) -- 2.54.0 From 167c590b781fa26387a67b2c1a5a908a02ef2523 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 21:11:54 +0200 Subject: [PATCH 0449/1428] Use triple equals in JS --- guis/desktop/AccountConfigMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 0147510..98cb746 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -60,7 +60,7 @@ ConfigItem { var onMainView = (accountOverlay.state === "showTransactions") if (onMainView || (accountOverlay.state === "accountDetails" - && portfolio.current != root.account)) + && portfolio.current !== root.account)) items.push(detailsAction); var encrypted = root.account.needsPinToOpen; var decrypted = root.account.isDecrypted; -- 2.54.0 From 1b2ad144c1cf67ba0de28f2cc06db20f4c11393d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 21:36:54 +0200 Subject: [PATCH 0450/1428] Avoid hitting assert QML tends to call our getters even in unrealistic scenarios, which means an extra check is useful to avoid hitting an assert, or doing an out-of- bounds access. --- src/WalletHistoryModel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 4bea509..81030f8 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -261,6 +261,8 @@ QString WalletHistoryModel::dateForItem(qreal offset) const if (std::isnan(offset) || offset < 0 || offset > 1.0) return QString(); const size_t row = std::round(offset * m_rowsProxy.size()); + if (row >= m_rowsProxy.size()) + return QString(); auto item = m_wallet->m_walletTransactions.at(txIndexFromRow(row)); if (item.minedBlockHeight <= 0) return QString(); -- 2.54.0 From 374480bfdb7ee3b2901a0a8a44c1e2f562f32da3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 21:46:43 +0200 Subject: [PATCH 0451/1428] Detect problem and bail early. When the wallet already noticed a new block but the blockchain database doesn't have it, we should just bail. The only way this is likely to happen is because a previous run of the wallet didn't write out its data. We keep the assert for that case, but in release builds we are now more robust. --- src/WalletHistoryModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 81030f8..930e30b 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -377,6 +377,10 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) timestamp = secsSinceEpochFor(blockheight); } assert(timestamp > 0); + if (timestamp == 0) { + // some inconsistency between wallet and libp2p. + return; + } if (!m_groups.back().add(txIndex, timestamp)) { // didn't fit, make a new group and add it there. -- 2.54.0 From 1e73c5bee5e097f4d71f5381deda0ac5159490ad Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 27 Apr 2023 17:29:35 +0200 Subject: [PATCH 0452/1428] Step one of rebuilding the payment-requests This mostly removes the less than successful architecture. This architecture stems from my first attempts at mixing C++ and QML, and its not great. As we can see from the removals, it touches a lot of places and especially the wallet owning them is messy, but in QML we have to do a lot of null pointer checks, also not exactly readable. Lets try something different. --- guis/mobile/ReceiveTab.qml | 204 +------------------------------------ src/AccountInfo.cpp | 15 --- src/AccountInfo.h | 13 --- src/PaymentRequest.cpp | 169 +++++++----------------------- src/PaymentRequest.h | 56 ++-------- src/Wallet.cpp | 159 +---------------------------- src/Wallet.h | 13 --- src/Wallet_encryption.cpp | 11 -- src/Wallet_p.h | 12 --- 9 files changed, 50 insertions(+), 602 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 7b7ab95..13900db 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -30,209 +30,9 @@ FocusScope { focus: true property QtObject account: portfolio.current - /* - TZ: I apologize for the design, I made it when I just had learned QML / cpp mixing. - This is a quick port from the desktop one. In general this component needs - a redesign so we avoid lots of null pointer checks and similar logic that really - should be hidden from the UI layer. - */ - - onAccountChanged: { - if (qr.request != null) { - if (qr.request.state === PaymentRequest.Unpaid) - qr.request.switchAccount(portfolio.current); - else - qr.request = null; - } - } - onActiveFocusChanged: { - if (activeFocus && qr.request == null) { - qr.request = account.createPaymentRequest(receiveTab) - } + PaymentRequest { + id: request } - function reset() { - if (qr.request.saveState !== PaymentRequest.Stored) - qr.request.destroy(); - qr.request = account.createPaymentRequest(receiveTab) - description.text = ""; - // bitcoinValueField.reset(); - } - - Flowee.Label { - id: instructions - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 - text: qsTr("Share this QR to receive") - opacity: 0.5 - } - - Flowee.QRWidget { - id: qr - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: instructions.bottom - anchors.topMargin: 20 - width: parent.width - } - - // the "payment received" screen. - Rectangle { - anchors.top: parent.top - anchors.topMargin: 20 - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: qr.bottom - radius: 10 - gradient: Gradient { - GradientStop { - position: 0.6 - color: { - if (qr.request == null) - return "red" - var state = qr.request.state; - if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) - return palette.base - if (state === PaymentRequest.DoubleSpentSeen) - return "#640e0f" // red - return "#3e8b4e" // in all other cases: green - } - Behavior on color { ColorAnimation {} } - } - GradientStop { - position: 0.1 - color: palette.base - } - } - opacity: qr.request == null ? 0 : (qr.request.state === PaymentRequest.Unpaid ? 0: 1) - - // animating timer to indicate our checking the security of the transaction. - // (i.e. waiting for the double spent proof) - Item { - id: feedback - width: 160 - height: 160 - y: (parent.height - height) / 3 * 2 - visible: qr.request == null || qr.request.state !== PaymentRequest.DoubleSpentSeen - Shape { - id: circleShape - anchors.fill: parent - opacity: progressCircle.sweepAngle <= 340 ? 0 : 1 - x: 40 - ShapePath { - strokeWidth: 20 - strokeColor: "#9ea0b0" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 100; startY: 10 - - PathAngleArc { - id: progressCircle - centerX: 80 - centerY: 80 - radiusX: 70; radiusY: 70 - startAngle: -80 - sweepAngle: qr.request == null || qr.request.state === PaymentRequest.Unpaid ? 0: 340 - - Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } - } - } - - Flowee.Label { - anchors.centerIn: parent - text: qsTr("Checking") // checking security - } - Behavior on opacity { OpacityAnimator {} } - } - - Flowee.Label { - color: "green" - text: "✔" - opacity: 1 - circleShape.opacity - font.pixelSize: 130 - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - } - } - - Flowee.Label { - id: feedbackLabel - text: { - if (qr.request == null) - return ""; - var s = qr.request.state; - if (s === PaymentRequest.DoubleSpentSeen) - // double-spent-proof received - return qsTr("Transaction high risk") - if (s === PaymentRequest.PaymentSeen) - return qsTr("Payment Seen") - if (s === PaymentRequest.PaymentSeenOk) - return qsTr("Payment Accepted") - if (s === PaymentRequest.Confirmed) - return qsTr("Payment Settled") - return "INTERNAL ERROR"; - } - width: parent.width - 40 - anchors.verticalCenter: feedback.verticalCenter - anchors.left: feedback.visible ? feedback.right : parent.left - anchors.leftMargin: 20 - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pointSize: 20 - } - Flowee.Label { - visible: qr.request == null || qr.request.state === PaymentRequest.DoubleSpentSeen - anchors.top: feedbackLabel.bottom - anchors.right: parent.right - anchors.rightMargin: 10 - width: parent.width - 20 - horizontalAlignment: Qt.AlignRight - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: qsTr("Instant payment failed. Wait for confirmation. (double spent proof received)") - } - - Behavior on opacity { OpacityAnimator {} } - } - - // entry-fields - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: qr.bottom - anchors.topMargin: 30 - Flowee.Label { - text: qsTr("Description") + ":" - } - Flowee.TextField { - id: description - Layout.fillWidth: true - enabled: qr.request != null && qr.request.state === PaymentRequest.Unpaid - onTextChanged: qr.request.message = text - } - - /* - Flowee.Label { - id: payAmount - text: qsTr("Amount") + ":" - } - RowLayout { - spacing: 10 - Flowee.BitcoinValueField { - id: bitcoinValueField - enabled: qr.request != null && qr.request.state === PaymentRequest.Unpaid - onValueChanged: qr.request.amount = value - } - Flowee.Label { - Layout.alignment: Qt.AlignBaseline - anchors.baselineOffset: bitcoinValueField.baselineOffset - text: Fiat.formattedPrice(bitcoinValueField.value, Fiat.price) - } - } - */ - - Flowee.Button { - Layout.alignment: Qt.AlignRight - text: qr.request == null || qr.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") - onClicked: reset(); - } - } } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index b1994aa..42c8af0 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -40,7 +40,6 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) emit lastBlockSynchedChanged(); emit timeBehindChanged(); }, Qt::QueuedConnection); - connect(wallet, SIGNAL(paymentRequestsChanged()), this, SIGNAL(paymentRequestsChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(userOwnedChanged()), this, SIGNAL(userOwnedChanged()), Qt::QueuedConnection); connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); @@ -285,15 +284,6 @@ bool AccountInfo::userOwnedWallet() return m_wallet->userOwnedWallet(); } -QList AccountInfo::paymentRequests() const -{ - QList answer; - for (auto *pr : m_wallet->paymentRequests()) { - answer.append(pr); - } - return answer; -} - TransactionInfo* AccountInfo::txInfo(int walletIndex, QObject *parent) { Q_ASSERT(parent); @@ -302,11 +292,6 @@ TransactionInfo* AccountInfo::txInfo(int walletIndex, QObject *parent) return info; } -QObject *AccountInfo::createPaymentRequest(QObject *parent) -{ - return new PaymentRequest(m_wallet, parent); -} - void AccountInfo::encryptPinToPay(const QString &password) { m_wallet->setEncryption(Wallet::SecretsEncrypted, password); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index f43b6d5..bf41921 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -57,7 +57,6 @@ class AccountInfo : public QObject Q_PROPERTY(bool isSingleAddressAccount READ isSingleAddressAccount NOTIFY encryptionChanged) Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) - Q_PROPERTY(QList paymentRequests READ paymentRequests NOTIFY paymentRequestsChanged) Q_PROPERTY(QString mnemonic READ hdWalletMnemonic NOTIFY encryptionChanged) Q_PROPERTY(QString hdDerivationPath READ hdDerivationPath NOTIFY encryptionChanged) Q_PROPERTY(QString xpub READ xpub NOTIFY encryptionChanged) @@ -106,18 +105,7 @@ public: // maps to Wallet::userOwnedWallet bool userOwnedWallet(); - /** - * All payment requests that are created for this account. - */ - QList paymentRequests() const; - Q_INVOKABLE TransactionInfo* txInfo(int walletIndex, QObject *parent); - /** - * Start a new payment-request - * QML callers should pass a panel as parent, preferably one that is loaded. - */ - Q_INVOKABLE QObject* createPaymentRequest(QObject *parent); - Q_INVOKABLE void encryptPinToPay(const QString &password); Q_INVOKABLE void encryptPinToOpen(const QString &password); Q_INVOKABLE bool decrypt(const QString &password); @@ -164,7 +152,6 @@ signals: void lastBlockSynchedChanged(); void timeBehindChanged(); void isPrimaryAccountChanged(); - void paymentRequestsChanged(); void userOwnedChanged(); void isArchivedChanged(); void hasFreshTransactionsChanged(); diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index a9ec168..079505f 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -29,14 +29,6 @@ PaymentRequest::PaymentRequest(QObject *parent) : QObject(parent), m_wallet(nullptr) { -} - -PaymentRequest::PaymentRequest(Wallet *wallet, QObject *parent) - : QObject(parent), - m_wallet(nullptr) -{ - assert(wallet); - setWallet(wallet); #if 0 // by enabling this you can simulate the payment request being fulfilled QTimer::singleShot(5000, [=]() { @@ -47,33 +39,26 @@ PaymentRequest::PaymentRequest(Wallet *wallet, QObject *parent) }); }); #endif -} -// constructor used to 'load' a request, already owned by the wallet. -PaymentRequest::PaymentRequest(Wallet *wallet, int /* type */) - : QObject(wallet), - m_wallet(wallet), - m_saveState(Stored) -{ - // TODO remember type when we start to support more than just BIP21 - m_unusedRequest = false; + connect (this, &PaymentRequest::paymentStateChanged, this, [=]() { + if (m_paymentState == PaymentSeen) { + // unconfirmed fully paid, but lets see in a couple of seconds... + QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() { + // A DSP can change the status, if its still unchanged then we + // can safely move to the 'Ok' state. + if (m_paymentState == PaymentSeen) + setPaymentState(PaymentSeenOk); + }); + } + }, Qt::QueuedConnection); } -PaymentRequest::~PaymentRequest() -{ - // free address again, if the ownership hasn't moved to be the wallet itself. - if (m_unusedRequest) - setWallet(nullptr); -} - - void PaymentRequest::setPaymentState(PaymentState newState) { if (newState == m_paymentState) return; m_paymentState = newState; - m_dirty = true; emit paymentStateChanged(); } @@ -81,13 +66,6 @@ QString PaymentRequest::address() const { if (m_address.IsNull()) return QString(); - if (m_useLegacyAddressFormat) { - CBase58Data legacy; - legacy.setData(m_address, CBase58Data::PubkeyType, - FloweePay::instance()->chain() == P2PNet::MainChain - ? CBase58Data::Mainnet : CBase58Data::Testnet); - return QString::fromStdString(legacy.ToString()); - } CashAddress::Content c; c.hash = std::vector(m_address.begin(), m_address.end()); c.type = CashAddress::PUBKEY_TYPE; @@ -98,34 +76,23 @@ void PaymentRequest::setWallet(Wallet *wallet) { if (m_wallet == wallet) return; - if (!m_unusedRequest && wallet != nullptr) { - logFatal() << "Can't change a wallet on an already saved payment request"; - assert(m_unusedRequest); - return; - } + + if (!m_incomingOutputRefs.isEmpty()) + throw std::runtime_error("Not allowed to change wallet on partially fulfilled request"); + + const bool closedWAllet = wallet->encryption() == Wallet::FullyEncrypted && !wallet->isDecrypted(); + if (closedWAllet) + throw std::runtime_error("Payment Request can't use an encrypted wallet"); if (m_wallet) { disconnect (m_wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged())); - auto w = m_wallet; - m_wallet = nullptr; // avoid recursion in the next line. - w->removePaymentRequest(this); if (m_paymentState == Unpaid) - w->unreserveAddress(m_privKeyId); + m_wallet->unreserveAddress(m_privKeyId); } - // if the wallet is encrypted we don't use it. m_wallet = wallet; if (m_wallet) { - const bool closedWAllet = m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted(); - if (closedWAllet) { - // a closed wallet is barely a husk of a data-structure. So a payment request makes no sense for it. - // but we can listen to the signal and after that initialize our request. - m_address = KeyId(); - } - else { - m_privKeyId = m_wallet->reserveUnusedAddress(m_address); - m_wallet->addPaymentRequest(this); - } + m_privKeyId = m_wallet->reserveUnusedAddress(m_address); connect (m_wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged())); } emit walletChanged(); @@ -136,21 +103,11 @@ void PaymentRequest::walletEncryptionChanged() { assert (m_wallet); const bool closedWAllet = m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted(); - if (closedWAllet) { - m_address = KeyId(); - } - else { - m_privKeyId = m_wallet->reserveUnusedAddress(m_address); - m_wallet->addPaymentRequest(this); - } + if (closedWAllet) + setWallet(nullptr); emit qrCodeStringChanged(); } -qint64 PaymentRequest::amountSeen() const -{ - return m_amountSeen; -} - PaymentRequest::PaymentState PaymentRequest::paymentState() const { return m_paymentState; @@ -159,9 +116,7 @@ PaymentRequest::PaymentState PaymentRequest::paymentState() const void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight) { assert(value > 0); - if (m_wallet->isSingleAddressWallet() - && m_unusedRequest && m_message.isEmpty() - && m_amountRequested == 0) { + if (m_wallet->isSingleAddressWallet()) { // This is a completely empty payment-request and since we are // connected to a single-address wallet it is common for transactions // to arrive that match our 'reserved' address. @@ -178,16 +133,10 @@ void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight) } m_incomingOutputRefs.append(ref); m_amountSeen += value; - m_dirty = true; if (m_paymentState == Unpaid && m_amountSeen >= m_amountRequested) { if (blockHeight == -1) { setPaymentState(PaymentSeen); - // TODO, the usage of this timer won't work due to Qt threading issues - QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() { - if (m_paymentState == PaymentSeen) - setPaymentState(PaymentSeenOk); - }); } else { setPaymentState(Confirmed); } @@ -200,46 +149,11 @@ void PaymentRequest::paymentRejected(uint64_t ref, int64_t value) if (!m_incomingOutputRefs.contains(ref)) return; m_amountSeen -= value; - m_dirty = true; if (m_paymentState >= PaymentSeen && m_amountSeen < m_amountRequested) setPaymentState(Unpaid); emit amountSeenChanged(); } -bool PaymentRequest::stored() const -{ - return !m_unusedRequest; -} - -void PaymentRequest::setStored(bool on) -{ - if (on != m_unusedRequest) - return; - m_unusedRequest = !on; - setParent(m_wallet); - setSaveState(on ? Stored : Temporary); - if (!on) { - m_wallet->removePaymentRequest(this); - // deleteLater(); // possible memory leak, but better than crashes - } - - emit storedChanged(); -} - -PaymentRequest::SaveState PaymentRequest::saveState() const -{ - return m_saveState; -} - -void PaymentRequest::setSaveState(SaveState saveState) -{ - if (m_saveState == saveState) - return; - m_saveState = saveState; - m_dirty = true; - emit saveStateChanged(); -} - QString PaymentRequest::message() const { return m_message; @@ -250,12 +164,11 @@ void PaymentRequest::setMessage(const QString &message) if (m_message == message) return; m_message = message; - m_dirty = true; emit messageChanged(); emit qrCodeStringChanged(); } -void PaymentRequest::setAmountFP(double amount) +void PaymentRequest::setAmount(double amount) { qint64 newAmount = amount; if (newAmount == m_amountRequested) @@ -265,21 +178,16 @@ void PaymentRequest::setAmountFP(double amount) emit qrCodeStringChanged(); } -double PaymentRequest::amountFP() const +double PaymentRequest::amount() const { return m_amountRequested; } -double PaymentRequest::amountSeenFP() const +double PaymentRequest::amountSeen() const { return m_amountSeen; } -qint64 PaymentRequest::amount() const -{ - return m_amountRequested; -} - QString PaymentRequest::qrCodeString() const { if (m_address.IsNull()) @@ -317,21 +225,14 @@ QString PaymentRequest::qrCodeString() const return rc; } -bool PaymentRequest::useLegacyAddress() +void PaymentRequest::clear() { - return m_useLegacyAddressFormat; -} - -void PaymentRequest::setUseLegacyAddress(bool on) -{ - if (on == m_useLegacyAddressFormat) - return; - m_useLegacyAddressFormat = on; - emit legacyChanged(); - emit qrCodeStringChanged(); -} - -void PaymentRequest::switchAccount(AccountInfo *ai) -{ - if (ai) setWallet(ai->wallet()); + m_wallet = nullptr; + m_message.clear(); + m_address = KeyId(); + m_privKeyId = -1; + m_amountRequested = 0; + m_amountSeen = 0; + m_paymentState = Unpaid; + m_incomingOutputRefs.clear(); } diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 0905691..1390596 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,12 +36,9 @@ class PaymentRequest : public QObject Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) Q_PROPERTY(QString qr READ qrCodeString NOTIFY qrCodeStringChanged) - Q_PROPERTY(double amount READ amountFP WRITE setAmountFP NOTIFY amountChanged) - Q_PROPERTY(double amountSeen READ amountSeenFP NOTIFY amountSeenChanged) - Q_PROPERTY(bool legacy READ useLegacyAddress WRITE setUseLegacyAddress NOTIFY legacyChanged) - Q_PROPERTY(SaveState saveState READ saveState WRITE setSaveState NOTIFY saveStateChanged) + Q_PROPERTY(double amount READ amount WRITE setAmount NOTIFY amountChanged) + Q_PROPERTY(double amountSeen READ amountSeen NOTIFY amountSeenChanged) Q_PROPERTY(PaymentState state READ paymentState NOTIFY paymentStateChanged) - Q_PROPERTY(bool stored READ stored WRITE setStored NOTIFY storedChanged) public: /// The state of this payment enum PaymentState { @@ -51,39 +48,22 @@ public: PaymentSeenOk, //< A payment has been seen, we waited and no DSP came. Confirmed //< We got paid. }; - enum SaveState { - Temporary, - Stored - }; - Q_ENUM(SaveState PaymentState) + Q_ENUM(PaymentState) - /// Dummy constructor. PaymentRequest(QObject *parent = nullptr); - explicit PaymentRequest(Wallet *wallet, QObject *parent = nullptr); - ~PaymentRequest(); QString message() const; void setMessage(const QString &message); /// Set the amount requested (as floating point) in sats. - void setAmountFP(double amount); + void setAmount(double amount); /// return the amount requested (in sats) - double amountFP() const; + double amount() const; /// return the amount received (in sats) - double amountSeenFP() const; - /// return the amount requested (in sats) - qint64 amount() const; - /// return the amount received (in sats) - qint64 amountSeen() const; + double amountSeen() const; QString qrCodeString() const; - bool useLegacyAddress(); - void setUseLegacyAddress(bool on); - - SaveState saveState() const; - void setSaveState(PaymentRequest::SaveState saveState); - PaymentState paymentState() const; /** @@ -100,15 +80,7 @@ public: */ void paymentRejected(uint64_t ref, int64_t value); - bool stored() const; - /// if /a on, mark payment request as one to store and keep around until fulfilled or deleted - void setStored(bool on); - - /** - * This ties the request to a different wallet. - * \sa setWallet() - */ - Q_INVOKABLE void switchAccount(AccountInfo *ai); + Q_INVOKABLE void clear(); void setWallet(Wallet *wallet); @@ -120,21 +92,13 @@ signals: void qrCodeStringChanged(); void amountChanged(); void amountSeenChanged(); - void legacyChanged(); - void saveStateChanged(); void paymentStateChanged(); void walletChanged(); - void storedChanged(); - void addressChanged(); private slots: void walletEncryptionChanged(); -protected: - friend class Wallet; - explicit PaymentRequest(Wallet *wallet, int paymentType); - private: void setPaymentState(PaymentState newState); @@ -142,12 +106,8 @@ private: QString m_message; KeyId m_address; int m_privKeyId = -1; // refers to the Wallets list of private keys - bool m_unusedRequest = true; ///< true as long as the user did not decide to save the request - bool m_useLegacyAddressFormat = false; - bool m_dirty = true; // true is state changed and we need saving. qint64 m_amountRequested = 0; qint64 m_amountSeen = 0; - SaveState m_saveState = Temporary; PaymentState m_paymentState = Unpaid; QList m_incomingOutputRefs; // see Wallet::OutputRef diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 4a7b3de..2663ed8 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -18,7 +18,6 @@ #include "Wallet.h" #include "Wallet_p.h" #include "FloweePay.h" -#include "PaymentRequest.h" #include #include @@ -101,12 +100,6 @@ Wallet::~Wallet() // these return instantly if nothing has to be saved. saveSecrets(); saveWallet(); - // tell the payment requests that we are no more. (and avoids callbacks to a deleted object) - auto copy(m_paymentRequests); - for (auto prData = copy.begin(); prData != copy.end(); ++prData) { - assert(prData->pr); - prData->pr->setWallet(nullptr); - } } Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, const uint256 &txid, std::map &types, P2PNet::Notification *notifier) const @@ -377,14 +370,7 @@ void Wallet::newTransaction(const Tx &tx) key += i->first; logDebug(LOG_WALLET) << " inserting output"<< i->first << Log::Hex << "TxIndex:" << i->second.walletSecretId << "outRef:" << key; m_unspentOutputs.insert(std::make_pair(key, i->second.value)); - - const int privKeyId = i->second.walletSecretId; - for (auto prData : qAsConst(m_paymentRequests)) { - if (prData.pr->m_privKeyId == privKeyId) { - prData.pr->addPayment(key, i->second.value); - wtx.userComment = prData.pr->message(); - } - } + // TODO somehow make payment requests know about this. } // and remember the transaction @@ -521,13 +507,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: m_secretsChanged = true; } - // check the payment requests - for (auto prData : qAsConst(m_paymentRequests)) { - if (prData.pr->m_privKeyId == privKeyId) { - prData.pr->addPayment(key, i->second.value, blockHeight); - wtx.userComment = prData.pr->message(); - } - } + // TODO somehow make payment requests know about this. } @@ -580,12 +560,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: if (utxo != m_unspentOutputs.end()) m_unspentOutputs.erase(utxo); - // check the payment requests - const int privKeyId = i->second.walletSecretId; - for (auto prData : qAsConst(m_paymentRequests)) { - if (prData.pr->m_privKeyId == privKeyId) - prData.pr->paymentRejected(key, i->second.value); - } + // TODO somehow make payment requests know about this rejection. } } @@ -915,45 +890,6 @@ void Wallet::performUpgrades() } } -QList Wallet::paymentRequests() const -{ - QMutexLocker locker(&m_lock); - QList answer; - for (auto prData : m_paymentRequests) { - answer.append(prData.pr); - } - return answer; -} - -void Wallet::addPaymentRequest(PaymentRequest *pr) -{ - QMutexLocker locker(&m_lock); - for (auto prData : m_paymentRequests) { - if (prData.pr == pr) - return; - } - PRData data; - data.pr = pr; - data.saved = false; - m_paymentRequests.append(data); - emit paymentRequestsChanged(); -} - -void Wallet::removePaymentRequest(PaymentRequest *pr) -{ - QMutexLocker locker(&m_lock); - for (auto prData = m_paymentRequests.cbegin(); prData != m_paymentRequests.cend(); ++prData) { - if (prData->pr == pr) { - if (prData->saved) - m_walletChanged = true; - m_paymentRequests.erase(prData); - pr->setWallet(nullptr); // note, this is recursive. - emit paymentRequestsChanged(); - return; - } - } -} - int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript) const { std::vector > vSolutions; @@ -1789,7 +1725,6 @@ void Wallet::loadWallet() Output output; QSet newTx; int highestBlockHeight = 0; - PRData pr; OutputRef paymentRequestRef; while (parser.next() == Streaming::FoundTag) { if (parser.tag() == WalletPriv::Separator) { @@ -1920,53 +1855,6 @@ void Wallet::loadWallet() else if (parser.tag() == WalletPriv::WalletIsImporting) { m_walletIsImporting = parser.boolData(); } - else if (parser.tag() == WalletPriv::PaymentRequestType) { - pr.pr = new PaymentRequest(this, parser.intData()); - pr.saved = true; - m_paymentRequests.append(pr); - paymentRequestRef = OutputRef(); - } - else if (parser.tag() == WalletPriv::PaymentRequestAddress) { - // we assert on pr being null here and below based on the idea that the loaded file - // is private and trusted. The asserts are here to make sure that the saving code - // matches the loading, in production there is then no need to doubt the correctness - // of the loaded data. - assert(pr.pr); - pr.pr->m_privKeyId = parser.intData(); - auto i = m_walletSecrets.find(pr.pr->m_privKeyId); - if (i != m_walletSecrets.end()) { - i->second.reserved = true; - } else { - logFatal(LOG_WALLET) << "PaymentRequest refers to non-existing wallet-secret!"; - } - } - else if (parser.tag() == WalletPriv::PaymentRequestMessage) { - assert(pr.pr); - auto data = parser.bytesDataBuffer(); - pr.pr->m_message = QString::fromUtf8(data.begin(), data.size()); - } - else if (parser.tag() == WalletPriv::PaymentRequestAmount) { - assert(pr.pr); - pr.pr->m_amountRequested = parser.longData(); - } - else if (parser.tag() == WalletPriv::PaymentRequestOldAddress) { - assert(pr.pr); - pr.pr->m_useLegacyAddressFormat = parser.boolData(); - } - else if (parser.tag() == WalletPriv::PaymentRequestTxIndex) { - paymentRequestRef.setTxIndex(parser.intData()); - } - else if (parser.tag() == WalletPriv::PaymentRequestOutputIndex) { - paymentRequestRef.setOutputIndex(parser.intData()); - assert(pr.pr); - pr.pr->m_incomingOutputRefs.append(paymentRequestRef.encoded()); - } - else if (parser.tag() == WalletPriv::PaymentRequestPaid) { - assert(pr.pr); - pr.pr->m_amountSeen = parser.longData(); - if (pr.pr->m_amountSeen >= pr.pr->m_amountRequested) - pr.pr->m_paymentState = PaymentRequest::PaymentSeenOk; - } } // after inserting all outputs during load, now remove all inputs these tx's spent. @@ -2079,14 +1967,8 @@ void Wallet::saveWallet() logFatal(LOG_WALLET) << "Failed to save the wallet-name. Reason:" << e; } } - if (!m_walletChanged) { - bool changed = false; - for (auto i = m_paymentRequests.begin(); !changed && i != m_paymentRequests.end(); ++i) { - changed |= i->pr->m_dirty && i->pr->stored(); - } - if (!changed) - return; - } + if (!m_walletChanged) + return; if (m_encryptionLevel == FullyEncrypted && !m_haveEncryptionKey) { logFatal(LOG_WALLET) << "Wallet" << m_name << (m_walletChanged ? "was changed" : "has changed payment requests") @@ -2099,11 +1981,6 @@ void Wallet::saveWallet() const auto &wtx = i.second; saveFileSize += 110 + wtx.inputToWTX.size() * 22 + wtx.outputs.size() * 30; } - for (const auto &prData : qAsConst(m_paymentRequests)) { - if (!prData.pr->stored()) - continue; - saveFileSize += 100 + prData.pr->m_message.size() * 6 + prData.pr->m_incomingOutputRefs.size() * 12; - } Streaming::BufferPool pool(saveFileSize); Streaming::MessageBuilder builder(pool); @@ -2145,33 +2022,7 @@ void Wallet::saveWallet() if (m_walletIsImporting) builder.add(WalletPriv::WalletIsImporting, true); - for (auto prData = m_paymentRequests.begin(); prData != m_paymentRequests.end(); ++prData) { - if (!prData->pr->stored()) { - prData->saved = false; - continue; - } - prData->saved = true; - builder.add(WalletPriv::PaymentRequestType, 0); // bip21 is the only one supported right now - builder.add(WalletPriv::PaymentRequestAddress, prData->pr->m_privKeyId); - if (!prData->pr->m_message.isEmpty()) - builder.add(WalletPriv::PaymentRequestMessage, prData->pr->m_message.toUtf8().constData()); - assert(prData->pr->m_amountRequested >= 0); // never negative - if (prData->pr->m_amountRequested > 0) - builder.add(WalletPriv::PaymentRequestAmount, (uint64_t) prData->pr->m_amountRequested); - if (prData->pr->m_useLegacyAddressFormat) - builder.add(WalletPriv::PaymentRequestOldAddress, true); - - for (auto outRefNum : qAsConst(prData->pr->m_incomingOutputRefs)) { - OutputRef outRef(outRefNum); - builder.add(WalletPriv::PaymentRequestTxIndex, outRef.txIndex()); - builder.add(WalletPriv::PaymentRequestOutputIndex, outRef.outputIndex()); - } - if (prData->pr->m_amountSeen > 0) - builder.add(WalletPriv::PaymentRequestPaid, (uint64_t) prData->pr->m_amountSeen); - } - auto data = builder.buffer(); - try { boost::filesystem::create_directories(m_basedir); boost::filesystem::remove(m_basedir / "wallet.dat~"); diff --git a/src/Wallet.h b/src/Wallet.h index 44883fe..e50e008 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -36,7 +36,6 @@ class WalletInfoObject; class TransactionInfo; -class PaymentRequest; struct WalletInsertBeforeData; // private wallet data struct. namespace P2PNet { @@ -209,11 +208,6 @@ public: // Fill the transactionInfo object from walletTransaction \a txIndex void fetchTransactionInfo(TransactionInfo *info, int txIndex); - void addPaymentRequest(PaymentRequest *pr); - void removePaymentRequest(PaymentRequest *pr); - - QList paymentRequests() const; - /** * Returns a 'seed' to add to the user password on a (partially) encrypted * wallet. @@ -376,7 +370,6 @@ signals: void utxosChanged(); void appendedTransactions(int firstNew, int count); void lastBlockSynchedChanged(); - void paymentRequestsChanged(); void userOwnedChanged(); void transactionChanged(int txIndex); void transactionConfirmed(int txIndex); @@ -591,12 +584,6 @@ private: std::vector> m_encryptionIR; QList> m_broadcastingTransactions; - - struct PRData { - PaymentRequest *pr = nullptr; - bool saved = false; // true if this PR has been persisted to disk - }; - QList m_paymentRequests; }; #endif diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 25832e2..c05dfd6 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "PaymentRequest.h" #include "Wallet.h" #include "Wallet_p.h" @@ -298,7 +297,6 @@ bool Wallet::decrypt(const QString &password) recalculateBalance(); emit encryptionChanged(); emit balanceChanged(); - emit paymentRequestsChanged(); saveSecrets(); // no-op if secrets are unchanged return true; } @@ -339,14 +337,6 @@ void Wallet::forgetEncryptedSecrets() m_hdData.reset(); m_txidCache.clear(); m_nextWalletTransactionId = 1; - for (const auto &prData : m_paymentRequests) { - // delete all the items we own, but not the unstored one - // the UI is showing as that one handles us getting encrypted on its own. - if (prData.pr->stored()) - delete prData.pr; - } - m_paymentRequests.clear(); - m_unspentOutputs.clear(); m_lockedOutputs.clear(); m_txidCache.clear(); @@ -354,7 +344,6 @@ void Wallet::forgetEncryptedSecrets() m_balanceImmature = 0; m_balanceUnconfirmed = 0; emit balanceChanged(); - emit paymentRequestsChanged(); } emit encryptionChanged(); } diff --git a/src/Wallet_p.h b/src/Wallet_p.h index c1cf41b..c110a8a 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -108,18 +108,6 @@ enum WalletDataSaveTags { OutputFromCoinbase, // bool KeyStoreIndex, // int that refers to the index of the privkey for the current tx-output. TxIsCashFusion, // bool - - // PaymentRequests - PaymentRequestType = 40, // int, BIP21 or other - PaymentRequestAddress, // int, the ID of the private-key - PaymentRequestMessage, // string - PaymentRequestAmount, // long, num satoshis - PaymentRequestOldAddress, // bool (default false) - - PaymentRequestPaid, // long, num satoshis received - PaymentRequestTxIndex, // int, index in m_walletTransactions - PaymentRequestOutputIndex, // int. Together with prev makes an OutputRef - }; enum OutputLockStateEnum { -- 2.54.0 From d0b5134527d38bf1d036465ad26c73d8124d71fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 28 Apr 2023 12:07:26 +0200 Subject: [PATCH 0453/1428] Create a functional Payment Request screen. We now use a better design where the PaymentRequest object is owned by QML. --- guis/Flowee/QRWidget.qml | 19 ++++--- guis/mobile/ReceiveTab.qml | 77 ++++++++++++++++++++++++++ src/PaymentRequest.cpp | 107 ++++++++++++++++++++++--------------- src/PaymentRequest.h | 36 ++++++++++--- src/Wallet.cpp | 5 ++ src/Wallet.h | 2 + src/main.cpp | 1 + 7 files changed, 190 insertions(+), 57 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 07e0874..20f5be7 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -20,21 +20,25 @@ import Flowee.org.pay Item { id: root - property QtObject request: null property alias qrSize: qrImage.width implicitWidth: qrImage.width implicitHeight: qrImage.width + addressLine.height - opacity: root.request == null || root.request.state === PaymentRequest.Unpaid ? 1: 0 + property string qrText: "" function handleOnClicked() { - Pay.copyToClipboard(root.request.qr) + Pay.copyToClipboard(qrText); // invert the feedback so a second tap removes the feedback again. clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 } Image { id: qrImage - source: root.request == null ? "" : "image://qr/" + root.request.qr + source: { + var text = root.qrText; + if (text === "") + return ""; + return "image://qr/" + text; + } smooth: false width: 256 // exported at root level height: width @@ -58,12 +62,13 @@ Item { anchors.centerIn: parent width: parent.width text: { - if (root.request == null) - return ""; - var address = root.request.address + var address = root.qrText let index = address.indexOf(":"); if (index >= 0) address = address.substr(index + 1); // cut off the prefix + index = address.indexOf("?") + if (index >= 0) + address = address.substr(0, index - 1); // cut off the tailing parts return address; } horizontalAlignment: Text.AlignHCenter diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 13900db..06a5c67 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -34,5 +34,82 @@ FocusScope { id: request } + onAccountChanged: { + if (request.state !== PaymentRequest.Unpaid) { + console.log(" show dialog to request change of account"); + } + else { + request.account = account; + } + } + onActiveFocusChanged: { + if (activeFocus) { + console.log(" startign"); + request.start(); + } + } + + Flowee.Label { + id: instructions + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 20 + text: qsTr("Share this QR to receive") + opacity: 0.5 + } + + Flowee.QRWidget { + id: qr + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: instructions.bottom + anchors.topMargin: 20 + width: parent.width + qrText: request.qr + } + + // entry-fields + ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: qr.bottom + anchors.topMargin: 30 + Flowee.Label { + text: qsTr("Description") + ":" + } + Flowee.TextField { + id: description + Layout.fillWidth: true + enabled: request.state === PaymentRequest.Unpaid + onTextChanged: request.message = text + } + + Flowee.Label { + id: payAmount + text: qsTr("Amount") + ":" + } + RowLayout { + spacing: 10 + Flowee.BitcoinValueField { + id: bitcoinValueField + enabled: request.state === PaymentRequest.Unpaid + onValueChanged: request.amount = value + } + Flowee.Label { + Layout.alignment: Qt.AlignBaseline + anchors.baselineOffset: bitcoinValueField.baselineOffset + text: Fiat.formattedPrice(bitcoinValueField.value, Fiat.price) + } + } + Flowee.Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Clear") + onClicked: { + request.clear(); + request.account = portfolio.current; + description.text = ""; + bitcoinValueField.value = 0; + request.start(); + } + } + } } diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 079505f..d78238f 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -16,9 +16,9 @@ * along with this program. If not, see . */ #include "PaymentRequest.h" +#include "AccountInfo.h" #include "FloweePay.h" #include "Wallet.h" -#include "AccountInfo.h" #include #include @@ -26,8 +26,7 @@ #include PaymentRequest::PaymentRequest(QObject *parent) - : QObject(parent), - m_wallet(nullptr) + : QObject(parent) { #if 0 // by enabling this you can simulate the payment request being fulfilled @@ -62,6 +61,36 @@ void PaymentRequest::setPaymentState(PaymentState newState) emit paymentStateChanged(); } +void PaymentRequest::updateFailReason() +{ + if (m_account == nullptr) { + setFailReason(NoAccountSet); + } + else if (m_account->wallet()->encryption() == Wallet::FullyEncrypted + && !m_account->wallet()->isDecrypted()) { + setFailReason(AccountEncrypted); + } + else if (m_account->wallet()->walletIsImporting()) { + setFailReason(AccountSynchronizing); + } else { + setFailReason(NoFailure); + } +} + +PaymentRequest::FailReason PaymentRequest::failReason() const +{ + return m_failReason; +} + +void PaymentRequest::setFailReason(FailReason reason) +{ + if (m_failReason == reason) + return; + m_failReason = reason; + emit failReasonChanged(); + emit qrCodeStringChanged(); +} + QString PaymentRequest::address() const { if (m_address.IsNull()) @@ -72,40 +101,36 @@ QString PaymentRequest::address() const return QString::fromStdString(CashAddress::encodeCashAddr(FloweePay::instance()->chainPrefix(), c)); } -void PaymentRequest::setWallet(Wallet *wallet) +QObject *PaymentRequest::account() const { - if (m_wallet == wallet) + return m_account; +} + +void PaymentRequest::setAccount(QObject *account) +{ + if (account == m_account) return; if (!m_incomingOutputRefs.isEmpty()) throw std::runtime_error("Not allowed to change wallet on partially fulfilled request"); - const bool closedWAllet = wallet->encryption() == Wallet::FullyEncrypted && !wallet->isDecrypted(); - if (closedWAllet) - throw std::runtime_error("Payment Request can't use an encrypted wallet"); + if (m_account && m_paymentState == Unpaid) + m_account->wallet()->unreserveAddress(m_privKeyId); - if (m_wallet) { - disconnect (m_wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged())); - if (m_paymentState == Unpaid) - m_wallet->unreserveAddress(m_privKeyId); + m_account = qobject_cast(account); + m_privKeyId = -1; + m_address = KeyId(); + m_amountSeen = 0; + updateFailReason(); + if (m_account) { + connect (m_account->wallet(), &Wallet::encryptionChanged, this, [=]() { + updateFailReason(); + }); } - - m_wallet = wallet; - if (m_wallet) { - m_privKeyId = m_wallet->reserveUnusedAddress(m_address); - connect (m_wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged())); - } - emit walletChanged(); - emit qrCodeStringChanged(); -} - -void PaymentRequest::walletEncryptionChanged() -{ - assert (m_wallet); - const bool closedWAllet = m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted(); - if (closedWAllet) - setWallet(nullptr); + emit accountChanged(); emit qrCodeStringChanged(); + emit amountSeenChanged(); + emit addressChanged(); } PaymentRequest::PaymentState PaymentRequest::paymentState() const @@ -116,16 +141,6 @@ PaymentRequest::PaymentState PaymentRequest::paymentState() const void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight) { assert(value > 0); - if (m_wallet->isSingleAddressWallet()) { - // This is a completely empty payment-request and since we are - // connected to a single-address wallet it is common for transactions - // to arrive that match our 'reserved' address. - // This means that we have zero indication that incoming transactions - // are in response to this request. - - // Just return and keep us showing a QR of our only address. - return; - } if (m_incomingOutputRefs.contains(ref)) { if (m_paymentState >= PaymentSeen && blockHeight != -1) setPaymentState(Confirmed); @@ -154,6 +169,14 @@ void PaymentRequest::paymentRejected(uint64_t ref, int64_t value) emit amountSeenChanged(); } +void PaymentRequest::start() +{ + if (m_failReason == NoFailure && m_privKeyId == -1) { + m_privKeyId = m_account->wallet()->reserveUnusedAddress(m_address, Wallet::ReceivePath); + emit qrCodeStringChanged(); + } +} + QString PaymentRequest::message() const { return m_message; @@ -190,8 +213,9 @@ double PaymentRequest::amountSeen() const QString PaymentRequest::qrCodeString() const { - if (m_address.IsNull()) + if (m_failReason != NoFailure || m_address.IsNull()) { return QString(); + } QString rc = address(); bool separatorInserted = false; // the questionmark. if (m_amountRequested > 0) { @@ -227,12 +251,11 @@ QString PaymentRequest::qrCodeString() const void PaymentRequest::clear() { - m_wallet = nullptr; m_message.clear(); m_address = KeyId(); m_privKeyId = -1; m_amountRequested = 0; - m_amountSeen = 0; - m_paymentState = Unpaid; + setPaymentState(Unpaid); m_incomingOutputRefs.clear(); + setAccount(nullptr); } diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 1390596..2213cb7 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -19,10 +19,8 @@ #define PAYMENTREQUEST_H #include - #include -class Wallet; class AccountInfo; /** @@ -39,6 +37,9 @@ class PaymentRequest : public QObject Q_PROPERTY(double amount READ amount WRITE setAmount NOTIFY amountChanged) Q_PROPERTY(double amountSeen READ amountSeen NOTIFY amountSeenChanged) Q_PROPERTY(PaymentState state READ paymentState NOTIFY paymentStateChanged) + Q_PROPERTY(FailReason failReason READ failReason NOTIFY failReasonChanged) + Q_PROPERTY(QObject* account READ account WRITE setAccount NOTIFY accountChanged) + public: /// The state of this payment enum PaymentState { @@ -50,6 +51,14 @@ public: }; Q_ENUM(PaymentState) + enum FailReason { + NoFailure, + NoAccountSet, + AccountEncrypted, + AccountSynchronizing + }; + Q_ENUM(FailReason) + PaymentRequest(QObject *parent = nullptr); QString message() const; @@ -80,35 +89,46 @@ public: */ void paymentRejected(uint64_t ref, int64_t value); + /** + * For this request and the set properties, reserve an address and show a QR + */ + Q_INVOKABLE void start(); + /** + * Forget all details, the account and become clean. + */ Q_INVOKABLE void clear(); - void setWallet(Wallet *wallet); + QObject *account() const; + void setAccount(QObject *account); QString address() const; void setAddress(const QString &newAddress); + PaymentRequest::FailReason failReason() const; + void setFailReason(FailReason reason); + signals: void messageChanged(); void qrCodeStringChanged(); void amountChanged(); void amountSeenChanged(); void paymentStateChanged(); - void walletChanged(); + void accountChanged(); void addressChanged(); - -private slots: - void walletEncryptionChanged(); + void failReasonChanged(); private: void setPaymentState(PaymentState newState); + void updateFailReason(); - Wallet *m_wallet; + AccountInfo *m_account = nullptr; QString m_message; KeyId m_address; int m_privKeyId = -1; // refers to the Wallets list of private keys qint64 m_amountRequested = 0; qint64 m_amountSeen = 0; PaymentState m_paymentState = Unpaid; + FailReason m_failReason = NoAccountSet; QList m_incomingOutputRefs; // see Wallet::OutputRef }; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 2663ed8..f703f0a 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -294,6 +294,11 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) saveSecrets(); } +bool Wallet::walletIsImporting() const +{ + return m_walletIsImporting; +} + void Wallet::updateSignatureTypes(const std::map &txData) { // this basically processes the output argument received from createWalletTransactionFromTx diff --git a/src/Wallet.h b/src/Wallet.h index e50e008..49667c2 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -355,6 +355,8 @@ public: /// Check the loaded wallet version Id and make internal changes to upgrade it to current. void performUpgrades(); + bool walletIsImporting() const; + public slots: void delayedSave(); diff --git a/src/main.cpp b/src/main.cpp index 9124da7..af78985 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,6 +93,7 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); + qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); auto cld = createCLD(qapp); auto *logger = Log::Manager::instance(); -- 2.54.0 From 4ba71ff8705430bae8c4b7db57d562a833c1e853 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 19:44:40 +0200 Subject: [PATCH 0454/1428] Improve payment-request tracking We introduce a new WalletkeyView which is a class that provides a thread-safe view on a single private-key in the wallet. Detecting all transactions depositing money in that key and thus being an ideal backend for the PaymentRequest. --- guis/mobile/ReceiveTab.qml | 19 ++++-- src/CMakeLists.txt | 1 + src/PaymentRequest.cpp | 77 ++++++++++++------------ src/PaymentRequest.h | 19 +----- src/Wallet.h | 3 +- src/WalletKeyView.cpp | 117 +++++++++++++++++++++++++++++++++++++ src/WalletKeyView.h | 81 +++++++++++++++++++++++++ 7 files changed, 258 insertions(+), 59 deletions(-) create mode 100644 src/WalletKeyView.cpp create mode 100644 src/WalletKeyView.h diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 06a5c67..cb12c84 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -35,18 +35,27 @@ FocusScope { } onAccountChanged: { - if (request.state !== PaymentRequest.Unpaid) { - console.log(" show dialog to request change of account"); + if (request.state !== PaymentRequest.Unpaid + && request.state !== PaymentRequest.Unpaid + && request.state !== PaymentRequest.Unpaid) { + changeAccountDialog.show(); } else { request.account = account; } } + + Flowee.Dialog { + id: changeAccountDialog + title: qsTr("Clear Request?") + text: qsTr("The payment request is pending, are you sure you want to clear it before completion?") + + // TODO + } + onActiveFocusChanged: { - if (activeFocus) { - console.log(" startign"); + if (activeFocus) request.start(); - } } Flowee.Label { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4a62c0a..58a7d2f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,7 @@ set (PAY_SOURCES Wallet.cpp WalletCoinsModel.cpp WalletHistoryModel.cpp + WalletKeyView.cpp WalletSecretsModel.cpp Wallet_encryption.cpp Wallet_support.cpp diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index d78238f..d27fad1 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -19,8 +19,10 @@ #include "AccountInfo.h" #include "FloweePay.h" #include "Wallet.h" +#include "WalletKeyView.h" #include +#include #include #include #include @@ -110,23 +112,52 @@ void PaymentRequest::setAccount(QObject *account) { if (account == m_account) return; - - if (!m_incomingOutputRefs.isEmpty()) + if (m_view && !m_view->transactions().isEmpty()) throw std::runtime_error("Not allowed to change wallet on partially fulfilled request"); - if (m_account && m_paymentState == Unpaid) + if (m_account && m_privKeyId != -1 && m_paymentState == Unpaid) m_account->wallet()->unreserveAddress(m_privKeyId); + delete m_view; + m_view = nullptr; m_account = qobject_cast(account); m_privKeyId = -1; m_address = KeyId(); m_amountSeen = 0; - updateFailReason(); if (m_account) { - connect (m_account->wallet(), &Wallet::encryptionChanged, this, [=]() { + assert(QThread::currentThread() == thread()); + m_view = new WalletKeyView(m_account->wallet(), this); + connect (m_view, &WalletKeyView::walletEncrypted, this, [=]() { updateFailReason(); }); + connect (m_view, &WalletKeyView::importFinished, this, [=]() { + start(); + updateFailReason(); + }); + connect (m_view, &WalletKeyView::transactionMatch, this, [=]() { + uint64_t seen = 0; + for (const auto &tx : m_view->transactions()) { + if (tx.state != WalletKeyView::UTXORejected) { + seen += tx.amount; + } + } + + if (seen != m_amountSeen) { + m_amountSeen = seen; + if (m_amountSeen >= m_amountRequested) { + if (m_paymentState <= PartiallyPaid) + setPaymentState(PaymentSeen); + } + else if (m_amountSeen >= 0) { + if (m_paymentState == Unpaid) + setPaymentState(PartiallyPaid); + } + + emit amountSeenChanged(); + } + }); } + updateFailReason(); emit accountChanged(); emit qrCodeStringChanged(); emit amountSeenChanged(); @@ -138,41 +169,12 @@ PaymentRequest::PaymentState PaymentRequest::paymentState() const return m_paymentState; } -void PaymentRequest::addPayment(uint64_t ref, int64_t value, int blockHeight) -{ - assert(value > 0); - if (m_incomingOutputRefs.contains(ref)) { - if (m_paymentState >= PaymentSeen && blockHeight != -1) - setPaymentState(Confirmed); - return; - } - m_incomingOutputRefs.append(ref); - m_amountSeen += value; - - if (m_paymentState == Unpaid && m_amountSeen >= m_amountRequested) { - if (blockHeight == -1) { - setPaymentState(PaymentSeen); - } else { - setPaymentState(Confirmed); - } - } - emit amountSeenChanged(); -} - -void PaymentRequest::paymentRejected(uint64_t ref, int64_t value) -{ - if (!m_incomingOutputRefs.contains(ref)) - return; - m_amountSeen -= value; - if (m_paymentState >= PaymentSeen && m_amountSeen < m_amountRequested) - setPaymentState(Unpaid); - emit amountSeenChanged(); -} - void PaymentRequest::start() { if (m_failReason == NoFailure && m_privKeyId == -1) { m_privKeyId = m_account->wallet()->reserveUnusedAddress(m_address, Wallet::ReceivePath); + assert(m_view); + m_view->setPrivKeyIndex(m_privKeyId); emit qrCodeStringChanged(); } } @@ -255,7 +257,8 @@ void PaymentRequest::clear() m_address = KeyId(); m_privKeyId = -1; m_amountRequested = 0; + m_amountSeen = 0; setPaymentState(Unpaid); - m_incomingOutputRefs.clear(); setAccount(nullptr); + assert(m_view == nullptr); // ensure that setAccount() did that } diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 2213cb7..3eba60b 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -22,6 +22,7 @@ #include class AccountInfo; +class WalletKeyView; /** * This is a user-created request for payment to a specific wallet/address. @@ -44,6 +45,7 @@ public: /// The state of this payment enum PaymentState { Unpaid, //< we have not seen any payment yet. + PartiallyPaid, //< we have seen some payments, not full amount DoubleSpentSeen,//< We have seen a double-spend-proof (DSP). This is bad. PaymentSeen, //< A payment has been seen, there is still risk. PaymentSeenOk, //< A payment has been seen, we waited and no DSP came. @@ -75,20 +77,6 @@ public: PaymentState paymentState() const; - /** - * Add a payment made towards fulfilling the request. - * @param ref the Wallet reference to the payment. - * @param value the amount paid, in satoshis. - * @param blockHeight or -1 when not mined yet - */ - void addPayment(uint64_t ref, int64_t value, int blockHeight = -1); - /** - * Mark a payment as rejected (typically double-spent). - * @param ref the Wallet reference to the payment. - * @param value the amount paid, in satoshis. - */ - void paymentRejected(uint64_t ref, int64_t value); - /** * For this request and the set properties, reserve an address and show a QR */ @@ -121,6 +109,7 @@ private: void setPaymentState(PaymentState newState); void updateFailReason(); + WalletKeyView *m_view = nullptr; AccountInfo *m_account = nullptr; QString m_message; KeyId m_address; @@ -129,8 +118,6 @@ private: qint64 m_amountSeen = 0; PaymentState m_paymentState = Unpaid; FailReason m_failReason = NoAccountSet; - - QList m_incomingOutputRefs; // see Wallet::OutputRef }; #endif diff --git a/src/Wallet.h b/src/Wallet.h index 49667c2..402bbae 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -36,7 +36,7 @@ class WalletInfoObject; class TransactionInfo; -struct WalletInsertBeforeData; // private wallet data struct. +class WalletKeyView; namespace P2PNet { struct Notification; @@ -564,6 +564,7 @@ private: friend class WalletHistoryModel; friend class WalletSecretsModel; friend class WalletCoinsModel; + friend class WalletKeyView;; // auto-detected, causes us to send bigger gaps for bloom filters. bool m_walletStoresCashFusions = false; diff --git a/src/WalletKeyView.cpp b/src/WalletKeyView.cpp new file mode 100644 index 0000000..7b7ea1e --- /dev/null +++ b/src/WalletKeyView.cpp @@ -0,0 +1,117 @@ + +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "WalletKeyView.h" +#include "Wallet.h" + +WalletKeyView::WalletKeyView(Wallet *wallet, QObject *parent) + : QObject(parent), + m_wallet(wallet) +{ + assert(wallet); + m_walletIsImporting = wallet->walletIsImporting(); + + // connect all. + // Notice that the wallet emits those in any thread. So we only use + // queued connections here. + connect(wallet, SIGNAL(appendedTransactions(int,int)), this, + SLOT(appendedTransactions(int,int)), Qt::QueuedConnection); + connect(wallet, SIGNAL(lastBlockSynchedChanged()), this, + SLOT(lastBlockSynchedChanged()), Qt::QueuedConnection); + connect(wallet, SIGNAL(transactionConfirmed(int)), this, + SLOT(transactionConfirmed(int)), Qt::QueuedConnection); + connect(wallet, SIGNAL(encryptionChanged()), this, + SLOT(encryptionChanged()), Qt::QueuedConnection); +} + +void WalletKeyView::setPrivKeyIndex(int privKeyIndex) +{ + m_privKeyIndex = privKeyIndex; +} + +void WalletKeyView::appendedTransactions(const int firstNew, const int count) +{ + if (m_walletIsImporting) + return; + QMutexLocker locker(&m_wallet->m_lock); + // logDebug() << " getting" << index.row() << "=>" << txIndex; + for (int txIndex = firstNew; txIndex < firstNew + count; ++txIndex) { + auto itemIter = m_wallet->m_walletTransactions.find(txIndex); + if (itemIter == m_wallet->m_walletTransactions.end()) // the wallet changed, we're probably waiting for a queued connection + continue; + for (auto output : itemIter->second.outputs) { + if (output.second.walletSecretId == m_privKeyIndex) { + Transaction tx; + tx.ref = Wallet::OutputRef(txIndex, output.first).encoded(); + tx.state = UTXOSeen; + const auto blockHeight = itemIter->second.minedBlockHeight; + if (blockHeight > 0) + tx.state = UTXOConfirmed; + else if (blockHeight == -2) + tx.state = UTXORejected; + tx.amount = output.second.value; + bool exists = false; + for (auto txIter = m_transactions.begin(); txIter != m_transactions.end(); ++txIter) { + if (txIter->ref == tx.ref) { + exists = true; + txIter->state = tx.state; + assert(txIter->amount == tx.amount); + } + } + if (!exists) + m_transactions.append(tx); + emit transactionMatch(tx.ref, tx.amount, tx.state); + } + } + } +} + +void WalletKeyView::lastBlockSynchedChanged() +{ + if (m_walletIsImporting && !m_wallet->walletIsImporting()) { + m_walletIsImporting = false; + emit importFinished(); + } +} + +QList WalletKeyView::transactions() const +{ + return m_transactions; +} + +void WalletKeyView::transactionConfirmed(int txIndex) +{ + if (m_walletIsImporting) + return; + auto refBase = Wallet::OutputRef(txIndex, 0).encoded(); + for (auto txIter = m_transactions.begin(); txIter != m_transactions.end(); ++txIter) { + if (refBase == (txIter->ref & 0xFFFFFFFFFFFF0000)) { + if (txIter->state != UTXOConfirmed) { + txIter->state = UTXOConfirmed; + emit transactionMatch(txIter->ref, txIter->amount, UTXOConfirmed); + } + } + } +} + +void WalletKeyView::encryptionChanged() +{ + if (m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted()) + emit walletEncrypted(); +} diff --git a/src/WalletKeyView.h b/src/WalletKeyView.h new file mode 100644 index 0000000..eba9554 --- /dev/null +++ b/src/WalletKeyView.h @@ -0,0 +1,81 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +class Wallet; + +/** + * This class goes together with a wallet and provides + * a live view on transactions depositing money on a certain + * private key, which has to be owned by the wallet. + * + * The Wallet::reserveUnusedAddress() method returns a + * privagte key-index, you can set that with setPrivKeyIndex() + * to activate this class. + * + * While a wallet is importing (see walletIsImporting()) all + * transactions added are ignored as the point of this class + * is to view the live changes, not (probably ancient) historical + * ones. + */ +class WalletKeyView : public QObject +{ + Q_OBJECT +public: + WalletKeyView(Wallet *wallet, QObject *parent); + + // set which private key of the viewed wallet we are filtering on. + void setPrivKeyIndex(int privKeyId); + + bool walletIsImporting() const; + + enum UTXOState { + UTXOSeen, + UTXOConfirmed, + UTXORejected + }; + + struct Transaction { + UTXOState state = UTXOSeen; + uint64_t ref = 0; + uint64_t amount = 0; + }; + + QList transactions() const; + +signals: + void transactionMatch(uint64_t ref, uint64_t amount, UTXOState state); + void walletEncrypted(); + void importFinished(); + +private slots: + void appendedTransactions(int firstNew, int count); + void transactionConfirmed(int txIndex); + void encryptionChanged(); + void lastBlockSynchedChanged(); + +private: + const Wallet * const m_wallet; + // set at startup to true if the wallet is in the (once in its lifetime) importing + // stage. If true, all deposits are historical! + bool m_walletIsImporting; + int m_privKeyIndex = -1; + + QList m_transactions; +}; -- 2.54.0 From 4f93cdface5896505deb75f59d0ee66001a737de Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 1 May 2023 21:11:11 +0200 Subject: [PATCH 0455/1428] Cleanup layout of receive tab --- guis/mobile/ReceiveTab.qml | 177 ++++++++++++++++++++++++++++++++++--- src/PaymentRequest.cpp | 26 +++--- 2 files changed, 177 insertions(+), 26 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index cb12c84..96706ef 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -35,24 +35,14 @@ FocusScope { } onAccountChanged: { - if (request.state !== PaymentRequest.Unpaid - && request.state !== PaymentRequest.Unpaid - && request.state !== PaymentRequest.Unpaid) { - changeAccountDialog.show(); - } - else { + var state = request.state; + if (request.state === PaymentRequest.Unpaid) { + // I can only change the wallet without cost + // if no payment has been seen. request.account = account; } } - Flowee.Dialog { - id: changeAccountDialog - title: qsTr("Clear Request?") - text: qsTr("The payment request is pending, are you sure you want to clear it before completion?") - - // TODO - } - onActiveFocusChanged: { if (activeFocus) request.start(); @@ -121,4 +111,163 @@ FocusScope { } } + // the "payment received" screen. + Rectangle { + anchors.fill: parent + anchors.leftMargin: -10 // be actually edge to edge + anchors.rightMargin: -10 + radius: 10 + gradient: Gradient { + GradientStop { + position: 0.6 + color: { + var state = request.state; + if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) + return palette.base + if (state === PaymentRequest.DoubleSpentSeen) + return mainWindow.errorRedBg + return "#3e8b4e" // in all other cases: green + } + Behavior on color { ColorAnimation {} } + } + GradientStop { + position: 0.1 + color: palette.base + } + } + opacity: request.state === PaymentRequest.Unpaid ? 0: 1 + + // animating timer to indicate our checking the security of the transaction. + // (i.e. waiting for the double spent proof) + Item { + id: feedback + width: 160 + height: 160 + + y: 60 + x: 30 + visible: request.state !== PaymentRequest.DoubleSpentSeen + Shape { + id: circleShape + anchors.fill: parent + opacity: request.state === PaymentRequest.Unpaid ? 0: 1 + x: 40 + ShapePath { + strokeWidth: 20 + strokeColor: request.state === PaymentRequest.PaymentSeenOk + ? "green" : "#9ea0b0" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 100; startY: 10 + + PathAngleArc { + id: progressCircle + centerX: 80 + centerY: 80 + radiusX: 70; radiusY: 70 + startAngle: -20 + sweepAngle: request.state === PaymentRequest.Unpaid ? 0: 340 + + Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } + } + } + + Behavior on opacity { OpacityAnimator {} } + } + Flowee.Label { + color: "green" + text: "✔" + opacity: { + if (request.state === PaymentRequest.PaymentSeenOk + || request.state === PaymentRequest.Confirmed) + return 1; + return 0; + } + font.pixelSize: 130 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + } + } + // textual feedback + Item { + clip: true + anchors.top: feedback.top + anchors.left: feedback.right + anchors.right: parent.right + height: 160 + opacity: { + var s = request.state + if (s == PaymentRequest.PartiallyPaid + || s === PaymentRequest.PaymentSeen) + return 1; + return 0; + } + Flowee.Label { + id: label1 + text: qsTr("Payment Seen") + x: request.state === PaymentRequest.Unpaid ? (0 - width - 10) : 0 + y: 30 + Behavior on x { NumberAnimation { } } + } + Flowee.Label { + id: label2 + text: qsTr("Checking...") // checking security + x: (label1.x > (0 - label1.width) + 20) ? 0 : 0 - width - 10 + y: label1.height + 8 + 30 + Behavior on x { NumberAnimation { } } + } + Behavior on opacity { OpacityAnimator { duration: 1000 } } + } + + + Flowee.Label { + id: feedbackLabel + text: { + var s = request.state; + if (s === PaymentRequest.DoubleSpentSeen) { + // double-spent-proof received + return qsTr("Transaction high risk"); + } + if (s === PaymentRequest.PartiallyPaid) + return qsTr("Partially Paid"); + if (s === PaymentRequest.PaymentSeen) + return qsTr("Payment Seen"); + if (s === PaymentRequest.PaymentSeenOk) + return qsTr("Payment Accepted"); + if (s === PaymentRequest.Confirmed) + return qsTr("Payment Settled"); + return "INTERNAL ERROR"; + } + width: parent.width - 40 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: feedback.bottom + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 20 + } + Flowee.Label { + visible: request.state === PaymentRequest.DoubleSpentSeen + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: feedbackLabel.bottom + anchors.topMargin: 20 + width: parent.width - 40 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: qsTr("Instant payment failed. Wait for confirmation. (double spent proof received)") + } + + Behavior on opacity { OpacityAnimator {} } + + Flowee.Button { + anchors.right: parent.right + anchors.rightMargin: 10 + y: 400 + text: qsTr("Continue") + onClicked: { + request.clear(); + description.text = ""; + bitcoinValueField.value = 0; + request.account = portfolio.current; + request.start(); + } + } + } } diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index d27fad1..32fbdbc 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -30,17 +30,6 @@ PaymentRequest::PaymentRequest(QObject *parent) : QObject(parent) { -#if 0 - // by enabling this you can simulate the payment request being fulfilled - QTimer::singleShot(5000, [=]() { - setPaymentState(PaymentSeen); - QTimer::singleShot(3000, [=]() { - // setPaymentState(PaymentSeenOk); - setPaymentState(DoubleSpentSeen); - }); - }); -#endif - connect (this, &PaymentRequest::paymentStateChanged, this, [=]() { if (m_paymentState == PaymentSeen) { // unconfirmed fully paid, but lets see in a couple of seconds... @@ -176,6 +165,17 @@ void PaymentRequest::start() assert(m_view); m_view->setPrivKeyIndex(m_privKeyId); emit qrCodeStringChanged(); + +#if 0 + // by enabling this you can simulate the payment request being fulfilled + QTimer::singleShot(5000, [=]() { + setPaymentState(PaymentSeen); + QTimer::singleShot(3000, [=]() { + setPaymentState(PaymentSeenOk); + // setPaymentState(DoubleSpentSeen); + }); + }); +#endif } } @@ -259,6 +259,8 @@ void PaymentRequest::clear() m_amountRequested = 0; m_amountSeen = 0; setPaymentState(Unpaid); + delete m_view; + m_view = nullptr; setAccount(nullptr); - assert(m_view == nullptr); // ensure that setAccount() did that + assert(m_view == nullptr); // ensure we didn't get a new one } -- 2.54.0 From 3dfb2b3d3834c932ae3a9e840dc9dbd1ebb0dd95 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 10:40:04 +0200 Subject: [PATCH 0456/1428] Port the startupscreen and desktop receive tab too This makes the new PaymentRequest design be used by all components. --- guis/desktop/ReceiveTransactionPane.qml | 130 +++++++++--------------- guis/desktop/main.qml | 10 +- guis/mobile/ReceiveTab.qml | 18 ++++ guis/mobile/StartupScreen.qml | 10 +- src/PaymentRequest.cpp | 4 +- src/PaymentRequest.h | 2 +- 6 files changed, 83 insertions(+), 91 deletions(-) diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index 8e1f622..3295735 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -26,21 +26,22 @@ Pane { id: receivePane property QtObject account: portfolio.current - onAccountChanged: { - if (account == null) - return; - if (qr.request == null) - qr.request = account.createPaymentRequest(receivePane) - else - qr.request.switchAccount(portfolio.current); + + PaymentRequest { + id: request } - function reset() { - if (qr.request.saveState !== PaymentRequest.Stored) - qr.request.destroy(); - qr.request = account.createPaymentRequest(receivePane) - description.text = ""; - bitcoinValueField.reset(); + onAccountChanged: { + var state = request.state; + if (request.state === PaymentRequest.Unpaid) { + // I can only change the wallet without cost + // if no payment has been seen. + request.account = account; + } + } + onActiveFocusChanged: { + if (activeFocus) + request.start(); } Label { @@ -57,6 +58,25 @@ Pane { anchors.top: instructions.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 20 + qrText: request.qr + + Flowee.Label { + visible: request.failReason !== PaymentRequest.NoFailure + text: { + var f = request.failReason; + if (f === PaymentRequest.AccountEncrypted) + return qsTr("Encrypted Wallet"); + if (f === PaymentRequest.AccountImporting) + return qsTr("Import Running..."); + if (f === PaymentRequest.NoAccountSet) + return "No Account Set"; // not translated b/c cause is bug in QML + return ""; + } + anchors.centerIn: parent + width: parent.width - 40 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 18 + } } // the "payment received" screen. @@ -71,7 +91,7 @@ Pane { GradientStop { position: 0.6 color: { - var state = qr.request.state; + var state = request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) return palette.base if (state === PaymentRequest.DoubleSpentSeen) @@ -85,7 +105,7 @@ Pane { color: palette.base } } - opacity: qr.request.state === PaymentRequest.Unpaid ? 0: 1 + opacity: request.state === PaymentRequest.Unpaid ? 0: 1 // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) @@ -94,7 +114,7 @@ Pane { width: 160 height: 160 y: (parent.height - height) / 3 * 2 - visible: qr.request.state !== PaymentRequest.DoubleSpentSeen + visible: request.state !== PaymentRequest.DoubleSpentSeen Shape { id: circleShape anchors.fill: parent @@ -113,7 +133,7 @@ Pane { centerY: 80 radiusX: 70; radiusY: 70 startAngle: -80 - sweepAngle: qr.request.state === PaymentRequest.Unpaid ? 0: 340 + sweepAngle: request.state === PaymentRequest.Unpaid ? 0: 340 Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } } @@ -139,7 +159,7 @@ Pane { Label { id: feedbackLabel text: { - var s = qr.request.state; + var s = request.state; if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received return qsTr("Transaction high risk") @@ -159,7 +179,7 @@ Pane { font.pointSize: 20 } Label { - visible: qr.request.state === PaymentRequest.DoubleSpentSeen + visible: request.state === PaymentRequest.DoubleSpentSeen anchors.top: feedbackLabel.bottom anchors.right: parent.right anchors.rightMargin: 10 @@ -186,8 +206,8 @@ Pane { Flowee.TextField { id: description Layout.fillWidth: true - enabled: qr.request.state === PaymentRequest.Unpaid - onTextChanged: qr.request.message = text + enabled: request.state === PaymentRequest.Unpaid + onTextChanged: request.message = text focus: true } @@ -199,8 +219,8 @@ Pane { spacing: 10 Flowee.BitcoinValueField { id: bitcoinValueField - enabled: qr.request.state === PaymentRequest.Unpaid - onValueChanged: qr.request.amount = value + enabled: request.state === PaymentRequest.Unpaid + onValueChanged: request.amount = value } Label { Layout.alignment: Qt.AlignBaseline @@ -216,65 +236,13 @@ Pane { Layout.fillWidth: true } Button { - text: qsTr("Remember", "payment request") - visible: qr.request.state === PaymentRequest.Unpaid || qr.request.state === PaymentRequest.DoubleSpentSeen + text: request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") onClicked: { - qr.request.stored = true - reset(); - } - } - Button { - text: qr.request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") - onClicked: { - reset(); - } - } - } - } - - Flow { - width: parent.width - anchors.bottom: parent.bottom - spacing: 10 - Repeater { - model: portfolio.current.paymentRequests - delegate: Rectangle { - width: 70 - height: width - radius: 25 - clip: true - border.width: 6 - border.color: { - var state = modelData.state; - if (state === PaymentRequest.Unpaid) - return "#888888" - if (state === PaymentRequest.PaymentSeen) - return "yellow" - if (state === PaymentRequest.DoubleSpentSeen) - return "red" - // in all other cases: - return "green" - } - - // don't show the one we are editing - visible: modelData.saveState === PaymentRequest.Stored - - Text { - anchors.centerIn: parent - text: modelData.message - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton | Qt.LeftButton - onClicked: paymentContextMenu.popup() - Menu { - id: paymentContextMenu - MenuItem { - text: qsTr("Delete") - onTriggered: modelData.stored = false; - } - } + request.clear(); + request.account = portfolio.current; + description.text = ""; + bitcoinValueField.value = 0; + request.start(); } } } diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 6414ec6..8830171 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -40,17 +40,17 @@ ApplicationWindow { onIsLoadingChanged: { if (!isLoading) { + // delay loading to avoid errors due to not having a portfolio + // portfolio is only initialized after a second or so. + receivePane.source = "./ReceiveTransactionPane.qml" + sendTransactionPane.source = "./SendTransactionPane.qml" + if (!portfolio.current.isUserOwned) { // Open on receive tab if the wallet is effectively empty tabbar.currentIndex = 2; } else { tabbar.currentIndex = 0; } - - // delay loading to avoid errors due to not having a portfolio - // portfolio is only initialized after a second or so. - receivePane.source = "./ReceiveTransactionPane.qml" - sendTransactionPane.source = "./SendTransactionPane.qml" } } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 96706ef..00a7a2b 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -63,6 +63,24 @@ FocusScope { anchors.topMargin: 20 width: parent.width qrText: request.qr + + Flowee.Label { + visible: request.failReason !== PaymentRequest.NoFailure + text: { + var f = request.failReason; + if (f === PaymentRequest.AccountEncrypted) + return qsTr("Encrypted Wallet"); + if (f === PaymentRequest.AccountImporting) + return qsTr("Import Running..."); + if (f === PaymentRequest.NoAccountSet) + return "No Account Set"; // not translated b/c cause is bug in QML + return ""; + } + anchors.centerIn: parent + width: parent.width - 40 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 18 + } } // entry-fields diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index fac81d3..3aea28c 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Layouts import "../Flowee" as Flowee +import Flowee.org.pay Page { id: root @@ -26,6 +27,7 @@ Page { headerButtonText: qsTr("Continue") onHeaderButtonClicked: { if (!portfolio.current.isUserOwned) { + request.clear(); // to make the QR here the same as there var mainView = thePile.get(0); mainView.currentIndex = 2 // go to the receive-tab } @@ -43,7 +45,12 @@ Page { thePile.pop(); } } + PaymentRequest { + id: request + account: portfolio.current + } } + onActiveFocusChanged: request.start(); Flickable { anchors.fill: parent @@ -150,10 +157,9 @@ Page { width: column.width * 0.8 x: column.width / 10 } - Flowee.QRWidget { - request: portfolio.current.createPaymentRequest(root) width: parent.width + qrText: request.qr } Item { width: 1; height: 40 } // spacer diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 32fbdbc..76f9f84 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -62,7 +62,7 @@ void PaymentRequest::updateFailReason() setFailReason(AccountEncrypted); } else if (m_account->wallet()->walletIsImporting()) { - setFailReason(AccountSynchronizing); + setFailReason(AccountImporting); } else { setFailReason(NoFailure); } @@ -255,7 +255,6 @@ void PaymentRequest::clear() { m_message.clear(); m_address = KeyId(); - m_privKeyId = -1; m_amountRequested = 0; m_amountSeen = 0; setPaymentState(Unpaid); @@ -263,4 +262,5 @@ void PaymentRequest::clear() m_view = nullptr; setAccount(nullptr); assert(m_view == nullptr); // ensure we didn't get a new one + m_privKeyId = -1; } diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 3eba60b..28758fb 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -57,7 +57,7 @@ public: NoFailure, NoAccountSet, AccountEncrypted, - AccountSynchronizing + AccountImporting }; Q_ENUM(FailReason) -- 2.54.0 From bb4e0bc13871eff9f58e546f987651a31ed5afeb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 11:14:15 +0200 Subject: [PATCH 0457/1428] Always show BCH value on testnet The feature to show only the exchange-rate based value on the main screen doesn't make sense on anything but mainnet due to the simple fact that thats the only one that has an exchange rate. We hide the feature on testnet and make it always show the BCH value on the overview screens. Notice that the actuall setting, as written in the config file, is shared between chains. We only have one confg file. So any solution with default values would not work. --- guis/desktop/SettingsPane.qml | 2 ++ guis/desktop/WalletTransaction.qml | 4 ++-- guis/mobile/AccountHistory.qml | 4 ++-- guis/mobile/GuiSettings.qml | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index 4e29457..8f23dad 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -97,11 +97,13 @@ Pane { Layout.alignment: Qt.AlignRight checked: Pay.activityShowsBch onCheckedChanged: Pay.activityShowsBch = checked + visible: Pay.isMainChain } Flowee.CheckBoxLabel { Layout.columnSpan: 2 buddy: showBchOnActivity text: qsTr("Show Bitcoin Cash value on Activity page") + visible: Pay.isMainChain } Flowee.CheckBox { diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index 761c6f6..cab8644 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -133,7 +133,7 @@ Rectangle { Flowee.BitcoinAmountLabel { id: bitcoinAmountLabel - visible: Pay.activityShowsBch + visible: Pay.activityShowsBch || !Pay.isMainChain value: { let inputs = model.fundsIn let outputs = model.fundsOut @@ -149,7 +149,7 @@ Rectangle { Flowee.Label { anchors.top: mainLabel.top anchors.right: parent.right - visible: !Pay.activityShowsBch + visible: bitcoinAmountLabel.visible === false text: { var timestamp = model.date; if (timestamp === undefined) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index fcde014..51d6288 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -320,7 +320,7 @@ ListView { height: amount.height + 8 anchors.right: parent.right y: { - if (Pay.activityShowsBch) // my bottom at parent verticalCenter + if (Pay.activityShowsBch || !Pay.isMainChain) // my bottom at parent verticalCenter return parent.height / 2 - height return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter } @@ -353,7 +353,7 @@ ListView { opacity: price.opacity } Flowee.BitcoinAmountLabel { - visible: price.visible && Pay.activityShowsBch + visible: price.visible && (Pay.activityShowsBch || !Pay.isMainChain) anchors.right: parent.right anchors.rightMargin: 25 anchors.baseline: dateLine.baseline diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index ec1359f..61ed270 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -143,6 +143,7 @@ Page { Flowee.Label { text: qsTr("Main View") font.bold: true + visible: Pay.isMainChain // because we only have one option right now } Item { width: 10 @@ -153,6 +154,7 @@ Page { text: qsTr("Show Bitcoin Cash value") checked: Pay.activityShowsBch onCheckedChanged: Pay.activityShowsBch = checked + visible: Pay.isMainChain // only mainchain has fiat value } } } -- 2.54.0 From 1929e99a4ab45bd42d6e2f0560c60668e387517b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 12:18:40 +0200 Subject: [PATCH 0458/1428] Fix behavior on initial sync After the sync is ended is now actually shows the QR. --- src/PaymentRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 76f9f84..7eb683c 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -120,8 +120,8 @@ void PaymentRequest::setAccount(QObject *account) updateFailReason(); }); connect (m_view, &WalletKeyView::importFinished, this, [=]() { - start(); updateFailReason(); + start(); }); connect (m_view, &WalletKeyView::transactionMatch, this, [=]() { uint64_t seen = 0; -- 2.54.0 From 2975b5e5d7c16468894b61b3466b413020ded52f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 12:21:23 +0200 Subject: [PATCH 0459/1428] Make 'importing' not be for any new wallet. --- src/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index f703f0a..882a28f 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -815,9 +815,9 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons if (startHeight < 1000000) { // when its a blockheight and not a timestamp. m_segment->blockSynched(startHeight); m_segment->blockSynched(startHeight); // yes, twice + m_walletIsImporting = true; } deriveHDKeys(200, 200, startHeight); - m_walletIsImporting = true; } int Wallet::lastTransactionTimestamp() const -- 2.54.0 From 1aa3503b065e0ca10419d74cd1b31ffb7b812162 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 15:45:42 +0200 Subject: [PATCH 0460/1428] Fixlets in i18n --- guis/Flowee/WalletSecretsView.qml | 2 +- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/TransactionDetails.qml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 349fb68..9b86181 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -31,7 +31,7 @@ ListView { Label { width: parent.width font.italic: true - text: qsTr("Explanation:"); + text: qsTr("Explanation") + ":"; } Label { width: parent.width diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index df9a375..a2082c0 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -194,7 +194,7 @@ QQC2.Control { text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked - tooltipText: qsTr("Switches between still in use addresses and formerly used, nmw empty, addresses") + tooltipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") } Flowee.WalletSecretsView { diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 3de29d2..a3f5462 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -61,7 +61,7 @@ Page { } Flowee.Label { text: root.infoObject == null ? "" : - qsTr("%1 bytes", "", root.infoObject.size).arg(root.infoObject.size) + qsTr("%1 bytes").arg(root.infoObject.size) } /* VisualSeparator {} -- 2.54.0 From 28c11cf067c18edd9f1c36485522acaad8c1d1e7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 15:49:22 +0200 Subject: [PATCH 0461/1428] Import translation updates from crowdin --- translations/floweepay-common_nl.ts | 63 +++-- translations/floweepay-desktop_nl.ts | 226 ++++++++-------- translations/floweepay-mobile_en.ts | 379 +++++++++++++------------- translations/floweepay-mobile_nl.ts | 381 ++++++++++++++------------- 4 files changed, 538 insertions(+), 511 deletions(-) diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index de39ab6..5d4c926 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -62,6 +62,15 @@ Dit is een eenvoudige portemonnee met diverse adressen. + + AddressInfoWidget + + + self + payment to self + zelf + + BroadcastFeedback @@ -111,30 +120,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -143,13 +152,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -237,22 +246,22 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. @@ -260,7 +269,7 @@ QRWidget - + Copied to clipboard Naar klembord gekopieerd @@ -269,7 +278,7 @@ Wallet - + Change #%1 Wisselmunt #%1 @@ -354,23 +363,37 @@ WalletSecretsView - - + + Explanation + Uitleg + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Munten a / b + a) actieve munten aantal. + b) historische munten. + + + + Copy Address Kopieer adres - + Copy Private Key Kopieer privésleutel - + Coins: %1 / %2 Munten: %1 / %2 - + Signed with Schnorr signatures in the past In het verleden ondertekend met Schnorr diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index abd2e1b..3debed7 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -108,11 +108,6 @@ Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - - - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. - Backup details @@ -138,6 +133,11 @@ <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. + AccountListItem @@ -333,19 +333,19 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - Alternate phrase - Alternatieve zin - - - Start Height Beginhoogte - + Derivation Derivatie + + + Alternate phrase + Alternatieve zin + NewAccountPane @@ -452,74 +452,83 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. ReceiveTransactionPane - + Share your QR code or copy address to receive Deel uw QR code of kopieer het adres waarop u Bitcoin Cash kan ontvangen - + + Encrypted Wallet + Versleutelde portemonnee + + + + Import Running... + Bezig met importeren... + + + Checking Controleren - + Transaction high risk Transactie met hoog risico - + Payment Seen Betaling gezien - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (dubbel uitgegeven bewijs ontvangen) - + Description Omschrijving - + Amount Bedrag - - Remember - payment request - Onthoud - - - + Clear Wissen - + Done Klaar - - - Delete - Verwijder - SendTransactionPane + + + Confirm delete + Verwijderen bevestigen + + + + Do you really want to delete this detail? + Wilt u dit detail echt wissen? + Add Destination @@ -530,6 +539,11 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Prepare Bereid voor + + + Enter your PIN + Voer uw PIN in + Transaction Details @@ -540,6 +554,11 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Not prepared yet Nog niet voorbereid + + + Copy transaction-ID + Kopieer transactie-ID + Fee @@ -571,31 +590,6 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Send Verstuur - - - Cancel - Afbreken - - - - Copy transaction-ID - Kopieer transactie-ID - - - - Confirm delete - Verwijderen bevestigen - - - - Do you really want to delete this detail? - Wilt u dit detail echt wissen? - - - - Enter your PIN - Voer uw PIN in - Destination @@ -614,53 +608,52 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - + Copy Address Kopieer adres - - self - payment to self - deze - - - + Amount Bedrag - + Max Max - + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + + Cancel + Afbreken + + + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -669,53 +662,53 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt @@ -728,42 +721,47 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Instellingen - + Unit Eenheid - + + Show Bitcoin Cash value on Activity page + Toon Bitcoin Cash waarde op de activiteitenpagina + + + Show Block Notifications Toon blok notificaties - + When a new block is mined, Flowee Pay shows a desktop notification Wanneer een nieuw blok is gedolven, toont Flowee Pay een desktop notificatie - + Night Mode Nachtmodus - + Version Versie - + Library Version Bibliotheek versie - + Synchronization Synchronisatie - + Network Status Netwerk Status @@ -1023,95 +1021,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - - Offline - Offline - - - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + + Offline + Offline + + + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1120,7 +1118,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index c669a2d..ceded87 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -4,43 +4,34 @@ About - About About - Help translate this app Help translate this app - License License - - Credits Credits - © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander and contributors - Project Home Project Home - With git repository and issues tracker With git repository and issues tracker - Telegram Telegram @@ -48,52 +39,38 @@ AccountHistory - Home Home - - Scheduled - Scheduled - - - Miner Reward Miner Reward - Cash Fusion Cash Fusion - Received Received - Moved Moved - Sent Sent - Sending Sending - Seen Seen - Rejected Rejected @@ -101,91 +78,79 @@ AccountPageListItem - Sync Status Sync Status - Primary Wallet Primary Wallet - Backup information Backup information - Backup Details Backup Details - Wallet seed-phrase Wallet seed-phrase - Derivation Path Derivation Path - Starting Height height refers to block-height Starting Height - Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - Wallet keys Wallet keys - + Show Index + toggle to show numbers + Show Index + + Change Addresses Change Addresses - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - Used Addresses Used Addresses - - Switches between unused and used Bitcoin addresses - Switches between unused and used Bitcoin addresses - - - Addresses and Keys Addresses and Keys + + Switches between still in use addresses and formerly used, new empty, addresses + Switches between still in use addresses and formerly used, new empty, addresses + - AccountSelector + AccountSelectorPopup - Your Wallets Your Wallets - last active last active @@ -193,7 +158,6 @@ AccountSyncState - Status: Offline Status: Offline @@ -201,7 +165,6 @@ AccountsList - Wallet Information Wallet Information @@ -209,7 +172,6 @@ CurrencySelector - Select Currency Select Currency @@ -217,96 +179,87 @@ GuiSettings - Display Settings Display Settings - Font sizing Font sizing - Unit Unit - Change Currency (%1) Change Currency (%1) + + Main View + Main View + + + Show Bitcoin Cash value + Show Bitcoin Cash value + ImportWalletPage - Import Wallet Import Wallet - Create Create - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Secret The seed-phrase or private key Secret - Private key description of type Private key - BIP 39 seed-phrase description of type BIP 39 seed-phrase - Unrecognized word Word from the seed-phrases lexicon Unrecognized word - Name Name - Force Single Address Force Single Address - When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - Alternate phrase Alternate phrase - Oldest Transaction Oldest Transaction - Derivation Derivation @@ -314,7 +267,6 @@ Change will come back to the imported key. MenuOverlay - Add Wallet Add Wallet @@ -322,306 +274,386 @@ Change will come back to the imported key. NetView - Peers Peers - Address network address (IP) Address - Start-height: %1 Start-height: %1 - ban-score: %1 ban-score: %1 - initializing connection initializing connection - Verifying peer Verifying peer - - Peer for wallet - Peer for wallet - - - Peer for wallet: %1 Peer for wallet: %1 + + Peer for wallet + Peer for wallet + NewAccount - New Bitcoin Cash Wallet New Bitcoin Cash Wallet - Next Next - Create a New Wallet Create a New Wallet - Basic Basic - Private keys based Property of a wallet Private keys based - Difficult to backup Context: wallet type Difficult to backup - Great for brief usage Context: wallet type Great for brief usage - HD wallet HD wallet - Seed-phrase based Context: wallet type Seed-phrase based - Easy to backup Context: wallet type Easy to backup - Most compatible The most compatible wallet type Most compatible - Import Existing Wallet Import Existing Wallet - Import Import - Imports seed-phrase Imports seed-phrase - Imports private key Imports private key - New Wallet New Wallet - - Create Create - This creates a new empty wallet with simple multi-address capability This creates a new empty wallet with simple multi-address capability - - Name Name - Force Single Address Force Single Address - When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up - Derivation Derivation - New HD-Wallet New HD-Wallet - This creates a new empty wallet with smart creation of addresses from a single seed-phrase This creates a new empty wallet with smart creation of addresses from a single seed-phrase - PayWithQR + PayToOthers - - Approve Payment - Approve Payment + Build Transaction + Build Transaction + + + Building Error + error during build + Building Error + + + Add Payment Detail + page title + Add Payment Detail + + + Add Destination + Add Destination + + + an address to send money to + an address to send money to + + + Confirm Sending + confirm we want to send the transaction + Confirm Sending + + + TXID + TXID + + + Copy transaction-ID + Copy transaction-ID + + + Fee + Fee + + + Transaction size + Transaction size + + + %1 bytes + %1 bytes + + + Fee per byte + Fee per byte + + + %1 sat/byte + fee + %1 sat/byte + + + Destination + Destination + + + unset + indication of empty + unset + + + Copy Address + Copy Address + + + Amount + Amount + + + Edit Destination + Edit Destination - Send All all money in wallet Send All - - Payment description - Payment description + Bitcoin Cash Address + Bitcoin Cash Address - - All Currencies - All Currencies + Warning + Warning + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + I am certain + I am certain + + + Drag to Edit + Drag to Edit + + + Drag to Delete + Drag to Delete + + + Prepare Payment... + Prepare Payment... + + + + PayWithQR + + Approve Payment + Approve Payment + + + Send All + all money in wallet + Send All + + + Payment description + Payment description PriceDetails - 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 - 7d 7 days 7d - 1m 1 month 1m - 3m 3 months 3m + + PriceInputWidget + + All Currencies + All Currencies + + ReceiveTab - Receive Receive - Share this QR to receive Share this QR to receive - - Checking - Checking + Encrypted Wallet + Encrypted Wallet - - Transaction high risk - Transaction high risk + Import Running... + Import Running... - - Payment Seen - Payment Seen - - - - Payment Accepted - Payment Accepted - - - - Payment Settled - Payment Settled - - - - Instant payment failed. Wait for confirmation. (double spent proof received) - Instant payment failed. Wait for confirmation. (double spent proof received) - - - Description Description - + Amount + Amount + + Clear Clear - - Done - Done + Payment Seen + Payment Seen + + + Checking... + Checking... + + + Transaction high risk + Transaction high risk + + + Partially Paid + Partially Paid + + + Payment Accepted + Payment Accepted + + + Payment Settled + Payment Settled + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + Continue + Continue SendTransactionsTab - Send Send - Scan a QR code Scan a QR code + + Build transaction + Build transaction + SlideToApprove - SLIDE TO SEND SLIDE TO SEND @@ -629,32 +661,26 @@ This ensures only one private key will need to be backed up StartupScreen - Welcome! Welcome! - Continue Continue - Moving the world towards a Bitcoin Cash economy Moving the world towards a Bitcoin Cash economy - I already have a wallet I already have a wallet - OR OR - I want to send funds to my new wallet I want to send funds to my new wallet @@ -662,68 +688,53 @@ This ensures only one private key will need to be backed up TransactionDetails - Transaction Details Transaction Details - Rejected Rejected - Unconfirmed Unconfirmed - Mined Mined - Coinbase Coinbase - CashFusion transaction CashFusion transaction - Size Size - - + %1 bytes - - %1 bytes - %1 bytes - + %1 bytes TxInfoSmall - Transaction is rejected Transaction is rejected - Processing Processing - Mined Mined - %1 blocks ago Confirmations @@ -732,42 +743,34 @@ This ensures only one private key will need to be backed up - Miner Reward Miner Reward - Cash Fusion Cash Fusion - Received Received - - Moved - Moved + Payment to self + Payment to self - Sent Sent - Value then Value then - Value now Value now - Transaction Details Transaction Details diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 7f87c84..e35101d 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -4,43 +4,34 @@ About - About Over Ons - Help translate this app Help deze app te vertalen - License Licentie - - Credits Met dank aan - © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander en bijdragers - Project Home Startpagina project - With git repository and issues tracker Met git data en takenlijst - Telegram Telegram @@ -48,52 +39,38 @@ AccountHistory - Home Start - - Scheduled - Gepland - - - Miner Reward Mijnwerker Beloning - Cash Fusion Cash Fusion - Received Ontvangen - Moved Zelf-betaling - Sent Verzonden - Sending Verzenden - Seen Gezien - Rejected Geweigerd @@ -101,99 +78,86 @@ AccountPageListItem - Sync Status Synchronisatie status - Primary Wallet Primaire portemonnee - Backup information Back-up Informatie - Backup Details Back-up gegevens - Wallet seed-phrase Herstelzin opslaan - Derivation Path Derivatie pad - Starting Height height refers to block-height Beginhoogte - Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - Wallet keys Sleutels van portemonnee - + Show Index + toggle to show numbers + Toon Index + + Change Addresses Wisselgeldadres - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - Used Addresses Gebruikte adressen - - Switches between unused and used Bitcoin addresses - Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - - - Addresses and Keys Adressen en sleutels + + Switches between still in use addresses and formerly used, new empty, addresses + Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen + - AccountSelector + AccountSelectorPopup - Your Wallets Uw portemonnees - last active - laatst actief + meest recent actief AccountSyncState - Status: Offline Status: offline @@ -201,7 +165,6 @@ AccountsList - Wallet Information Portemonnee Informatie @@ -209,7 +172,6 @@ CurrencySelector - Select Currency Selecteer valuta @@ -217,96 +179,87 @@ GuiSettings - Display Settings Scherminstellingen - Font sizing Lettertypegrootte - Unit Eenheid - Change Currency (%1) Verander valuta (%1) + + Main View + Hoofd overzicht + + + Show Bitcoin Cash value + Toon Bitcoin Cash waarde + ImportWalletPage - Import Wallet Portemonnee importeren - Create Creëer - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - Secret The seed-phrase or private key Geheim - Private key description of type Privé-sleutel - BIP 39 seed-phrase description of type BIP 39 herstelzin - Unrecognized word Word from the seed-phrases lexicon Niet herkend woord - Name Naam - Force Single Address Forceer één adres - When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - Alternate phrase Alternatieve zin - Oldest Transaction Oudste transactie - Derivation Derivatie @@ -314,7 +267,6 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MenuOverlay - Add Wallet Portemonnee toevoegen @@ -322,306 +274,386 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. NetView - Peers Peers - Address network address (IP) Adres - Start-height: %1 Beginhoogte: %1 - ban-score: %1 ban-score: %1 - initializing connection verbinding initialiseren - Verifying peer Peer verifiëren - - Peer for wallet - Peer voor portemonnee - - - Peer for wallet: %1 Peer voor portemonnee: %1 + + Peer for wallet + Peer voor portemonnee + NewAccount - New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee - Next Volgende - Create a New Wallet Maak een nieuwe portemonnee - Basic Eenvoudig - Private keys based Property of a wallet Privé sleutels gebaseerd - Difficult to backup Context: wallet type Back-up moeilijk - Great for brief usage Context: wallet type Ideaal voor kort gebruik - HD wallet HD portemonnee - Seed-phrase based Context: wallet type Herstelzin gebaseerd - Easy to backup Context: wallet type Back-upt makkelijk - Most compatible The most compatible wallet type Meest compatibel - Import Existing Wallet Importeer bestaande portemonnee - Import Importeer - Imports seed-phrase Importeert herstelzin - Imports private key Importeert Privésleutel - New Wallet Nieuwe Portemonnee - - Create Creëer - This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden - - Name Naam - Force Single Address Forceer één adres - When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Derivation Derivatie - New HD-Wallet Nieuwe Portemonnee - This creates a new empty wallet with smart creation of addresses from a single seed-phrase Dit maakt een nieuwe lege portemonnee die slim adressen maakt van één enkele herstelzin - PayWithQR + PayToOthers - - Approve Payment - Betaling goedkeuren + Build Transaction + Bouw Transactie + + + Building Error + error during build + Fout bij bouwen + + + Add Payment Detail + page title + Voeg betalingsgegevens toe + + + Add Destination + Voeg bestemming toe + + + an address to send money to + een adres om geld naar te sturen + + + Confirm Sending + confirm we want to send the transaction + Verzenden bevestigen + + + TXID + TXID + + + Copy transaction-ID + Transactie-ID kopiëren + + + Fee + Transactiekosten + + + Transaction size + Transactie grootte + + + %1 bytes + %1 bytes + + + Fee per byte + Transactiekosten per byte + + + %1 sat/byte + fee + %1 sats/byte + + + Destination + Bestemming + + + unset + indication of empty + niet ingesteld + + + Copy Address + Kopieer adres + + + Amount + Bedrag + + + Edit Destination + Bewerk Bestemming - Send All all money in wallet Alles verzenden - - Payment description - Omschrijving betaling + Bitcoin Cash Address + Bitcoin Cash-adres - - All Currencies - Alle valuta's + Warning + Waarschuwing + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? + + + I am certain + Ik ben zeker + + + Drag to Edit + Sleep om te bewerken + + + Drag to Delete + Sleep om te verwijderen + + + Prepare Payment... + Betaling voorbereiden... + + + + PayWithQR + + Approve Payment + Betaling goedkeuren + + + Send All + all money in wallet + Alles verzenden + + + Payment description + Omschrijving betaling PriceDetails - 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 - 7d 7 days 7d - 1m 1 month 1m - 3m 3 months 3m + + PriceInputWidget + + All Currencies + Alle valuta's + + ReceiveTab - Receive Ontvangen - Share this QR to receive Betaler moet deze QR lezen - - Checking - Verifiëren + Encrypted Wallet + Versleutelde portemonnee - - Transaction high risk - Transactie met hoog risico + Import Running... + Bezig met importeren... - - Payment Seen - Betaling gezien - - - - Payment Accepted - Betaling geaccepteerd - - - - Payment Settled - Betaling Afgewikkeld - - - - Instant payment failed. Wait for confirmation. (double spent proof received) - Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - - - Description Omschrijving - + Amount + Bedrag + + Clear Wissen - - Done - Klaar + Payment Seen + Betaling gezien + + + Checking... + Controleren... + + + Transaction high risk + Transactie met hoog risico + + + Partially Paid + Deels betaald + + + Payment Accepted + Betaling geaccepteerd + + + Payment Settled + Betaling Afgewikkeld + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) + + + Continue + Doorgaan SendTransactionsTab - Send Versturen - Scan a QR code Scan QR-code + + Build transaction + Bouw transactie + SlideToApprove - SLIDE TO SEND SWIPE VOOR VERZENDEN @@ -629,32 +661,26 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt StartupScreen - Welcome! Welkom! - Continue Doorgaan - Moving the world towards a Bitcoin Cash economy We lopen het pad naar een Bitcoin Cash economie - I already have a wallet Ik heb al een portemonnee - OR OF - I want to send funds to my new wallet Ik wil geld naar mijn nieuwe portemonnee sturen @@ -662,68 +688,53 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionDetails - Transaction Details Transactiedetails - Rejected Afgewezen - Unconfirmed Onbevestigd - Mined Gedolven - Coinbase Coinbase - CashFusion transaction CashFusion transactie - Size Grootte - - + %1 bytes - - %1 bytes - %1 bytes - + %1 bytes TxInfoSmall - Transaction is rejected Transactie geweigerd - Processing In behandeling - Mined Gedolven - %1 blocks ago Confirmations @@ -732,42 +743,34 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Miner Reward Mijnwerker Beloning - Cash Fusion Cash Fusion - Received Ontvangen - - Moved - Zelf-betaling + Payment to self + Betaling aan uzelf - Sent Verzonden - Value then Waarde toen - Value now Waarde nu - Transaction Details Transactiedetails -- 2.54.0 From 8534f6368ffd5cc5546100000ca4ef8c98e74293 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 16:07:22 +0200 Subject: [PATCH 0462/1428] New version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index af78985..fefe8d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.04.1"); + qapp.setApplicationVersion("2023.05.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 4bebaa27eb99bce83766ab0a5753fdccf8a47048 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 20:06:46 +0200 Subject: [PATCH 0463/1428] Fix use after free. The 'erase' actually deletes the wtx, so we should not reference the txid from it after we call erase. Found with valgrind. --- src/Wallet_support.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 9c1945a..a7d66d0 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -476,11 +476,11 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) ibd.oldTransactionIds.insert(i->first); ibd.transactions.push_back(i->second); - m_walletTransactions.erase(i); auto txidIter = m_txidCache.find(wtx.txid); assert(txidIter != m_txidCache.end()); m_txidCache.erase(txidIter); logDebug(LOG_WALLET) << "Rolling back tx:" << wtx.txid << wtx.minedBlockHeight; + m_walletTransactions.erase(i); // last command, it invalidates wtx } return ibd; } -- 2.54.0 From 2e4860f1c1dfd98033e6e05d7497bbb1f617589c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 2 May 2023 20:07:39 +0200 Subject: [PATCH 0464/1428] Fix unit test sometimes failing As the code is by nature probabilistic, the unit test needed to be a little more flexible in handling different outcomes. --- testing/wallet/TestWallet.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index b26d48a..d17a103 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -365,6 +365,13 @@ void TestWallet::lockingOutputs() QVERIFY(unlockSuccess); walletSet = wallet->findInputsFor(1801000, 1, 1, change); + if (walletSet.outputs.size() == 0) { + // findInputsFor is probabilistic, and about 1 in 10 runs it selects + // for the above B1/T1 the bigger 400000 sats output instead of the + // smaller 100000 output, which is thus now locked. + // In case that happens, just request less. + walletSet = wallet->findInputsFor(1501000, 1, 1, change); + } QCOMPARE(walletSet.outputs.size(), 4L); // should be all outputs, including the unconfirmed one. TransactionBuilder b1; for (auto ref : walletSet.outputs) { -- 2.54.0 From a6774ef8fcc50c55d0f7e420358e9e8496a906f5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 4 May 2023 20:09:43 +0200 Subject: [PATCH 0465/1428] New Andoid release version --- android/AndroidManifest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 6679f44..39e39d6 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,5 +1,7 @@ - + -- 2.54.0 From b0c22c98e1cade6cb9695a8c5359d6e66c0b1402 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 11:34:42 +0200 Subject: [PATCH 0466/1428] Fix regression; make green check show up again On having a correct address we validate it and show that green check. This follows the refactor where we moved the logic to the AddressInfo QML class. --- guis/desktop/SendTransactionPane.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index b6ccc18..5ae90bc 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -313,7 +313,7 @@ Item { Label { color: "green" font.pixelSize: 24 - text: destination.addressOk ? "✔" : " " + text: addressInfo.addressOk ? "✔" : " " } } Flowee.LabelWithClipboard { -- 2.54.0 From d12618f6bc72a2a11581dd20be3f2479a7e80e64 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 11:54:03 +0200 Subject: [PATCH 0467/1428] Fix days math This avoids math problems when the two dates are in a different month. --- src/WalletHistoryModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 930e30b..b03fac1 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -50,7 +50,7 @@ bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) period = WalletEnums::EarlierThisWeek; date = date.addDays(-1 * date.dayOfWeek()); const auto yesterday = today.addDays(-1); - days = yesterday.day() - date.day(); + days = date.daysTo(yesterday); } else if (date >= today.addDays(-1 * today.day() + 1)) { // this month -- 2.54.0 From 88440a6eff2e3c9ed14d4d8a68f4fe458e9b9796 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 12:45:33 +0200 Subject: [PATCH 0468/1428] Various fixes in PriceDataHistory Fixes: #16 * Don't store the initial dummy value '100,-' in the history. * Don't store the 404 server reply as a price file. * On switching of currency, instantly try downloading the historical prices instead of waiting for the next restart. --- src/PriceDataProvider.cpp | 4 ++-- src/PriceHistoryDataProvider.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 785ac17..6fe2ea4 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -276,10 +276,10 @@ void PriceDataProvider::loadPriceHistory(const QString &basedir) if (lastKnownPrice == 0) lastKnownPrice = 10000; // if we never fetched, set to 100,- m_currentPrice.price = lastKnownPrice; - emit priceChanged(lastKnownPrice); connect (this, &PriceDataProvider::priceChanged, m_priceHistory.get(), [=](int price) { - m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); + if (price != 10000) // ignore the magic starting price + m_priceHistory->addPrice(currencyName(), QDateTime::currentSecsSinceEpoch(), price); }); } diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index b17e3dc..3d68c15 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -298,8 +298,9 @@ bool PriceHistoryDataProvider::allowLogCompression() const void PriceHistoryDataProvider::initialPopulate() { - auto cur = currencyData(m_currency, FetchOnly); - if (cur && !cur->hasBlob) { + auto cur = currencyData(m_currency, FetchOrCreate); + assert(cur); + if (!cur->hasBlob) { logCritical() << "populate!"; cur->hasBlob = true; // avoid starting fetcher again InitialHistoryFetcher *f = new InitialHistoryFetcher(this); @@ -348,7 +349,9 @@ void InitialHistoryFetcher::fetch(const QString &path, const QString ¤cy) if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::ContentNotFoundError ) { QFile out(path + '/' + currency); if (out.open(QIODevice::WriteOnly)) { - out.write(reply->readAll()); + // but only write when we have no error. + if (reply->error() == QNetworkReply::NoError) + out.write(reply->readAll()); } else { logWarning() << "Failed to write to fiat file"; -- 2.54.0 From 5be3a6e1f118b54875c34075916c0a1a693df8a8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 12:45:52 +0200 Subject: [PATCH 0469/1428] Remove dead code. --- src/PriceHistoryDataProvider.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index aaf2c74..f83cca4 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -44,7 +44,6 @@ public: int historicalPrice(uint32_t timestamp, HistoricalPriceAccuracy = Nearest) const; QString currencyName() const; - void setCurrency(const QString &newCurrency); /** * Adding values may trigger the 'processLog' method -- 2.54.0 From 5c25710bbe558fc1b2917c65806ab29f43d39717 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 14:32:49 +0200 Subject: [PATCH 0470/1428] Avoid work. Don't load the currency files I'm not actually processing. --- src/PriceHistoryDataProvider.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index 3d68c15..725a5cf 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -38,6 +38,8 @@ PriceHistoryDataProvider::PriceHistoryDataProvider(const QString &basedir, const const bool isLog = currency.endsWith(".LOG"); if (isLog) // cut off the extension currency = currency.left(currency.size() - 4); + if (m_currency != currency) + continue; const qint64 fileSize = iter.fileInfo().size(); if (!isLog && fileSize > 50000) { logInfo() << "Big file in fiat dir, skipping:" << iter.fileName(); -- 2.54.0 From faf8529f71f10add074a331fa310f930d0a8dd42 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 14:40:29 +0200 Subject: [PATCH 0471/1428] Show consistent minus' on prices Both the 'price then' and 'price now' fields always show a positive number. --- guis/mobile/TxInfoSmall.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 1a94df6..5b8cfca 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -134,7 +134,7 @@ GridLayout { return ""; var fiatPriceNow = Fiat.price; var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - return Fiat.formattedPrice(amountBch, fiatPriceNow) + return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; } } -- 2.54.0 From 24dcdefd8b00e60fe1e1816c3f3d2415320ffac1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 16:12:14 +0200 Subject: [PATCH 0472/1428] Remove obsolete version check This added support for an older version of Qt, but we moved to require an even newer one. Making the check irrelevant. --- src/Wallet_support.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index a7d66d0..ee09339 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -211,20 +211,6 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) Tx tx = loadTransaction(wtx.txid, Streaming::pool(0)); info->m_txSize = tx.size(); - /* - * The QML to show these lists of inputs and outputs depends on - * the quality of the column layout component. It turns out that - * in an older version of Qt this simple didn't work and all items - * are shown on top of each other. - * This "fixes" that problem by simply not generating the data - * to show in the UI. - * Known problem was in Ubuntu 20.04 (LTS) with Qt 5.12.8 - */ - const auto qtVersion = QString(qVersion()).split('.'); - assert(qtVersion.size() == 3); - if (qtVersion.at(0).toInt() <= 5 && qtVersion.at(1).toInt() < 15) - return; - // find out how many inputs and how many outputs there are. Tx::Iterator txIter(tx); // If we created this transaction (we have inputs in it anyway) then -- 2.54.0 From 463077e569aea0d5e0833e9677d8b351899a9bc6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 16:59:41 +0200 Subject: [PATCH 0473/1428] Mark tag as deprecated. --- src/WalletHistoryModel.cpp | 2 +- src/WalletHistoryModel.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index b03fac1..bfc0e8c 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -238,7 +238,7 @@ QString WalletHistoryModel::groupingPeriod(int groupId) const QHash WalletHistoryModel::roleNames() const { QHash answer; - answer[NewTransaction] = "isNew"; + answer[NewTransaction] = "isNew"; // Deprecated answer[TxId] = "txid"; answer[MinedHeight] = "height"; answer[MinedDate] = "date"; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 37c989c..fa04427 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -42,7 +42,7 @@ public: enum { TxId = Qt::UserRole, - NewTransaction, ///< bool, if this transaction is newer than lastSyncIndicator + NewTransaction, ///< Deprecated MinedHeight, ///< int, height of block this tx was mined at. MinedDate, ///< A date-time object when the item was mined FundsIn, ///< value (in sats) of the funds we own being spent -- 2.54.0 From fadecb369cbd7351107f3c1d62ed9b0941962277 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 17:01:51 +0200 Subject: [PATCH 0474/1428] Expand the transaction-info page. Add a lot more userful information to the mobile transaction-info page. --- guis/mobile/TransactionDetails.qml | 131 ++++++++++++++++++++++++----- src/TransactionInfo.cpp | 22 +++++ src/TransactionInfo.h | 22 ++++- src/Wallet.cpp | 27 +++--- src/Wallet.h | 4 + src/Wallet_support.cpp | 3 +- 6 files changed, 178 insertions(+), 31 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index a3f5462..0c99de1 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,11 +29,31 @@ Page { ColumnLayout { width: parent.width - Flowee.LabelWithClipboard { - id: txidLabel - text: root.transaction == null ? "" : root.transaction.txid - font.pixelSize: root.font.pixelSize * 0.8 + Flowee.Label { + text: qsTr("Transaction Hash") + ":" + } + Item { + implicitHeight: txidLabel.implicitHeight Layout.fillWidth: true + Flowee.Label { + id: txidLabel + text: root.transaction == null ? "" : root.transaction.txid + anchors.left: parent.left + anchors.right: copyIcon.left + anchors.rightMargin: 10 + wrapMode: Text.WrapAnywhere + } + Rectangle { + id: copyIcon + anchors.right: parent.right + width: 10 + height: 10 + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: Pay.copyToClipboard(txidLabel.text) + } + } } Flowee.Label { text: { @@ -44,9 +64,83 @@ Page { return qsTr("Rejected") if (h === -1) return qsTr("Unconfirmed") - return qsTr("Mined") + ": " + Pay.formatDateTime(root.transaction.date) + return qsTr("Mined") + ": " } } + Flowee.Label { + visible: text !== "" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.fillWidth: true + text: { + var tx = root.transaction; + if (tx == null) + return ""; + if (root.transaction.height < 1) + return ""; + + var answer = Pay.formatDateTime(tx.date) + answer += "\n"; + let txHeight = tx.height; + let blockAge = Pay.chainHeight - txHeight; + answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); + + answer += "\n" + txHeight; + return answer; + } + } + Flowee.Label { + text: qsTr("Transaction comment") + ":" + } + Item { + Layout.fillWidth: true + implicitHeight: commentEdit.implicitHeight + + Flowee.Label { + id: commentLabel + text: root.transaction == null ? "" : root.transaction.comment + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + anchors.baseline: commentEdit.baseline + visible: !commentEdit.visible + } + Rectangle { + id: editIcon + anchors.right: parent.right + width: 10 + height: 10 + // Editing is a risky business until QTBUG-109306 has been deployed + // visible: root.infoObject != null && root.infoObject.commentEditable + visible: false + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + commentEdit.visible = true + commentEdit.focus = true + commentEdit.forceActiveFocus(); + } + } + } + Flowee.TextField { + id: commentEdit + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + visible: false + text: root.transaction == null ? "" : root.transaction.comment + onTextChanged: if (root.infoObject != null) root.infoObject.userComment = text + } + } + Flowee.Label { + text: qsTr("Size") + ":" + } + Flowee.Label { + text: root.infoObject == null ? "" : + qsTr("%1 bytes").arg(root.infoObject.size) + } + Flowee.Label { text: qsTr("Coinbase") visible: root.transaction != null && root.transaction.isCoinbase @@ -55,23 +149,22 @@ Page { text: qsTr("CashFusion transaction") visible: root.transaction != null && root.transaction.isCashFusion } - VisualSeparator {} + + // We can't calculate the fees of just any transaction, only for the ones + // this account created. Flowee.Label { - text: qsTr("Size") + ":" + id: feesPaidLabel + visible: root.infoObject != null && root.infoObject.createdByUs + text: qsTr("Fees paid") + ":" } Flowee.Label { - text: root.infoObject == null ? "" : - qsTr("%1 bytes").arg(root.infoObject.size) + visible: feesPaidLabel.visible + text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") + .arg((root.infoObject.fees * 1000 / root.infoObject.size).toFixed(0)) } - /* - VisualSeparator {} - Flowee.Label { - text: qsTr("Transaction comment") + ":" + Flowee.BitcoinAmountLabel { + visible: feesPaidLabel.visible + value: root.infoObject == null ? 0 : root.infoObject.fees } - QQC2.TextField { - text: root.transaction == null ? "" : root.transaction.comment - Layout.fillWidth: true - onEditingFinished: root.transaction.comment = text - } */ } } diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index bbeb064..8bc2f80 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "TransactionInfo.h" +#include "Wallet.h" TransactionInfo::TransactionInfo(QObject *parent) : QObject(parent) @@ -64,6 +65,16 @@ const QString &TransactionInfo::userComment() const return m_userComment; } +void TransactionInfo::setUserComment(const QString &comment) +{ + if (comment == m_userComment) + return; + if (m_wallet) + m_wallet->setTransactionComment(m_walletIndex, comment); + m_userComment = comment; + emit commentChanged(); +} + bool TransactionInfo::isCashFusion() const { return m_isCashFusion; @@ -79,6 +90,17 @@ bool TransactionInfo::createdByUs() const return m_createdByUs; } +void TransactionInfo::setWalletDetails(Wallet *wallet, int walletIndex) +{ + m_wallet = wallet; + m_walletIndex = walletIndex; +} + +bool TransactionInfo::commentEditable() const +{ + return m_wallet; +} + // ////////////////////////////////////////////////////// diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 88a1bdd..71d55a2 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -22,6 +22,8 @@ #include #include +class Wallet; + class TransactionInputInfo : public QObject { Q_OBJECT @@ -91,10 +93,11 @@ class TransactionInfo : public QObject Q_PROPERTY(double fees READ fees CONSTANT) Q_PROPERTY(QList inputs READ inputs CONSTANT) Q_PROPERTY(QList outputs READ outputs CONSTANT) - Q_PROPERTY(QString userComment READ userComment CONSTANT) + Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY commentChanged) Q_PROPERTY(bool isCoinbase READ isCoinbase CONSTANT) Q_PROPERTY(bool isCashFusion READ isCashFusion CONSTANT) Q_PROPERTY(bool createdByUs READ createdByUs CONSTANT) + Q_PROPERTY(bool commentEditable READ commentEditable CONSTANT) public: explicit TransactionInfo(QObject *parent = nullptr); @@ -104,10 +107,20 @@ public: QList outputs() const; QList inputs() const; const QString &userComment() const; + void setUserComment(const QString &comment); bool isCoinbase() const; bool isCashFusion() const; bool createdByUs() const; + /** + * The API allows the user to set a 'user comment'. Which needs + * to be forwarded to the wallet to make that persistent. + * Call this method with the appropriate details to make enable + * editing of the user comment. + */ + void setWalletDetails(Wallet *wallet, int walletIndex); + bool commentEditable() const; + int m_txSize = 0; QList m_outputs; QList m_inputs; @@ -115,6 +128,13 @@ public: bool m_isCoinbase = false; bool m_isCashFusion = false; bool m_createdByUs = false; + +signals: + void commentChanged(); + +private: + Wallet *m_wallet = nullptr; + int m_walletIndex = -1; }; #endif diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 882a28f..5f845ed 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -683,22 +683,29 @@ void Wallet::setTransactionComment(const Tx &transaction, const QString &comment QMutexLocker locker(&m_lock); auto i = m_txidCache.find(transaction.createHash()); if (i != m_txidCache.end()) { - auto wtxIter = m_walletTransactions.find(i->second); - assert(wtxIter != m_walletTransactions.end()); - auto &wtx = wtxIter->second; - if (wtx.userComment != comment) { - wtx.userComment = comment; - m_walletChanged = true; - - emit transactionChanged(wtxIter->first); - emit startDelayedSave(); - } + setTransactionComment(i->second, comment); } else { logCritical(LOG_WALLET) << "Comment added to not known transaction"; } } +void Wallet::setTransactionComment(int txIndex, const QString &comment) +{ + auto wtxIter = m_walletTransactions.find(txIndex); + assert(wtxIter != m_walletTransactions.end()); + if (wtxIter == m_walletTransactions.end()) + return; + auto &wtx = wtxIter->second; + if (wtx.userComment != comment) { + wtx.userComment = comment; + m_walletChanged = true; + + emit transactionChanged(wtxIter->first); + emit startDelayedSave(); + } +} + std::map Wallet::walletSecrets() const { QMutexLocker locker(&m_lock); diff --git a/src/Wallet.h b/src/Wallet.h index 402bbae..351e60b 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -284,6 +284,10 @@ public: * Set comment field on this transaction, assuming it has been accepted by this wallet. */ void setTransactionComment(const Tx &transaction, const QString &comment); + /** + * Set comment field on this transaction, assuming it has been accepted by this wallet. + */ + void setTransactionComment(int txIndex, const QString &comment); struct WalletSecret { PrivateKey privKey; diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index ee09339..85735ae 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -197,7 +197,8 @@ bool Wallet::WalletTransaction::isRejected() const void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) { - Q_ASSERT(info); + assert(info); + info->setWalletDetails(this, txIndex); QMutexLocker locker(&m_lock); auto iter = m_walletTransactions.find(txIndex); if (m_walletTransactions.end() == iter) -- 2.54.0 From 24ccad719ba850e3cc89bf85c67726eb021ed36a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 17:27:24 +0200 Subject: [PATCH 0475/1428] Add senders/receivers to TransactionDetails screen --- guis/mobile/TransactionDetails.qml | 353 ++++++++++++++++++----------- 1 file changed, 224 insertions(+), 129 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 0c99de1..eab8469 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -27,144 +27,239 @@ Page { property QtObject infoObject: null headerText: qsTr("Transaction Details") - ColumnLayout { - width: parent.width - Flowee.Label { - text: qsTr("Transaction Hash") + ":" - } - Item { - implicitHeight: txidLabel.implicitHeight - Layout.fillWidth: true + Flickable { + anchors.fill: parent + contentHeight: content.height + ColumnLayout { + id: content + width: parent.width Flowee.Label { - id: txidLabel - text: root.transaction == null ? "" : root.transaction.txid - anchors.left: parent.left - anchors.right: copyIcon.left - anchors.rightMargin: 10 - wrapMode: Text.WrapAnywhere + text: qsTr("Transaction Hash") + ":" } - Rectangle { - id: copyIcon - anchors.right: parent.right - width: 10 - height: 10 - MouseArea { - anchors.fill: parent - anchors.margins: -15 - onClicked: Pay.copyToClipboard(txidLabel.text) + Item { + implicitHeight: txidLabel.implicitHeight + Layout.fillWidth: true + Flowee.Label { + id: txidLabel + text: root.transaction == null ? "" : root.transaction.txid + anchors.left: parent.left + anchors.right: copyIcon.left + anchors.rightMargin: 10 + wrapMode: Text.WrapAnywhere } - } - } - Flowee.Label { - text: { - if (root.transaction == null) - return "" - var h = root.transaction.height; - if (h === -2) - return qsTr("Rejected") - if (h === -1) - return qsTr("Unconfirmed") - return qsTr("Mined") + ": " - } - } - Flowee.Label { - visible: text !== "" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - Layout.fillWidth: true - text: { - var tx = root.transaction; - if (tx == null) - return ""; - if (root.transaction.height < 1) - return ""; - - var answer = Pay.formatDateTime(tx.date) - answer += "\n"; - let txHeight = tx.height; - let blockAge = Pay.chainHeight - txHeight; - answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); - - answer += "\n" + txHeight; - return answer; - } - } - Flowee.Label { - text: qsTr("Transaction comment") + ":" - } - Item { - Layout.fillWidth: true - implicitHeight: commentEdit.implicitHeight - - Flowee.Label { - id: commentLabel - text: root.transaction == null ? "" : root.transaction.comment - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - anchors.baseline: commentEdit.baseline - visible: !commentEdit.visible - } - Rectangle { - id: editIcon - anchors.right: parent.right - width: 10 - height: 10 - // Editing is a risky business until QTBUG-109306 has been deployed - // visible: root.infoObject != null && root.infoObject.commentEditable - visible: false - MouseArea { - anchors.fill: parent - anchors.margins: -15 - onClicked: { - commentEdit.visible = true - commentEdit.focus = true - commentEdit.forceActiveFocus(); + Rectangle { + id: copyIcon + anchors.right: parent.right + width: 10 + height: 10 + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: Pay.copyToClipboard(txidLabel.text) } } } - Flowee.TextField { - id: commentEdit - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - visible: false - text: root.transaction == null ? "" : root.transaction.comment - onTextChanged: if (root.infoObject != null) root.infoObject.userComment = text + Flowee.Label { + text: { + if (root.transaction == null) + return "" + var h = root.transaction.height; + if (h === -2) + return qsTr("Rejected") + if (h === -1) + return qsTr("Unconfirmed") + return qsTr("Mined") + ": " + } } - } - Flowee.Label { - text: qsTr("Size") + ":" - } - Flowee.Label { - text: root.infoObject == null ? "" : - qsTr("%1 bytes").arg(root.infoObject.size) - } + Flowee.Label { + visible: text !== "" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.fillWidth: true + text: { + var tx = root.transaction; + if (tx == null) + return ""; + if (root.transaction.height < 1) + return ""; - Flowee.Label { - text: qsTr("Coinbase") - visible: root.transaction != null && root.transaction.isCoinbase - } - Flowee.Label { - text: qsTr("CashFusion transaction") - visible: root.transaction != null && root.transaction.isCashFusion - } + var answer = Pay.formatDateTime(tx.date) + answer += "\n"; + let txHeight = tx.height; + let blockAge = Pay.chainHeight - txHeight; + answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); - // We can't calculate the fees of just any transaction, only for the ones - // this account created. - Flowee.Label { - id: feesPaidLabel - visible: root.infoObject != null && root.infoObject.createdByUs - text: qsTr("Fees paid") + ":" - } - Flowee.Label { - visible: feesPaidLabel.visible - text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") - .arg((root.infoObject.fees * 1000 / root.infoObject.size).toFixed(0)) - } - Flowee.BitcoinAmountLabel { - visible: feesPaidLabel.visible - value: root.infoObject == null ? 0 : root.infoObject.fees + answer += "\n" + txHeight; + return answer; + } + } + Flowee.Label { + text: qsTr("Transaction comment") + ":" + } + Item { + Layout.fillWidth: true + implicitHeight: commentEdit.implicitHeight + + Flowee.Label { + id: commentLabel + text: root.transaction == null ? "" : root.transaction.comment + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + anchors.baseline: commentEdit.baseline + visible: !commentEdit.visible + } + Rectangle { + id: editIcon + anchors.right: parent.right + width: 10 + height: 10 + // Editing is a risky business until QTBUG-109306 has been deployed + // visible: root.infoObject != null && root.infoObject.commentEditable + visible: false + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + commentEdit.visible = true + commentEdit.focus = true + commentEdit.forceActiveFocus(); + } + } + } + Flowee.TextField { + id: commentEdit + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + visible: false + text: root.transaction == null ? "" : root.transaction.comment + onTextChanged: if (root.infoObject != null) root.infoObject.userComment = text + } + } + Flowee.Label { + text: qsTr("Size") + ":" + } + Flowee.Label { + text: root.infoObject == null ? "" : + qsTr("%1 bytes").arg(root.infoObject.size) + } + + Flowee.Label { + text: qsTr("Coinbase") + visible: root.transaction != null && root.transaction.isCoinbase + } + Flowee.Label { + text: qsTr("CashFusion transaction") + visible: root.transaction != null && root.transaction.isCashFusion + } + + // We can't calculate the fees of just any transaction, only for the ones + // this account created. + Flowee.Label { + id: feesPaidLabel + visible: root.infoObject != null && root.infoObject.createdByUs + text: qsTr("Fees paid") + ":" + } + Flowee.Label { + visible: feesPaidLabel.visible + text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") + .arg((root.infoObject.fees * 1000 / root.infoObject.size).toFixed(0)) + } + Flowee.BitcoinAmountLabel { + visible: feesPaidLabel.visible + value: root.infoObject == null ? 0 : root.infoObject.fees + } + + Flowee.Label { text: qsTr("Senders") + ":" } + Repeater { + model: root.infoObject == null ? 0 : root.infoObject.inputs + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width - 10 + height: modelData === null ? 6 : (5 + inAddress.height + amount.height) + Rectangle { + color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" + visible: inAddress.visible + x: inAddress.x - 3 + y: inAddress.y -3 + height: inAddress.height + 6 + width: Math.min(inAddress.width, inAddress.contentWidth) + 6 + radius: height / 3 + opacity: 0.2 + } + Flowee.LabelWithClipboard { + id: inAddress + menuText: qsTr("Copy Address") + text: { + if (modelData === null) + return ""; + var cloaked = modelData.cloakedAddress + if (cloaked !== "") + return cloaked; + return modelData.address; + } + width: parent.width + clipboardText: modelData === null ? "" : modelData.address + visible: modelData !== null + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.BitcoinAmountLabel { + id: amount + visible: modelData !== null + value: modelData === null ? 0 : (-1 * modelData.value) + fiatTimestamp: root.transaction.date + anchors.right: parent.right + anchors.top: inAddress.bottom + } + } + } + + Flowee.Label { text: qsTr("Receivers") + ":" } + Repeater { + model: root.infoObject == null ? 0 : root.infoObject.outputs + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width - 10 + height: modelData === null ? 6 : (5 + outAddress.height + outAmount.height) + Rectangle { + color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" + visible: modelData !== null && modelData.forMe + x: outAddress.x - 3 + y: outAddress.y -3 + height: outAddress.height + 6 + width: Math.min(outAddress.width, outAddress.contentWidth) + 6 + radius: height / 3 + opacity: 0.2 + } + Flowee.LabelWithClipboard { + id: outAddress + visible: modelData !== null + elide: Text.ElideMiddle + menuText: qsTr("Copy Address") + text: { + if (modelData === null) + return ""; + var cloaked = modelData.cloakedAddress + if (cloaked !== "") + return cloaked; + return modelData.address; + } + clipboardText: modelData === null ? "" : modelData.address + width: parent.width + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.BitcoinAmountLabel { + id: outAmount + visible: modelData !== null + value: modelData === null ? 0 : modelData.value + fiatTimestamp: root.transaction.date + colorize: modelData !== null && modelData.forMe + anchors.right: parent.right + anchors.top: outAddress.bottom + } + } + } } } } -- 2.54.0 From 31953638d1adf7d1c916c3d0e0c570eef7c0606f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 17:51:53 +0200 Subject: [PATCH 0476/1428] Display 'sent-to' address For a transaction we sent elsewhere, show which address was the recipient of the funds. --- guis/mobile/TxInfoSmall.qml | 179 ++++++++++++++++++++---------------- src/TransactionInfo.cpp | 21 +++++ src/TransactionInfo.h | 6 ++ 3 files changed, 126 insertions(+), 80 deletions(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 5b8cfca..45b09af 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -27,18 +27,15 @@ import Flowee.org.pay; * among others the isMoved and amountBch values for the transaction it is displaying */ -GridLayout { +ColumnLayout { id: root - columns: 2 - rowSpacing: 10 // set by the parent page property QtObject infoObject: null - property int minedHeight: model.height // local cache - QQC2.Label { - Layout.columnSpan: 2 + spacing: 10 + QQC2.Label { property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' text: { if (isRejected) @@ -57,91 +54,113 @@ GridLayout { } } - Flowee.Label { - visible: root.minedHeight > 0 - text: qsTr("Mined") + ":" - } - Flowee.Label { - Layout.fillWidth: true - visible: root.minedHeight > 0 - text: { - if (root.minedHeight <= 0) - return ""; - var rc = Pay.formatDateTime(model.date); - var confirmations = Pay.headerChainHeight - root.minedHeight + 1; - if (confirmations > 0 && confirmations < 100) - rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; - return rc; + GridLayout { + columns: 2 + rowSpacing: 10 + width: parent.width + + Flowee.Label { + visible: root.minedHeight > 0 + text: qsTr("Mined") + ":" } - } - Flowee.Label { - id: paymentTypeLabel - Layout.columnSpan: isMoved ? 2 : 1 - text: { - if (model.isCoinbase) - return qsTr("Miner Reward") + ":"; - if (model.isCashFusion) - return qsTr("Cash Fusion") + ":"; - if (model.fundsIn === 0) - return qsTr("Received") + ":"; - if (isMoved) - return qsTr("Payment to self"); - return qsTr("Sent") + ":"; + Flowee.Label { + Layout.fillWidth: true + visible: root.minedHeight > 0 + text: { + if (root.minedHeight <= 0) + return ""; + var rc = Pay.formatDateTime(model.date); + var confirmations = Pay.headerChainHeight - root.minedHeight + 1; + if (confirmations > 0 && confirmations < 100) + rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; + return rc; + } + } + Flowee.Label { + id: paymentTypeLabel + Layout.columnSpan: isMoved ? 2 : 1 + text: { + if (model.isCoinbase) + return qsTr("Miner Reward") + ":"; + if (model.isCashFusion) + return qsTr("Cash Fusion") + ":"; + if (model.fundsIn === 0) + return qsTr("Received") + ":"; + if (isMoved) + return qsTr("Payment to self"); + return qsTr("Sent") + ":"; + } + } + Flowee.BitcoinAmountLabel { + visible: isMoved === false + Layout.fillWidth: true + value: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) + fiatTimestamp: model.date + showFiat: false // might not fit } - } - Flowee.BitcoinAmountLabel { - visible: isMoved === false - Layout.fillWidth: true - value: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) - fiatTimestamp: model.date - showFiat: false // might not fit } - // price at mining - // value in exchange gained Flowee.Label { - id: priceAtMining - visible: { - if (root.minedHeight < 1) - return false; - if (model.isCashFusion) - return false; - if (isMoved) - return false; - if (valueThenLabel.fiatPrice === 0) - return false; - return true; + text: qsTr("Sent to") + ":" + visible: receiverName.text !== "" + } + Flowee.LabelWithClipboard { + id: receiverName + Layout.fillWidth: true + visible: text !== "" + text: infoObject == null ? "" : infoObject.receiver + font.pixelSize: paymentTypeLabel.font.pixelSize * 0.8 + } + GridLayout { + columns: 2 + rowSpacing: 10 + width: parent.width + + Flowee.Label { + visible: priceAtMining.visible + text: qsTr("Value now") + ":" } - text: qsTr("Value then") + ":" - } - Flowee.Label { - Layout.fillWidth: true - id: valueThenLabel - visible: priceAtMining.visible - // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false - property int fiatPrice: Fiat.historicalPriceAccurate(model.date) - text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) - } - Flowee.Label { - visible: priceAtMining.visible - text: qsTr("Value now") + ":" - } - Flowee.Label { - Layout.fillWidth: true - visible: priceAtMining.visible - text: { - if (root.minedHeight <= 0) - return ""; - var fiatPriceNow = Fiat.price; - var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) - + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; + Flowee.Label { + visible: priceAtMining.visible + text: { + if (root.minedHeight <= 0) + return ""; + var fiatPriceNow = Fiat.price; + var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 + return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) + + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; + } + } + + // price at mining + // value in exchange gained + Flowee.Label { + id: priceAtMining + visible: { + if (root.minedHeight < 1) + return false; + if (model.isCashFusion) + return false; + if (isMoved) + return false; + if (valueThenLabel.fiatPrice === 0) + return false; + return true; + } + text: qsTr("Value then") + ":" + } + Flowee.Label { + Layout.fillWidth: true + id: valueThenLabel + visible: priceAtMining.visible + // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false + property int fiatPrice: Fiat.historicalPriceAccurate(model.date) + text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) } } TextButton { id: txDetailsButton - Layout.columnSpan: 2 text: qsTr("Transaction Details") showPageIcon: true onClicked: { diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 8bc2f80..34fe59b 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -90,6 +90,27 @@ bool TransactionInfo::createdByUs() const return m_createdByUs; } +QString TransactionInfo::receiver() const +{ + if (!m_createdByUs) // the receiver, is us! + return QString(); + /* + * We iterate over the outputs and reject all outputs that we own. + * If exactly one is left we have a 'receiver'. + */ + QString answer; + for (auto out : m_outputs) { + if (!out->forMe()) { + if (answer.isEmpty()) + answer = out->address(); + else + return QString(); // more than one address + } + + } + return answer; +} + void TransactionInfo::setWalletDetails(Wallet *wallet, int walletIndex) { m_wallet = wallet; diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 71d55a2..1413e84 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -94,6 +94,10 @@ class TransactionInfo : public QObject Q_PROPERTY(QList inputs READ inputs CONSTANT) Q_PROPERTY(QList outputs READ outputs CONSTANT) Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY commentChanged) + /** + * The recipient of the transaction. Typically just an address. + */ + Q_PROPERTY(QString receiver READ receiver CONSTANT) Q_PROPERTY(bool isCoinbase READ isCoinbase CONSTANT) Q_PROPERTY(bool isCashFusion READ isCashFusion CONSTANT) Q_PROPERTY(bool createdByUs READ createdByUs CONSTANT) @@ -112,6 +116,8 @@ public: bool isCashFusion() const; bool createdByUs() const; + QString receiver() const; + /** * The API allows the user to set a 'user comment'. Which needs * to be forwarded to the wallet to make that persistent. -- 2.54.0 From 371decdd3798cfddc596dcc3abaeabbd3327b831 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 17:58:38 +0200 Subject: [PATCH 0477/1428] Replace place-holders with actual images. --- guis/images/edit-copy-light.svg | 5 +++++ guis/mobile/TransactionDetails.qml | 15 +++++++++------ guis/widgets.qrc | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 guis/images/edit-copy-light.svg diff --git a/guis/images/edit-copy-light.svg b/guis/images/edit-copy-light.svg new file mode 100644 index 0000000..556eb6f --- /dev/null +++ b/guis/images/edit-copy-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index eab8469..e02b65c 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -47,11 +47,12 @@ Page { anchors.rightMargin: 10 wrapMode: Text.WrapAnywhere } - Rectangle { + Image { id: copyIcon anchors.right: parent.right - width: 10 - height: 10 + width: 20 + height: 20 + source: "qrc:/edit-copy" + (Pay.useDarkSkin ? "-light" : "") + ".svg" MouseArea { anchors.fill: parent anchors.margins: -15 @@ -109,11 +110,13 @@ Page { anchors.baseline: commentEdit.baseline visible: !commentEdit.visible } - Rectangle { + Image { id: editIcon anchors.right: parent.right - width: 10 - height: 10 + width: 20 + height: 20 + smooth: true + source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" // Editing is a risky business until QTBUG-109306 has been deployed // visible: root.infoObject != null && root.infoObject.commentEditable visible: false diff --git a/guis/widgets.qrc b/guis/widgets.qrc index ae0a716..cfd6bf9 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -1,6 +1,7 @@ images/edit-copy.svg + images/edit-copy-light.svg images/internet.svg Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml -- 2.54.0 From 0740c0b29003fe929c6c40de591cf5b480c7a354 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 18:32:54 +0200 Subject: [PATCH 0478/1428] Fix spacing in address lists. --- guis/mobile/TransactionDetails.qml | 211 +++++++++++++++++------------ 1 file changed, 127 insertions(+), 84 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index e02b65c..682cbbe 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -118,7 +118,7 @@ Page { smooth: true source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" // Editing is a risky business until QTBUG-109306 has been deployed - // visible: root.infoObject != null && root.infoObject.commentEditable + // visible: root.infoObject != null && infoObject.commentEditable visible: false MouseArea { anchors.fill: parent @@ -137,7 +137,7 @@ Page { anchors.rightMargin: 10 visible: false text: root.transaction == null ? "" : root.transaction.comment - onTextChanged: if (root.infoObject != null) root.infoObject.userComment = text + onTextChanged: if (root.infoObject != null) infoObject.userComment = text } } Flowee.Label { @@ -145,7 +145,7 @@ Page { } Flowee.Label { text: root.infoObject == null ? "" : - qsTr("%1 bytes").arg(root.infoObject.size) + qsTr("%1 bytes").arg(infoObject.size) } Flowee.Label { @@ -161,105 +161,148 @@ Page { // this account created. Flowee.Label { id: feesPaidLabel - visible: root.infoObject != null && root.infoObject.createdByUs + visible: root.infoObject != null && infoObject.createdByUs text: qsTr("Fees paid") + ":" } Flowee.Label { visible: feesPaidLabel.visible text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") - .arg((root.infoObject.fees * 1000 / root.infoObject.size).toFixed(0)) + .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) } Flowee.BitcoinAmountLabel { visible: feesPaidLabel.visible - value: root.infoObject == null ? 0 : root.infoObject.fees + value: root.infoObject == null ? 0 : infoObject.fees } - Flowee.Label { text: qsTr("Senders") + ":" } - Repeater { - model: root.infoObject == null ? 0 : root.infoObject.inputs - delegate: Item { - Layout.alignment: Qt.AlignRight - width: content.width - 10 - height: modelData === null ? 6 : (5 + inAddress.height + amount.height) - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: inAddress.visible - x: inAddress.x - 3 - y: inAddress.y -3 - height: inAddress.height + 6 - width: Math.min(inAddress.width, inAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { - id: inAddress - menuText: qsTr("Copy Address") - text: { + Flowee.Label { + id: sendersLabel + text: qsTr("Senders") + ":" + visible: root.infoObject != null && (infoObject.createdByUs || infoObject.isCashFusion) + } + Column { + // we nest a column in a column to be able to skip all the rows that are not ours. + // only really relevant with loads of inputs and outputs. + width: content.width + Repeater { + /* + * We only have the one transaction, which means we only have the previous txid and we have + * no details about which address or anything else these funds come from if we didn't + * create the Tx. So just don't show anything. + */ + model: sendersLabel.visible ? infoObject.inputs : 0 + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width + height: { if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; + return 0; + if (inAddress.implicitWidth + 10 + amount.implicitWidth < content.width) + return inAddress.height + 10; + return inAddress.height + amount.height + 16; + } + Rectangle { + color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" + visible: inAddress.visible + x: inAddress.x - 3 + y: inAddress.y -3 + height: inAddress.height + 6 + width: Math.min(inAddress.width, inAddress.contentWidth) + 6 + radius: height / 3 + opacity: 0.2 + } + Flowee.LabelWithClipboard { + id: inAddress + menuText: qsTr("Copy Address") + text: { + if (modelData === null) + return ""; + var cloaked = modelData.cloakedAddress + if (cloaked !== "") + return cloaked; + return modelData.address; + } + x: 10 + width: parent.width + clipboardText: modelData === null ? "" : modelData.address + visible: modelData !== null + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.BitcoinAmountLabel { + id: amount + visible: modelData !== null + value: modelData === null ? 0 : (-1 * modelData.value) + fiatTimestamp: root.transaction.date + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 } - width: parent.width - clipboardText: modelData === null ? "" : modelData.address - visible: modelData !== null - font.pixelSize: root.font.pixelSize * 0.8 - } - Flowee.BitcoinAmountLabel { - id: amount - visible: modelData !== null - value: modelData === null ? 0 : (-1 * modelData.value) - fiatTimestamp: root.transaction.date - anchors.right: parent.right - anchors.top: inAddress.bottom } } } - Flowee.Label { text: qsTr("Receivers") + ":" } - Repeater { - model: root.infoObject == null ? 0 : root.infoObject.outputs - delegate: Item { - Layout.alignment: Qt.AlignRight - width: content.width - 10 - height: modelData === null ? 6 : (5 + outAddress.height + outAmount.height) - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: modelData !== null && modelData.forMe - x: outAddress.x - 3 - y: outAddress.y -3 - height: outAddress.height + 6 - width: Math.min(outAddress.width, outAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { - id: outAddress - visible: modelData !== null - elide: Text.ElideMiddle - menuText: qsTr("Copy Address") - text: { + Flowee.Label { + text: { + if (root.infoObject == null) + return ""; + if (infoObject.createdByUs) + return qsTr("Deposit Address") + ":" + return qsTr("Receivers") + ":" + } + } + Column { + // we nest a column in a column to be able to skip all the rows that are not ours. + // only really relevant with loads of inputs and outputs. + width: content.width + Repeater { + model: root.infoObject == null ? 0 : infoObject.outputs + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width + height: { if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; + return 0; + if (outAddress.implicitWidth + 10 + outAmount.implicitWidth < width) + return outAmount.height + 10; + return outAddress.height + outAmount.height + 16; + } + Rectangle { + color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" + visible: modelData !== null && modelData.forMe + x: outAddress.x - 3 + y: outAddress.y -3 + height: outAddress.height + 6 + width: Math.min(outAddress.width, outAddress.contentWidth) + 6 + radius: height / 3 + opacity: 0.2 + } + Flowee.LabelWithClipboard { + id: outAddress + x: 10 + visible: modelData !== null + elide: Text.ElideMiddle + menuText: qsTr("Copy Address") + text: { + if (modelData === null) + return ""; + var cloaked = modelData.cloakedAddress + if (cloaked !== "") + return cloaked; + return modelData.address; + } + clipboardText: modelData === null ? "" : modelData.address + width: parent.width + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.BitcoinAmountLabel { + id: outAmount + visible: modelData !== null + value: modelData === null ? 0 : modelData.value + fiatTimestamp: root.transaction.date + colorize: modelData !== null && modelData.forMe + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 } - clipboardText: modelData === null ? "" : modelData.address - width: parent.width - font.pixelSize: root.font.pixelSize * 0.8 - } - Flowee.BitcoinAmountLabel { - id: outAmount - visible: modelData !== null - value: modelData === null ? 0 : modelData.value - fiatTimestamp: root.transaction.date - colorize: modelData !== null && modelData.forMe - anchors.right: parent.right - anchors.top: outAddress.bottom } } } -- 2.54.0 From 319ab0bea2f0bd3d4f8a7a213c5d192ec00f379e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 May 2023 19:35:47 +0200 Subject: [PATCH 0479/1428] Make UX pretty. Discovered various small isseus in a user test, this fixes them. --- guis/Flowee/BitcoinAmountLabel.qml | 15 ++++++++++----- guis/mobile/TransactionDetails.qml | 31 ++++++++++++++++++++---------- guis/mobile/TxInfoSmall.qml | 5 +++-- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 48be8dd..28e95c7 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,12 @@ import QtQuick.Layouts 1.11 */ QQC2.Control { id: root - property double value: 5E8 + property double value: 0 + /// When colorize is true can turn the label + /// green or red based on the value. Making this property + /// hold a different number than 'value' will allow things like + /// a red colored label without a minus. + property double colorizeValue: value property bool colorize: true property bool includeUnit: true property bool showFiat: true @@ -74,13 +79,13 @@ QQC2.Control { } color: { if (root.colorize) { - var num = root.value + let num = root.colorizeValue if (num > 0) // positive value return Pay.useDarkSkin ? "#86ffa8" : "green"; else if (num < 0) // negative - return Pay.useDarkSkin ? "#ffdede" : "#444446"; - // zero is shown without color, like below. + return Pay.useDarkSkin ? "#ffc0c0" : "#5f1717"; + // zero is shown in normal color } return root.color } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 682cbbe..7986e69 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -69,7 +69,7 @@ Page { return qsTr("Rejected") if (h === -1) return qsTr("Unconfirmed") - return qsTr("Mined") + ": " + return qsTr("Mined at") + ": " } } Flowee.Label { @@ -83,13 +83,11 @@ Page { if (root.transaction.height < 1) return ""; - var answer = Pay.formatDateTime(tx.date) - answer += "\n"; let txHeight = tx.height; + var answer = txHeight + "\n" + Pay.formatDateTime(tx.date) let blockAge = Pay.chainHeight - txHeight; + answer += "\n"; answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); - - answer += "\n" + txHeight; return answer; } } @@ -153,7 +151,7 @@ Page { visible: root.transaction != null && root.transaction.isCoinbase } Flowee.Label { - text: qsTr("CashFusion transaction") + text: qsTr("Is a CashFusion transaction.") visible: root.transaction != null && root.transaction.isCashFusion } @@ -172,12 +170,23 @@ Page { Flowee.BitcoinAmountLabel { visible: feesPaidLabel.visible value: root.infoObject == null ? 0 : infoObject.fees + colorize: false } Flowee.Label { id: sendersLabel - text: qsTr("Senders") + ":" - visible: root.infoObject != null && (infoObject.createdByUs || infoObject.isCashFusion) + text: { + if (root.infoObject == null) + return ""; + if (infoObject.isCashFusion) + return qsTr("Fused from my addresses") + ":" + if (infoObject.createdByUs) + return qsTr("Sent from my addresses") + ":"; + if (infoObject.isCashFusion) + return qsTr("Sent from addresses") + ":"; + return ""; + } + visible: text !== "" } Column { // we nest a column in a column to be able to skip all the rows that are not ours. @@ -244,9 +253,11 @@ Page { text: { if (root.infoObject == null) return ""; + if (infoObject.isCashFusion) + return qsTr("Fused into my addresses") + ":" if (infoObject.createdByUs) - return qsTr("Deposit Address") + ":" - return qsTr("Receivers") + ":" + return qsTr("Received at addresses") + ":" + return qsTr("Received at my addresses") + ":" } } Column { diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 45b09af..9c8bfea 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -94,7 +94,8 @@ ColumnLayout { Flowee.BitcoinAmountLabel { visible: isMoved === false Layout.fillWidth: true - value: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) + colorizeValue: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) + value: Math.abs(colorizeValue) fiatTimestamp: model.date showFiat: false // might not fit } @@ -106,7 +107,7 @@ ColumnLayout { } Flowee.LabelWithClipboard { id: receiverName - Layout.fillWidth: true + Layout.alignment: Qt.AlignRight visible: text !== "" text: infoObject == null ? "" : infoObject.receiver font.pixelSize: paymentTypeLabel.font.pixelSize * 0.8 -- 2.54.0 From b0d06d6f71101e7533e4c7c2358bd51a41bd4620 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 7 May 2023 13:59:22 +0200 Subject: [PATCH 0480/1428] import translations from crowdin --- translations/floweepay-mobile_nl.ts | 71 +++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index e35101d..2a3ebe3 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -699,18 +699,10 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Unconfirmed Onbevestigd - - Mined - Gedolven - Coinbase Coinbase - - CashFusion transaction - CashFusion transactie - Size Grootte @@ -719,6 +711,65 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt %1 bytes %1 bytes + + Transaction Hash + Transactie hash + + + Mined at + Gedolven in + + + %1 blocks ago + + Meest recente blok + %1 blokken terug + + + + Transaction comment + Transactie omschrijving + + + Is a CashFusion transaction. + CashFusion transactie. + + + Fees paid + Betaalde kosten + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + Fused from my addresses + Mijn gefuseerde adressen + + + Sent from my addresses + Verzonden vanaf mijn adressen + + + Sent from addresses + Verzonden vanaf adressen + + + Copy Address + Kopieer adres + + + Fused into my addresses + Gefuseerd naar mijn adressen + + + Received at addresses + Ontvangen op adressen + + + Received at my addresses + Ontvangen op mijn adressen + TxInfoSmall @@ -774,5 +825,9 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Transaction Details Transactiedetails + + Sent to + Verstuurd naar + -- 2.54.0 From a779bb5f4542e5e9503d974abd53b0d84d13c11b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 7 May 2023 17:03:44 +0200 Subject: [PATCH 0481/1428] Be much more persistent parsing a mnemonic We now handle correctly user input that we can expect when they manually enter the mnemonic via an on-screen keyboard. This includes spacing, line feeds and uppercasing of the first word. --- src/FloweePay.cpp | 140 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index db0fe3c..6cd62e6 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -70,6 +70,51 @@ enum FileTags { static P2PNet::Chain s_chain = P2PNet::MainChain; +namespace { +QList splitString(const QString &input) +{ + QList words; + const QStringView stringView(input); + int wordStart = -1; + for (int i = 0; i < input.length(); ++i) { + if (input.at(i).isSpace()) { + if (wordStart != -1) { + words.append(stringView.sliced(wordStart, i - wordStart)); + wordStart = -1; + } + } + else if (wordStart == -1) { + wordStart = i; + } + } + if (wordStart != -1) + words.append(stringView.sliced(wordStart, stringView.length() - wordStart)); + + return words; +} + +QString joinWords(const QList &words, bool lowercaseFirstWord) +{ + QString string; + for (const auto word : words) { + if (string.isEmpty()) { + if (lowercaseFirstWord) { + string = word.toString().toLower(); + continue; + } + } + else { + string += QLatin1Char(' '); + } + string += word.toString(); + } + + return string; +} + + +} + FloweePay::FloweePay() : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), m_chain(s_chain) @@ -822,11 +867,14 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, const QString &walletName, int startHeight) { + auto words = splitString(privateKey); // this is great to remove any type of whitespace + if (words.size() != 1) + throw std::runtime_error("Not valid private key"); auto wallet = createWallet(walletName); wallet->setSingleAddressWallet(true); if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; - wallet->addPrivateKey(privateKey.trimmed().remove('\n'), startHeight); + wallet->addPrivateKey(words.first().toString(), startHeight); emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -875,7 +923,8 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons std::vector derivationPath = HDMasterKey::deriveFromString(derivationPathStr.toStdString()); if (startHeight <= 1) startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; - wallet->createHDMasterKey(mnemonic.trimmed().remove('\n'), password.trimmed().remove('\n'), + auto seedWords = splitString(mnemonic); // this is great to remove any type of whitespace + wallet->createHDMasterKey(joinWords(seedWords, true), password.trimmed().remove('\n'), derivationPath, startHeight); emit walletsChanged(); if (!m_offline) @@ -900,13 +949,57 @@ bool FloweePay::checkDerivation(const QString &path) const WalletEnums::StringType FloweePay::identifyString(const QString &string) const { - const QString string_ = string.trimmed().remove('\n'); - const std::string s = string_.toStdString(); - if (string_.isEmpty()) { + auto words = splitString(string); + if (words.isEmpty()) { m_hdSeedValidator.clearSelectedLanguage(); return WalletEnums::Unknown; } + try { + int firstWord = -2; + // split into words. + for (const auto word : words) { + int index = m_hdSeedValidator.findWord(word.toString()); + if (firstWord == -2) { + bool lowerCased = false; + if (index == -1) { + // the first word, especially on Mobile, tends to start + // with an uppercase, expect that. + auto wordLc = word.toString().toLower(); + if (wordLc != word) { + index = m_hdSeedValidator.findWord(wordLc); + lowerCased = true; + } + } + firstWord = index; + if (index != -1) { + QString mnemonic = joinWords(words, lowerCased); + auto validity = m_hdSeedValidator.validateMnemonic(mnemonic, index); + if (validity == Mnemonic::Valid) + return WalletEnums::CorrectMnemonic; + } + else { // not a recognized word + break; + } + } + else if (index == -1) { // a not-first-word failed the lookup. + if (word != words.last()) // this is the last word, don't highlight while writing. + return WalletEnums::PartialMnemonicWithTypo; + break; + } + // if we get to this point in the loop then we have a real word that we found in the dictionary. + // Lets continue checking words and check if the rest of the words are part of the lexicon too. + } + if (firstWord >= 0) + return WalletEnums::PartialMnemonic; + } catch (const std::exception &e) { + // probably deployment issues (faulty word list) + logFatal() << e; + return WalletEnums::MissingLexicon; + } + + const QString string_ = joinWords(words, false); + const std::string s = string_.toStdString(); CBase58Data legacy; if (legacy.SetString(s)) { if ((m_chain == P2PNet::MainChain && legacy.isMainnetPkh()) @@ -928,43 +1021,6 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const return WalletEnums::CashSH; } - try { - int firstWord = -2; - int space = -1; - do { - ++space; - int space2 = string.indexOf(' ', space); - auto word = string.mid(space, space2 - space); - if (!word.isEmpty()) { - int index = m_hdSeedValidator.findWord(word); - if (firstWord == -2) { - firstWord = index; - if (index != -1) { - auto validity = m_hdSeedValidator.validateMnemonic(string_, index); - if (validity == Mnemonic::Valid) - return WalletEnums::CorrectMnemonic; - } - else { // not a recognized word - break; - } - } - else if (index == -1) { // a not-first-word failed the lookup. - if (space2 != -1) // this is the last word, don't highlight while writing. - return WalletEnums::PartialMnemonicWithTypo; - break; - } - // if we get to this point in the loop then we have a real word that we found in the dictionary. - // Lets continue checking words and check if the rest of the words are part of the lexicon too. - } - space = space2; - } while (space != -1); - if (firstWord >= 0) - return WalletEnums::PartialMnemonic; - } catch (const std::exception &e) { - // probably deployment issues (faulty word list) - logFatal() << e; - return WalletEnums::MissingLexicon; - } return WalletEnums::Unknown; } -- 2.54.0 From 14871ab90a7b6214888dab7f6db16445c2a78a8e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 7 May 2023 17:13:43 +0200 Subject: [PATCH 0482/1428] Move the first-date widgets up This makes sure that they show up even if the on-screen keyboard is open --- guis/mobile/ImportWalletPage.qml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 2180a1c..25c1c12 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -84,7 +84,7 @@ Page { Flowee.Label { id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color + color: typedData === Wallet.PartialMnemonicWithTypo ? mainWindow.errorRed : feedback.color text: { var typedData = importAccount.typedData if (typedData === Wallet.PrivateKey) @@ -118,19 +118,6 @@ Page { visible: importAccount.isPrivateKey Layout.columnSpan: 2 } - Flowee.Label { - text: qsTr("Alternate phrase") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - Layout.columnSpan: 2 - } Flowee.Label { Layout.columnSpan: 2 text: qsTr("Oldest Transaction") + ":" @@ -174,5 +161,18 @@ Page { visible: !importAccount.isPrivateKey color: Pay.checkDerivation(text) ? palette.text : "red" } + Flowee.Label { + text: qsTr("Alternate phrase") + ":" + Layout.columnSpan: 2 + visible: !importAccount.isPrivateKey + } + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + visible: !importAccount.isPrivateKey + Layout.fillWidth: true + Layout.columnSpan: 2 + } } } -- 2.54.0 From 7a5eced658e805ad2942776c5a1d3718c30b556e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 7 May 2023 17:55:07 +0200 Subject: [PATCH 0483/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 39e39d6..61d733a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="9" android:versionName="2023.05.1"> diff --git a/src/main.cpp b/src/main.cpp index fefe8d6..141a831 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.05.0"); + qapp.setApplicationVersion("2023.05.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 5125ecc222b8b7b5dc65038d25827e013ae1b884 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 8 May 2023 10:28:04 +0200 Subject: [PATCH 0484/1428] Upgrade tests for 'earlier-this-month' model. This fixes some cornercases and makes the test a) properly data-driven (don't use 'today'). b) much more extensive. This also removes some duplicate code and fixes some bugs in the actual model it tests. --- src/WalletHistoryModel.cpp | 51 +++++----- src/WalletHistoryModel.h | 11 ++- .../TestWalletHistoryModel.cpp | 92 +++++++++++++++---- .../TestWalletHistoryModel.h | 1 + 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index bfc0e8c..e4fb5f5 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -28,7 +28,7 @@ * Attempt to add a transaction to this group. * Retuns false if the txIndex is not meant for this group */ -bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) +bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp, const QDate &today) { if (startTxIndex == -1) { startTxIndex = txIndex; @@ -36,7 +36,6 @@ bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) // first one in this group. Now we need to decide which period we area actually looking at. QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); int days = 0; - QDate today = QDate::currentDate(); if (date == today) { period = WalletEnums::Today; days = 1; @@ -52,28 +51,24 @@ bool WalletHistoryModel::TransactionGroup::add(int txIndex, uint32_t timestamp) const auto yesterday = today.addDays(-1); days = date.daysTo(yesterday); } - else if (date >= today.addDays(-1 * today.day() + 1)) { - // this month - period = WalletEnums::EarlierThisMonth; - date = date.addDays(-1 * date.day() + 1); - const auto weekStart = today.addDays(-1 * today.dayOfWeek() + 1); - days = weekStart.day() - 1; - - const auto yesterday = today.addDays(-1); - if (yesterday.year() == date.year() && yesterday.month() == date.month()) { - // don't eat the events that happend yesterday. - days -= 1; - } - } - else { // any (other) month + else { // a whole month. period = WalletEnums::Month; - date = date.addDays(-1 * date.day() + 1); - days = date.daysInMonth(); + if (date >= today.addDays(-1 * today.day() + 1)) // special case THIS month + period = WalletEnums::EarlierThisMonth; - const auto yesterday = today.addDays(-1); - if (yesterday.year() == date.year() && yesterday.month() == date.month()) { - // don't eat the events that happend yesterday. - days -= 1; + days = date.daysInMonth(); + date = date.addDays(-1 * date.day() + 1); + const auto endOfMonth = date.addDays(days); + // make it smaller if needed. + const auto startThisWeek = today.addDays(-1 * today.dayOfWeek() + 1); + if (startThisWeek < endOfMonth) { + days = startThisWeek.day() - 1; + } + else { + // date = date.addDays(-1 * date.day() + 1); + const auto yesterday = today.addDays(-1); + if (yesterday < endOfMonth) // don't eat the events that happend yesterday. + days -= 1; } } const QDateTime dt(date, QTime()); @@ -228,7 +223,7 @@ QString WalletHistoryModel::groupingPeriod(int groupId) const default: { uint32_t timestamp = m_groups.at(groupId).endTime; QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); - if (date.year() == QDate::currentDate().year()) + if (date.year() == m_today.year()) return date.toString("MMMM"); return date.toString("MMMM yyyy"); } @@ -338,6 +333,7 @@ void WalletHistoryModel::createMap() m_rowsProxy.clear(); m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); m_groups.clear(); + m_today = today(); // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. @@ -382,11 +378,11 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) return; } - if (!m_groups.back().add(txIndex, timestamp)) { + if (!m_groups.back().add(txIndex, timestamp, m_today)) { // didn't fit, make a new group and add it there. TransactionGroup newGroup; newGroup.period = m_groups.back().period; - bool ok = newGroup.add(txIndex, timestamp); + bool ok = newGroup.add(txIndex, timestamp, m_today); assert (ok); m_groups.push_back(newGroup); } @@ -450,6 +446,11 @@ uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const return FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime; } +QDate WalletHistoryModel::today() const +{ + return QDate::currentDate(); +} + int WalletHistoryModel::lastSyncIndicator() const { return m_lastSyncIndicator; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index fa04427..5c7d54d 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -19,6 +19,7 @@ #define WALLETHSTORYMODEL_H #include +#include #include "Wallet.h" #include "WalletEnums.h" @@ -83,7 +84,7 @@ public: uint32_t endTime = 0; /// returns true if added, false if the tx-index is not for this group - bool add(int txIndex, uint32_t timestamp); + bool add(int txIndex, uint32_t timestamp, const QDate &today); }; void freezeModel(bool on); @@ -99,6 +100,9 @@ protected: /// return the timestamp of a block (aka nTime) as defined by the block-header virtual uint32_t secsSinceEpochFor(int blockHeight) const; + // only reimplemented in the unit test. + virtual QDate today() const; + protected slots: void appendTransactions(int firstNew, int count); void removeTransaction(int txIndex); @@ -126,6 +130,11 @@ private: QVector m_rowsProxy; Wallet *m_wallet; QFlags m_includeFlags = WalletEnums::IncludeAll; + /* + * To avoid weirdness in the assiging of transactions to groups based on the current date, + * we keep the 'today' variable the same for the duration. + */ + QDate m_today; std::vector m_groups; struct GroupCache { diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.cpp b/testing/walletHistoryModel/TestWalletHistoryModel.cpp index 33f3a2b..5390008 100644 --- a/testing/walletHistoryModel/TestWalletHistoryModel.cpp +++ b/testing/walletHistoryModel/TestWalletHistoryModel.cpp @@ -31,15 +31,29 @@ public: appendTransactions(firstNew, count); } + void setMockDate(const QDate &newMockDate) { + m_mockDate = newMockDate; + createMap(); + } + protected: uint32_t secsSinceEpochFor(int blockHeight) const override { assert(blockHeight > 0); assert(blockHeight <= 6000); - // lets make our blocks range from 1000 hours ago (a little over 40 days) to now. + // lets make our blocks range from 1000 hours ago (a little over 40 days) to 'mockdate'. // we define one block every 600 seconds exect, making the 1000 hours take 6000 blocks. - static auto now = QDateTime::currentSecsSinceEpoch(); - return static_cast(now - 1000 * 3600) + blockHeight * 600; + assert(m_mockDate.isValid()); + QDateTime d(m_mockDate, QTime(12, 0, 0)); + const auto mockSecsSinceEpoch = d.toSecsSinceEpoch(); + return static_cast(mockSecsSinceEpoch - 1000 * 3600) + blockHeight * 600; } + QDate today() const override { + assert(m_mockDate.isValid()); + return m_mockDate; + } + +private: + QDate m_mockDate; }; class MockWallet : public Wallet @@ -72,8 +86,16 @@ void TestWalletHistoryModel::cleanup() void TestWalletHistoryModel::basic() { + QFETCH(QDate, date); + QFETCH(QList, thisWeek); + QFETCH(QList, thisMonth); + + assert(date.isValid()); + logCritical() << "Our TODAY:" << date.toString(); + auto wallet = createWallet(); std::unique_ptr model(new MockWalletHistoryModel(wallet.get())); + model->setMockDate(date); QCOMPARE(model->rowCount(), 0); wallet->createTransactions1(); model->notifyNewTransactions(1, 6000); // wallet starts counting at 1 @@ -86,14 +108,14 @@ void TestWalletHistoryModel::basic() first = model->data(model->index(0, 0), WalletHistoryModel::GroupId); QVERIFY(first.isValid()); /* - * As the grouping is done on the current date, I need to also verify relative to the - * current date. - * The first transaction will always be in "today", but the amount of transactions after - * that are in that same group is relative to what time it is... + * The first transaction will always be in "today", since the mock-model + * hardcodes that we have 12 hours in todays date and thus 12 * 6 = 72 blocks. + * But the amount of transactions after that are in that same group is relative + * to the picked date. Which is the reason we test various dates. * * This leads the groups to be numbered like this: * - * 0: prev month [may be twice] + * 0: some month [may be twice] * 1: earlier this month [optional] * 2: earlier this week [optional] * 3: yesterday [optional] @@ -110,26 +132,60 @@ void TestWalletHistoryModel::basic() QVERIFY(first.toInt() > groupId.toInt()); QCOMPARE(model->groupingPeriod(groupId.toInt()), "Yesterday"); - const auto now = QDateTime::currentDateTime(); - auto today = now.date(); - logFatal() << "dayOfWeek" << today.dayOfWeek(); - if (today.dayOfWeek() > 2 && today.dayOfWeek() <= today.day()) { - // that means we should have an 'EarlierThisWeek' group. +#if 0 + groupId = -1; + for (int i = 0; i < 6000; ++i) { + int x = model->data(model->index(i, 0), WalletHistoryModel::GroupId).toInt(); + if (x != groupId.toInt()) { + groupId = x; + logFatal() << i << model->groupingPeriod(x) << model->data(model->index(i, 0), + WalletHistoryModel::MinedDate).toDate().toString(); + } + } +#endif - groupId = model->data(model->index(24 * 6 * (today.dayOfWeek() - 1), 0), WalletHistoryModel::GroupId); + for (const auto &dayInWeek : thisWeek) { + QVERIFY(dayInWeek.isValid()); + int days = dayInWeek.daysTo(date); + groupId = model->data(model->index(24 * 6 * days, 0), WalletHistoryModel::GroupId); QVERIFY(groupId.isValid()); QCOMPARE(model->groupingPeriod(groupId.toInt()), "Earlier this week"); } - if (today.day() > 2) { - // that means we should have a 'EarlierThisMonth' group. - groupId = model->data(model->index(24 * 6 * (today.day() - 1), 0), WalletHistoryModel::GroupId); + for (const auto &someDay : thisMonth) { + QVERIFY(someDay.isValid()); + int days = someDay.daysTo(date); + groupId = model->data(model->index(24 * 6 * days, 0), WalletHistoryModel::GroupId); QVERIFY(groupId.isValid()); QCOMPARE(model->groupingPeriod(groupId.toInt()), "Earlier this month"); } - } +void TestWalletHistoryModel::basic_data() +{ + QTest::addColumn("date"); + QTest::addColumn >("thisWeek"); // any dates where we expect 'earlier this week' labels. + QTest::addColumn >("thisMonth"); // any dates where we expect 'earlier this month' labels. + QTest::newRow("basic") << QDate(2023, 1, 1) + << QList() // no 'earlier this week' + << QList(); // no 'earlier this month' + + QTest::newRow("end") << QDate(2023, 2, 28) + << QList() // no 'earlier this week' + << QList {QDate(2023, 2, 1), QDate(2023, 2, 26)}; + + QTest::newRow("edge") << QDate(2023, 3, 1) + << QList { QDate(2023, 2 , 27) } + << QList(); // no 'earlier this month' + + QTest::newRow("edge2") << QDate(2023, 3, 2) + << QList { QDate(2023, 2 , 27), QDate(2023, 2 , 28) } + << QList(); // no 'earlier this month' + + QTest::newRow("startWeekNotMonth") << QDate(2023, 5, 7) + << QList { QDate(2023, 5 , 1), QDate(2023, 5 , 2), QDate(2023, 5 , 3), QDate(2023, 5 , 4) , QDate(2023, 5 , 5) } + << QList(); // no 'earlier this month' +} QTEST_MAIN(TestWalletHistoryModel) diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.h b/testing/walletHistoryModel/TestWalletHistoryModel.h index 1660421..8c91d65 100644 --- a/testing/walletHistoryModel/TestWalletHistoryModel.h +++ b/testing/walletHistoryModel/TestWalletHistoryModel.h @@ -30,6 +30,7 @@ public: private slots: void cleanup(); // called after each testcase. void basic(); + void basic_data(); private: template -- 2.54.0 From 86fc45c98b5669659ace6ac2118b2ad2c51c58a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 8 May 2023 13:37:11 +0200 Subject: [PATCH 0485/1428] UX fixes Make the importing page not requires a name for the wallet. Also fix the width of the month combobox. --- guis/mobile/ImportWalletPage.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 25c1c12..5f69fe8 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -27,7 +27,7 @@ Page { headerButtonVisible: true headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 && finished + headerButtonEnabled: finished property var typedData: Pay.identifyString(secrets.text) property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; @@ -123,6 +123,8 @@ Page { text: qsTr("Oldest Transaction") + ":" } Flow { + Layout.fillWidth: true + Layout.columnSpan: 2 spacing: 10 QQC2.ComboBox { id: month -- 2.54.0 From fe97d923c008460535c3df905666437539f5be0a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 9 May 2023 18:23:40 +0200 Subject: [PATCH 0486/1428] Until we fully implement DSPs, don't spent much time on them. --- guis/mobile/defaults.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/defaults.ini b/guis/mobile/defaults.ini index bffbf29..5bf4f69 100644 --- a/guis/mobile/defaults.ini +++ b/guis/mobile/defaults.ini @@ -21,5 +21,5 @@ countryCodes=en_US, en_GB, nl_NL, zh_CN [payment] # Double Spend Proof timeout in milliseconds. # After the timeout is reached we deem the tx safe. -#dsp-timeout=3000 +dsp-timeout=300 -- 2.54.0 From 001335e895b1cd04f51161289b1aa952b9f7dbb6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 9 May 2023 18:49:41 +0200 Subject: [PATCH 0487/1428] Fix width when used in a layout manager --- guis/mobile/VisualSeparator.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/VisualSeparator.qml b/guis/mobile/VisualSeparator.qml index 6e323a1..bb19658 100644 --- a/guis/mobile/VisualSeparator.qml +++ b/guis/mobile/VisualSeparator.qml @@ -20,6 +20,7 @@ import QtQuick Item { height: 21 width: parent.width + implicitWidth: parent.width // for when we are used in a layout Rectangle { width: parent.width * 0.8 height: 1.3 -- 2.54.0 From d359719d4a70a28288338a4fe8efd1f6cbbe1935 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 9 May 2023 19:25:06 +0200 Subject: [PATCH 0488/1428] Work out a design for boring-list-pages --- guis/mobile.qrc | 1 + guis/mobile/PageTitledBox.qml | 52 ++++++ guis/mobile/TransactionDetails.qml | 277 ++++++++++++++--------------- 3 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 guis/mobile/PageTitledBox.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 2574ae2..835931f 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -68,5 +68,6 @@ mobile/PriceInputWidget.qml mobile/NumericKeyboardWidget.qml mobile/AccountSelectorWidget.qml + mobile/PageTitledBox.qml diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml new file mode 100644 index 0000000..ed5cba9 --- /dev/null +++ b/guis/mobile/PageTitledBox.qml @@ -0,0 +1,52 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee + +Item { + id: pageTitledBox + + property alias title: boxTitle.text + default property alias content: contentColumn.children + + implicitHeight: (boxTitle.text === "" ? 0 : boxTitle.height + 6) + 4 + contentColumn.implicitHeight + 6 + Layout.fillWidth: true + height: implicitHeight + + Rectangle { + anchors.fill: parent + anchors.leftMargin: -10 + anchors.rightMargin: -10 + color: palette.alternateBase + } + Flowee.Label { + id: boxTitle + font.weight: 600 + y: 4 + } + + Column { + id: contentColumn + width: parent.width + anchors.top: boxTitle.text === "" ? parent.top : boxTitle.bottom + anchors.topMargin: 6 + spacing: 6 + + } +} diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 7986e69..c9dc3af 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -33,35 +33,39 @@ Page { ColumnLayout { id: content width: parent.width - Flowee.Label { - text: qsTr("Transaction Hash") + ":" - } - Item { - implicitHeight: txidLabel.implicitHeight - Layout.fillWidth: true - Flowee.Label { - id: txidLabel - text: root.transaction == null ? "" : root.transaction.txid - anchors.left: parent.left - anchors.right: copyIcon.left - anchors.rightMargin: 10 - wrapMode: Text.WrapAnywhere - } - Image { - id: copyIcon - anchors.right: parent.right - width: 20 - height: 20 - source: "qrc:/edit-copy" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - MouseArea { - anchors.fill: parent - anchors.margins: -15 - onClicked: Pay.copyToClipboard(txidLabel.text) + spacing: 10 + + PageTitledBox { + title: qsTr("Transaction Hash") + + Item { + implicitHeight: txidLabel.implicitHeight + width: parent.width + Flowee.Label { + id: txidLabel + text: root.transaction == null ? "" : root.transaction.txid + anchors.left: parent.left + anchors.right: copyIcon.left + anchors.rightMargin: 10 + wrapMode: Text.WrapAnywhere + } + Image { + id: copyIcon + anchors.right: parent.right + width: 20 + height: 20 + source: "qrc:/edit-copy" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: Pay.copyToClipboard(txidLabel.text) + } } } } - Flowee.Label { - text: { + + PageTitledBox { + title: { if (root.transaction == null) return "" var h = root.transaction.height; @@ -69,136 +73,130 @@ Page { return qsTr("Rejected") if (h === -1) return qsTr("Unconfirmed") - return qsTr("Mined at") + ": " + return qsTr("Mined at") } - } - Flowee.Label { - visible: text !== "" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - Layout.fillWidth: true - text: { - var tx = root.transaction; - if (tx == null) - return ""; - if (root.transaction.height < 1) - return ""; - - let txHeight = tx.height; - var answer = txHeight + "\n" + Pay.formatDateTime(tx.date) - let blockAge = Pay.chainHeight - txHeight; - answer += "\n"; - answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); - return answer; - } - } - Flowee.Label { - text: qsTr("Transaction comment") + ":" - } - Item { - Layout.fillWidth: true - implicitHeight: commentEdit.implicitHeight - Flowee.Label { - id: commentLabel - text: root.transaction == null ? "" : root.transaction.comment + visible: text !== "" wrapMode: Text.WrapAtWordBoundaryOrAnywhere - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - anchors.baseline: commentEdit.baseline - visible: !commentEdit.visible - } - Image { - id: editIcon - anchors.right: parent.right - width: 20 - height: 20 - smooth: true - source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - // Editing is a risky business until QTBUG-109306 has been deployed - // visible: root.infoObject != null && infoObject.commentEditable - visible: false - MouseArea { - anchors.fill: parent - anchors.margins: -15 - onClicked: { - commentEdit.visible = true - commentEdit.focus = true - commentEdit.forceActiveFocus(); - } + Layout.fillWidth: true + text: { + var tx = root.transaction; + if (tx == null) + return ""; + if (root.transaction.height < 1) + return ""; + + let txHeight = tx.height; + var answer = txHeight + "\n" + Pay.formatDateTime(tx.date) + let blockAge = Pay.chainHeight - txHeight; + answer += "\n"; + answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); + return answer; } } - Flowee.TextField { - id: commentEdit - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - visible: false - text: root.transaction == null ? "" : root.transaction.comment - onTextChanged: if (root.infoObject != null) infoObject.userComment = text - } - } - Flowee.Label { - text: qsTr("Size") + ":" - } - Flowee.Label { - text: root.infoObject == null ? "" : - qsTr("%1 bytes").arg(infoObject.size) } - Flowee.Label { - text: qsTr("Coinbase") - visible: root.transaction != null && root.transaction.isCoinbase + PageTitledBox { + title: qsTr("Transaction comment") + + Item { + width: parent.width + implicitHeight: commentEdit.implicitHeight + height: commentEdit.height + + Flowee.Label { + id: commentLabel + text: root.transaction == null ? "" : root.transaction.comment + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + anchors.baseline: commentEdit.baseline + visible: !commentEdit.visible + } + Image { + id: editIcon + anchors.right: parent.right + width: 20 + height: 20 + smooth: true + source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + // Editing is a risky business until QTBUG-109306 has been deployed + // visible: root.infoObject != null && infoObject.commentEditable + visible: false + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + commentEdit.visible = true + commentEdit.focus = true + commentEdit.forceActiveFocus(); + } + } + } + Flowee.TextField { + id: commentEdit + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + visible: false + text: root.transaction == null ? "" : root.transaction.comment + onTextChanged: if (root.infoObject != null) infoObject.userComment = text + } + } } - Flowee.Label { - text: qsTr("Is a CashFusion transaction.") - visible: root.transaction != null && root.transaction.isCashFusion + + PageTitledBox { + Flowee.Label { + text: root.infoObject == null ? "" : qsTr("Size: %1 bytes").arg(infoObject.size) + } + Flowee.Label { + text: qsTr("Coinbase") + visible: root.transaction != null && root.transaction.isCoinbase + } + Flowee.Label { + text: qsTr("Is a CashFusion transaction.") + visible: root.transaction != null && root.transaction.isCashFusion + } } + // We can't calculate the fees of just any transaction, only for the ones // this account created. - Flowee.Label { - id: feesPaidLabel + PageTitledBox { + title: qsTr("Fees paid") visible: root.infoObject != null && infoObject.createdByUs - text: qsTr("Fees paid") + ":" - } - Flowee.Label { - visible: feesPaidLabel.visible - text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") - .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) - } - Flowee.BitcoinAmountLabel { - visible: feesPaidLabel.visible - value: root.infoObject == null ? 0 : infoObject.fees - colorize: false + Flowee.Label { + text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") + .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) + } + Flowee.BitcoinAmountLabel { + value: root.infoObject == null ? 0 : infoObject.fees + colorize: false + } } - Flowee.Label { - id: sendersLabel - text: { + PageTitledBox { + title: { if (root.infoObject == null) return ""; if (infoObject.isCashFusion) - return qsTr("Fused from my addresses") + ":" + return qsTr("Fused from my addresses"); if (infoObject.createdByUs) - return qsTr("Sent from my addresses") + ":"; + return qsTr("Sent from my addresses"); if (infoObject.isCashFusion) - return qsTr("Sent from addresses") + ":"; + return qsTr("Sent from addresses"); return ""; } - visible: text !== "" - } - Column { - // we nest a column in a column to be able to skip all the rows that are not ours. - // only really relevant with loads of inputs and outputs. - width: content.width + visible: title !== "" + Repeater { /* * We only have the one transaction, which means we only have the previous txid and we have * no details about which address or anything else these funds come from if we didn't * create the Tx. So just don't show anything. */ - model: sendersLabel.visible ? infoObject.inputs : 0 + model: parent.visible ? infoObject.inputs : 0 delegate: Item { Layout.alignment: Qt.AlignRight width: content.width @@ -230,11 +228,10 @@ Page { return cloaked; return modelData.address; } - x: 10 width: parent.width clipboardText: modelData === null ? "" : modelData.address visible: modelData !== null - font.pixelSize: root.font.pixelSize * 0.8 + font.pixelSize: root.font.pixelSize * 0.9 } Flowee.BitcoinAmountLabel { id: amount @@ -244,26 +241,22 @@ Page { anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 10 + font.pixelSize: root.font.pixelSize * 0.9 } } } } - Flowee.Label { - text: { + PageTitledBox { + title: { if (root.infoObject == null) return ""; if (infoObject.isCashFusion) - return qsTr("Fused into my addresses") + ":" + return qsTr("Fused into my addresses"); if (infoObject.createdByUs) - return qsTr("Received at addresses") + ":" - return qsTr("Received at my addresses") + ":" + return qsTr("Received at addresses"); + return qsTr("Received at my addresses"); } - } - Column { - // we nest a column in a column to be able to skip all the rows that are not ours. - // only really relevant with loads of inputs and outputs. - width: content.width Repeater { model: root.infoObject == null ? 0 : infoObject.outputs delegate: Item { @@ -288,7 +281,6 @@ Page { } Flowee.LabelWithClipboard { id: outAddress - x: 10 visible: modelData !== null elide: Text.ElideMiddle menuText: qsTr("Copy Address") @@ -302,7 +294,7 @@ Page { } clipboardText: modelData === null ? "" : modelData.address width: parent.width - font.pixelSize: root.font.pixelSize * 0.8 + font.pixelSize: root.font.pixelSize * 0.9 } Flowee.BitcoinAmountLabel { id: outAmount @@ -313,6 +305,7 @@ Page { anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 10 + font.pixelSize: root.font.pixelSize * 0.9 } } } -- 2.54.0 From 2608b2f82b30f2d2fdefbd6422cb3dd94750c42d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 9 May 2023 21:20:08 +0200 Subject: [PATCH 0489/1428] Loads of UI love Follow the introduction of the PagTitlexBox and make the layout in most screens consistent. Also implement the task to show a QR for the seed phrase. --- guis/Flowee/QRWidget.qml | 6 +- guis/Flowee/WalletSecretsView.qml | 48 ++++-- guis/mobile/AccountPageListItem.qml | 227 ++++++++++++++++++---------- guis/mobile/AccountsList.qml | 1 + guis/mobile/GuiSettings.qml | 161 +++++++++----------- guis/mobile/PageTitledBox.qml | 11 +- 6 files changed, 272 insertions(+), 182 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 20f5be7..3044bf9 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -21,10 +21,12 @@ import Flowee.org.pay Item { id: root property alias qrSize: qrImage.width - implicitWidth: qrImage.width - implicitHeight: qrImage.width + addressLine.height + property alias textVisible: addressLine.visible property string qrText: "" + implicitWidth: qrImage.width + implicitHeight: qrImage.width + addressLine.visible ? addressLine.height : 0 + function handleOnClicked() { Pay.copyToClipboard(qrText); // invert the feedback so a second tap removes the feedback again. diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 9b86181..557a695 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -22,27 +22,43 @@ ListView { id: root required property QtObject account; - clip: true model: root.account.secrets property bool showHdIndex: true - header: Column { + header: Item { width: root.width - Label { - width: parent.width - font.italic: true - text: qsTr("Explanation") + ":"; + implicitHeight: contentColumn.implicitHeight + 16 + Rectangle { + color: palette.alternateBase + anchors { + fill: parent + leftMargin: -10 + rightMargin: -10 + } } - Label { - width: parent.width - font.italic: true - text: qsTr("Coins a / b\n a) active coin-count.\n b) historical coin-count."); + + Column { + id: contentColumn + width: root.width + y: 4 + spacing: 6 + Label { + width: parent.width + font.italic: true + text: qsTr("Explanation") + font.weight: 600 + } + Label { + width: parent.width + font.italic: true + text: qsTr("Coins a / b\n a) active coin-count.\n b) historical coin-count."); + } } } - delegate: Rectangle { + delegate: Item { + id: delegateRoot - color: (index % 2) == 0 ? palette.base : palette.alternateBase width: ListView.view.width height: addressLabel.height + 6 + amountLabel.height + 6 + (lineCount === 3 ? coinCountLabel.height + 6: 0) + 12 @@ -55,6 +71,14 @@ ListView { return 2; } + Rectangle { + color: (index % 2) == 0 ? palette.base : palette.alternateBase + anchors { + fill: parent + leftMargin: -10 + rightMargin: -10 + } + } Label { text: hdIndex anchors.baseline: addressLabel.baseline diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index a2082c0..e672aba 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -30,44 +30,47 @@ QQC2.Control { ColumnLayout { id: column width: parent.width + spacing: 10 Flowee.AccountTypeLabel { Layout.fillWidth: true account: root.account } - Flowee.Label { - text: qsTr("Sync Status") + ":" - } - Flowee.Label { - text: { - var height = root.account.lastBlockSynched - if (height < 1) - return "" - var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); - if (time !== "") - time = " (" + time + ")"; - return height + " / " + Pay.chainHeight + time; + PageTitledBox { + title: qsTr("Sync Status") + + Flowee.Label { + text: { + var height = root.account.lastBlockSynched + if (height < 1) + return "" + var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); + if (time !== "") + time = " (" + time + ")"; + return height + " / " + Pay.chainHeight + time; + } } } - Flowee.TextField { - text: account.name - onTextChanged: root.account.name = text - Layout.fillWidth: true + PageTitledBox { + title: qsTr("Wallet Name") + + Flowee.TextField { + text: account.name + onTextChanged: root.account.name = text + width: parent.width + } } - TextButton { - visible: !portfolio.singleAccountSetup - Layout.fillWidth: true - text: qsTr("Primary Wallet") - onClicked: if (!root.account.isArchived) root.account.isPrimaryAccount = !root.account.isPrimaryAccount + PageTitledBox { + title: qsTr("Options") Flowee.CheckBox { + visible: !portfolio.singleAccountSetup enabled: !root.account.isArchived - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter checked: root.account.isPrimaryAccount onClicked: root.account.isPrimaryAccount = checked + text: qsTr("Primary Wallet") } } @@ -99,14 +102,6 @@ QQC2.Control { TODO archived wallets functionality */ - /* // TODO - TextButton { - text: qsTr("Wallet Settings") - showPageIcon: true - Layout.fillWidth: true - onClicked: {} - }*/ - TextButton { text: qsTr("Backup information") showPageIcon: true @@ -116,43 +111,100 @@ QQC2.Control { Component { id: hdBackupDetails Page { + id: detailsPage headerText: qsTr("Backup Details") + + Item { + // non-layoutable items. + + QQC2.Popup { + id: seedPopup + width: 260 + height: 260 + x: 55 + y: 100 + modal: true + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + Flowee.QRWidget { + id: seedQr + qrSize: 250 + textVisible: false + } + } + } + ColumnLayout { width: parent.width - Flowee.Label { text: "xpub" + ":" } - Flowee.LabelWithClipboard { - id: xpub - Layout.fillWidth: true - text: root.account.xpub - } - VisualSeparator { } + spacing: 10 - Flowee.Label { - text: qsTr("Wallet seed-phrase") + ":" - } - // TODO allow showing the seed phrase as a QR - Flowee.LabelWithClipboard { - Layout.fillWidth: true - text: root.account.mnemonic - wrapMode: Text.Wrap - } - VisualSeparator { } - Flowee.Label { text: qsTr("Derivation Path") + ":" } - Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } - VisualSeparator { } - Flowee.Label { text: qsTr("Starting Height", "height refers to block-height") + ":" } - Flowee.LabelWithClipboard { text: root.account.initialBlockHeight } + PageTitledBox { + title: qsTr("Wallet seed-phrase") + + Item { + implicitHeight: mnemonicLabel.implicitHeight + width: parent.width + Flowee.LabelWithClipboard { + id: mnemonicLabel + text: root.account.mnemonic + width: parent.width - 36 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.wordSpacing: 1 + } + Image { + width: 20 + height: 20 + anchors.right: parent.right + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + MouseArea { + anchors.fill: parent + onClicked: { + seedQr.qrText = root.account.mnemonic + seedPopup.open(); + } + } + } + } + } + + PageTitledBox { + title: qsTr("Starting Height", "height refers to block-height") + Flowee.LabelWithClipboard { text: root.account.initialBlockHeight } + } + + PageTitledBox { + title: qsTr("Derivation Path") + Flowee.LabelWithClipboard { + text: root.account.hdDerivationPath + } + } + + PageTitledBox { + title: qsTr("xpub") + + Flowee.LabelWithClipboard { + text: root.account.xpub + width: parent.width + wrapMode: Text.WrapAnywhere + } + } - VisualSeparator { } Flowee.Label { Layout.fillWidth: true text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile.") textFormat: Text.StyledText + font.italic: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Flowee.Label { Layout.fillWidth: true text: qsTr("Important: Never share your seed-phrase with others!") + font.italic: true textFormat: Text.StyledText wrapMode: Text.WrapAtWordBoundaryOrAnywhere } @@ -178,34 +230,51 @@ QQC2.Control { return []; } - Flowee.CheckBox { - id: changeAddresses - text: qsTr("Change Addresses") - anchors.top: parent.top - visible: root.account.isHDWallet - onClicked: root.account.secrets.showChangeChain = checked - tooltipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") - } - Flowee.CheckBox { - id: usedAddresses - anchors.top: changeAddresses.bottom - anchors.topMargin: 16 - width: parent.width - text: qsTr("Used Addresses"); - visible: !root.account.isSingleAddressAccount - onClicked: root.account.secrets.showUsedAddresses = checked - tooltipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") + PageTitledBox { + id: optionsBox + Flowee.CheckBox { + text: qsTr("Change Addresses") + visible: root.account.isHDWallet + onClicked: root.account.secrets.showChangeChain = checked + tooltipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") + } + Flowee.CheckBox { + text: qsTr("Used Addresses"); + visible: !root.account.isSingleAddressAccount + onClicked: root.account.secrets.showUsedAddresses = checked + tooltipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") + } } - Flowee.WalletSecretsView { - id: listView - anchors.top: usedAddresses.visible ? usedAddresses.bottom : parent.top - anchors.topMargin: 10 - anchors.bottom: parent.bottom - width: parent.width - account: root.account + Item { + // this is a horrible hack... + // First, ListViews almost always require clipping on, + // otherwise list-items can overlap the rest of your view. + // But if I enable clipping I no longer get the nice + // width-filling backgrounds... + // Sooo. I need a clipping item that is full width (negative + // left and right margin) + id: clipItem + anchors { + top: optionsBox.bottom + topMargin: 10 + bottom: parent.bottom + left: parent.left + right: parent.right + leftMargin: -10 + rightMargin: -10 + } clip: true - showHdIndex: false + Flowee.WalletSecretsView { + id: listView + anchors { + fill: parent + leftMargin: 10 + rightMargin: 10 + } + account: root.account + showHdIndex: false + } } } } diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index e0e107f..f208427 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -118,6 +118,7 @@ Page { AccountPageListItem { width: accountPageListView.width account: modelData + clip: true } } } diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 61ed270..7f10cf0 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -26,59 +26,49 @@ Page { ColumnLayout { width: parent.width - spacing: 6 - Flowee.Label { - text: qsTr("Font sizing") - } + spacing: 10 - Row { - width: parent.width - id: fontSizing - property double buttonWidth: width / 6 - Repeater { - model: 6 - delegate: Item { - width: fontSizing.buttonWidth - height: 30 - property int target: index * 25 + 75 + PageTitledBox { + title: qsTr("Font sizing") + Row { + width: parent.width + id: fontSizing + property double buttonWidth: width / 6 + Repeater { + model: 6 + delegate: Item { + width: fontSizing.buttonWidth + height: 30 + property int target: index * 25 + 75 - Rectangle { - width: parent.width - 5 - x: 2.5 - height: 3 - color: Pay.fontScaling === target ? palette.highlight : palette.button - } + Rectangle { + width: parent.width - 5 + x: 2.5 + height: 3 + color: Pay.fontScaling === target ? palette.highlight : palette.button + } - Flowee.Label { - font.pixelSize: 15 - text: "" + target - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - } - MouseArea { - anchors.fill: parent - anchors.topMargin: -30 - onClicked: Pay.fontScaling = target + Flowee.Label { + font.pixelSize: 15 + text: "" + target + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + MouseArea { + anchors.fill: parent + anchors.topMargin: -30 + onClicked: Pay.fontScaling = target + } } } } } - Item { width: 1; height: 10; } // spacer + PageTitledBox { + title: qsTr("Unit") - Item { - width: parent.width - implicitHeight: Math.max(unitLabel.height, unitSelector.height) - Flowee.Label { - id: unitLabel - text: qsTr("Unit") + ":" - anchors.baseline: unitSelector.baseline - } Flowee.ComboBox { id: unitSelector - anchors.left: unitLabel.right - anchors.leftMargin: 6 - width: 160 model: { var answer = []; for (let i = 0; i < 5; ++i) { @@ -89,47 +79,47 @@ Page { currentIndex: Pay.unit onCurrentIndexChanged: Pay.unit = currentIndex } - } - Rectangle { - Layout.alignment: Qt.AlignHCenter - color: "#00000000" - radius: 6 - border.color: palette.button - border.width: 0.8 + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + color: "#00000000" + radius: 6 + border.color: palette.button + border.width: 0.8 - implicitHeight: units.height + 10 - implicitWidth: units.width + 10 + implicitHeight: units.height + 10 + implicitWidth: units.width + 10 - GridLayout { - id: units - columns: 3 - x: 5; y: 5 - rowSpacing: 0 - Flowee.Label { - text: { - var answer = "1"; - for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { - answer += "0"; + GridLayout { + id: units + columns: 3 + x: 5; y: 5 + rowSpacing: 0 + Flowee.Label { + text: { + var answer = "1"; + for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { + answer += "0"; + } + return answer + " " + Pay.unitName; } - return answer + " " + Pay.unitName; + Layout.alignment: Qt.AlignRight } - Layout.alignment: Qt.AlignRight - } - Flowee.Label { text: "=" } - Flowee.Label { text: "1 Bitcoin Cash" } + Flowee.Label { text: "=" } + Flowee.Label { text: "1 Bitcoin Cash" } - Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} - Flowee.Label { text: "="; visible: Pay.isMainChain} - Flowee.Label { - text: { - var amount = 1; - for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { - amount = amount * 10; + Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} + Flowee.Label { text: "="; visible: Pay.isMainChain} + Flowee.Label { + text: { + var amount = 1; + for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { + amount = amount * 10; + } + return Fiat.formattedPrice(amount, Fiat.price); } - return Fiat.formattedPrice(amount, Fiat.price); + visible: Pay.isMainChain } - visible: Pay.isMainChain } } } @@ -140,21 +130,16 @@ Page { onClicked: thePile.push("./CurrencySelector.qml") } - Flowee.Label { - text: qsTr("Main View") - font.bold: true + PageTitledBox { + title: qsTr("Main View") visible: Pay.isMainChain // because we only have one option right now - } - Item { - width: 10 - height: 10 - } - Flowee.CheckBox { - text: qsTr("Show Bitcoin Cash value") - checked: Pay.activityShowsBch - onCheckedChanged: Pay.activityShowsBch = checked - visible: Pay.isMainChain // only mainchain has fiat value + Flowee.CheckBox { + text: qsTr("Show Bitcoin Cash value") + checked: Pay.activityShowsBch + onCheckedChanged: Pay.activityShowsBch = checked + visible: Pay.isMainChain // only mainchain has fiat value + } } } } diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index ed5cba9..40a4c74 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -25,9 +25,18 @@ Item { property alias title: boxTitle.text default property alias content: contentColumn.children - implicitHeight: (boxTitle.text === "" ? 0 : boxTitle.height + 6) + 4 + contentColumn.implicitHeight + 6 + implicitHeight: { + var h = 6; + if (boxTitle.text !== "") + h += 4 + boxTitle.height; // 4 is the spacing above title + let contentHeight = contentColumn.implicitHeight; + if (contentHeight > 0) + h += contentHeight + 6; + return h; + } Layout.fillWidth: true height: implicitHeight + visible: implicitHeight > 0 Rectangle { anchors.fill: parent -- 2.54.0 From abdaaf59c98994f9e90155c6295b9dd7d34f233a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 11 May 2023 22:10:48 +0200 Subject: [PATCH 0490/1428] Add braces to make JS engine happy --- guis/Flowee/QRWidget.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 3044bf9..e2306f8 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -25,7 +25,7 @@ Item { property string qrText: "" implicitWidth: qrImage.width - implicitHeight: qrImage.width + addressLine.visible ? addressLine.height : 0 + implicitHeight: qrImage.width + (addressLine.visible ? addressLine.height : 0) function handleOnClicked() { Pay.copyToClipboard(qrText); -- 2.54.0 From a209bc64d2a6fa8933641de9dcfe9aff6d227e94 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 11 May 2023 22:19:48 +0200 Subject: [PATCH 0491/1428] Make the saving of the wallet-list instantly on encrypt Call the saving of the wallet-list directly after we encrypted a wallet, avoiding any delay in saving. Fixes: #17 --- src/FloweePay.cpp | 22 +++++++++++++++++----- src/FloweePay.h | 1 + src/Wallet.h | 1 + src/Wallet_encryption.cpp | 5 ++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 6cd62e6..d348bc7 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -300,12 +300,8 @@ void FloweePay::init() dl->addHeaderListener(w); dl->connectionManager().addPrivacySegment(w->segment()); m_wallets.append(w); + connectToWallet(w); logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId(); - connect (w, &Wallet::encryptionChanged, w, [=]() { - // make sure that we get peers for the wallet directly after it gets decrypted - if (!m_offline && w->isDecrypted()) - FloweePay::p2pNet()->addAction(); - }); lastOpened = w; } catch (const std::runtime_error &e) { logWarning() << "Wallet load failed:" << e; @@ -606,6 +602,7 @@ Wallet *FloweePay::createWallet(const QString &name) dl->connectionManager().addPrivacySegment(w->segment()); w->moveToThread(thread()); m_wallets.append(w); + connectToWallet(w); emit startSaveDate_priv(); // schedule a save of the m_wallets list return w; @@ -759,6 +756,20 @@ uint32_t FloweePay::walletStartHeightHint() const return time(nullptr); } +void FloweePay::connectToWallet(Wallet *wallet) +{ + connect (wallet, &Wallet::encryptionChanged, wallet, [=]() { + // make sure that we get peers for the wallet directly after it gets decrypted + if (!m_offline && wallet->isDecrypted()) + FloweePay::p2pNet()->addAction(); + }); + connect (wallet, &Wallet::encryptionSeedChanged, wallet, [=]() { + // the encryption seed is saved in the wallet-list. + // Save as soon as the data changed. + saveData(); + }, Qt::QueuedConnection); +} + bool FloweePay::activityShowsBch() const { return m_activityShowsBch; @@ -1050,6 +1061,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const emit walletsChanged(); m_wallets.append(wallet); emit walletsChanged(); + connectToWallet(wallet); return new NewWalletConfig(wallet); } } diff --git a/src/FloweePay.h b/src/FloweePay.h index 08cbb22..3e2cf41 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -299,6 +299,7 @@ private: // create wallet and add to list. Please consider calling walletsChanged() after Wallet *createWallet(const QString &name); uint32_t walletStartHeightHint() const; + void connectToWallet(Wallet *wallet); mutable Mnemonic m_hdSeedValidator; diff --git a/src/Wallet.h b/src/Wallet.h index 351e60b..f2251f4 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -381,6 +381,7 @@ signals: void transactionConfirmed(int txIndex); void transactionRemoved(int txIndex); void encryptionChanged(); + void encryptionSeedChanged(); // \internal void startDelayedSave(); diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index c05dfd6..3025971 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -34,7 +34,10 @@ uint32_t Wallet::encryptionSeed() const void Wallet::setEncryptionSeed(uint32_t seed) { assert(!m_haveEncryptionKey); // Wrong order of calls. + if (m_encryptionSeed == seed) + return; m_encryptionSeed = seed; + emit encryptionSeedChanged(); } bool Wallet::parsePassword(const QString &password) @@ -77,7 +80,7 @@ bool Wallet::parsePassword(const QString &password) return false; // bad password } // password is correct, lets update internal wallet state - m_encryptionSeed = encryptionSeed; + setEncryptionSeed(encryptionSeed); m_encryptionChecksum = *crc; m_encryptionKey.resize(AES256_KEYSIZE); m_encryptionIR.resize(AES_BLOCKSIZE); -- 2.54.0 From 3dd39e2f64b8255d5b9404af9a4750cc19e98745 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 12:46:02 +0200 Subject: [PATCH 0492/1428] Add API docs. --- src/Wallet.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Wallet.h b/src/Wallet.h index f2251f4..bea025d 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -359,6 +359,13 @@ public: /// Check the loaded wallet version Id and make internal changes to upgrade it to current. void performUpgrades(); + /** + * Return the importing state. + * A wallet that starts far behind the chain-tip is seen as importing until + * it has received all block data until the tip. + * This boolean is true at most once in the lifetime of a wallet. + * This boolean is persisted to disk. + */ bool walletIsImporting() const; public slots: @@ -569,13 +576,14 @@ private: friend class WalletHistoryModel; friend class WalletSecretsModel; friend class WalletCoinsModel; - friend class WalletKeyView;; + friend class WalletKeyView; // auto-detected, causes us to send bigger gaps for bloom filters. bool m_walletStoresCashFusions = false; bool m_walletIsImporting = false; // only true for the initial wallet import. - // user settings + // Makes sure that we use only 1 address if true. bool m_singleAddressWallet = false; + // All wallets created by the usre are user-owned. bool m_userOwnedWallet = true; // operational -- 2.54.0 From 4ba37bed7c69eb2aacef8e332ba571095ce77e6e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 12:59:54 +0200 Subject: [PATCH 0493/1428] API docs. --- src/FloweePay.cpp | 6 +++--- src/FloweePay.h | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d348bc7..bf5c7af 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -618,10 +618,10 @@ void FloweePay::copyToClipboard(const QString &text) QGuiApplication::clipboard()->setText(text); } -void FloweePay::openInExplorer(const QString &text) +void FloweePay::openInExplorer(const QString &txid) { - if (text.size() == 64) { // assume this is a txid - QDesktopServices::openUrl(QUrl("https://blockchair.com/bitcoin-cash/transaction/" + text)); + if (txid.size() == 64) { // assume this is a txid + QDesktopServices::openUrl(QUrl("https://blockchair.com/bitcoin-cash/transaction/" + txid)); return; } // Add maybe other types? diff --git a/src/FloweePay.h b/src/FloweePay.h index 3e2cf41..0fa7cec 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -90,8 +90,17 @@ public: QList wallets() const; + /** + * Return the p2pNet owned by this application. + * Notice that we lazy-initialize this at first call, which is quite expensive. + * The startP2PInit() method should be called once, to ensure the p2pNet initializes + * in a worker-thread. + */ DownloadManager* p2pNet(); + /** + * Return the fiat-prices data provider owned by this application. + */ PriceDataProvider *prices() const; /// return the amount of milli-seconds we wait for a double-spent-proof @@ -104,7 +113,12 @@ public: /// returns platform name, Linux / Android / etc QString platform() const; - /// Load p2p layer. + /** + * Load p2p layer in a worker-thread. + * The signal loadComplete() will be triggered when that is done, after which it is safe to call + * the p2pNet() method. + * \sa loadComplete(); + */ void startP2PInit(); /// for a price, in satoshis, return a formatted string in unitName(). @@ -240,7 +254,7 @@ public: void setHideBalance(bool hideBalance); Q_INVOKABLE void copyToClipboard(const QString &text); - Q_INVOKABLE void openInExplorer(const QString &text); + Q_INVOKABLE void openInExplorer(const QString &txid); QString version() const; QString libsVersion() const; @@ -250,7 +264,12 @@ public: /// register the user preference for being offline. void setOffline(bool offline); - /// start the p2p networking, unless isOffline() + /** + * run the p2p networking agents, unless isOffline() is true. + * After the startP2PInit has successfully completed, call this + * to start the actions running on the network layer which will + * sync the databases from the p2p net. + */ void startNet(); /// If true, no notifications about new blocks will be shown -- 2.54.0 From 97a160f603cc7cca881a3581da0ea6f853d21f3f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 13:34:26 +0200 Subject: [PATCH 0494/1428] cleanup header file. remove class declaration that is no longer needed. --- src/FloweePay.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 0fa7cec..df4c9ed 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -33,9 +33,6 @@ #include -namespace Streaming { - class BufferPool; -} class Wallet; class NewWalletConfig; class KeyId; -- 2.54.0 From 5f05636dd382cc81a9eab82a41bbedb513f14998 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 14:50:17 +0200 Subject: [PATCH 0495/1428] Start a WalletConfig class This is meant to have a collection of user settings for each individual wallet. Things like if the wallet balance should be shown in the totalBalance. --- src/CMakeLists.txt | 1 + src/FloweePay.cpp | 26 +++++++++- src/FloweePay.h | 9 ++++ src/PortfolioDataProvider.cpp | 8 +++ src/WalletConfig.cpp | 92 +++++++++++++++++++++++++++++++++++ src/WalletConfig.h | 50 +++++++++++++++++++ 6 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/WalletConfig.cpp create mode 100644 src/WalletConfig.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e29d5d..8736aa9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,7 @@ set (PAY_SOURCES TransactionInfo.cpp Wallet.cpp WalletCoinsModel.cpp + WalletConfig.cpp WalletHistoryModel.cpp WalletKeyView.cpp WalletSecretsModel.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index bf5c7af..8554a57 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -20,6 +20,7 @@ #include "NewWalletConfig.h" #include "AddressInfo.h" #include "PriceDataProvider.h" +#include "WalletConfig.h" #include #include @@ -65,7 +66,9 @@ enum FileTags { WalletId, WalletPriority, // int, maps to PrivacySegment::Priority WalletName, // string. Duplicate of the wallet name - WalletEncryptionSeed // uint32 (see wallet.h) + WalletEncryptionSeed, // uint32 (see wallet.h) + WalletSetting_CountBalance, // bool, if we count balance in app-total + WalletSetting_FiatInstaPayLimit, // int, cents }; static P2PNet::Chain s_chain = P2PNet::MainChain; @@ -300,6 +303,7 @@ void FloweePay::init() dl->addHeaderListener(w); dl->connectionManager().addPrivacySegment(w->segment()); m_wallets.append(w); + m_walletConfigs.insert(w->segment()->segmentId(), {}); connectToWallet(w); logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId(); lastOpened = w; @@ -329,6 +333,19 @@ void FloweePay::init() else if (parser.tag() == WalletEncryptionSeed) { walletEncryptionSeed = static_cast(parser.longData()); } + else if (parser.tag() == WalletSetting_CountBalance) { + if (lastOpened) + m_walletConfigs[lastOpened->segment()->segmentId()].countBalance = parser.boolData(); + else + logWarning() << "Setting seen before walletId"; + } + else if (parser.tag() == WalletSetting_FiatInstaPayLimit) { + if (lastOpened) + m_walletConfigs[lastOpened->segment()->segmentId()].maxFiatInstaPay + = parser.intData(); + else + logWarning() << "Setting seen before walletId"; + } } } @@ -372,6 +389,11 @@ void FloweePay::saveData() auto nameData = wallet->name().toUtf8(); builder.addByteArray(WalletName, nameData.constData(), nameData.size()); } + + WalletConfig conf(wallet->segment()->segmentId()); + assert(conf.isValid()); + builder.add(WalletSetting_CountBalance, conf.countBalance()); + builder.add(WalletSetting_FiatInstaPayLimit, conf.maxFiatInstaPay()); } auto buf = builder.buffer(); @@ -602,6 +624,7 @@ Wallet *FloweePay::createWallet(const QString &name) dl->connectionManager().addPrivacySegment(w->segment()); w->moveToThread(thread()); m_wallets.append(w); + m_walletConfigs.insert(id, {}); connectToWallet(w); emit startSaveDate_priv(); // schedule a save of the m_wallets list @@ -1056,6 +1079,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const wallet->setUserOwnedWallet(true); if (!walletName.isEmpty()) wallet->setName(walletName); + assert(m_walletConfigs.contains(wallet->segment()->segmentId())); // little hacky to make listeners realize we really changed the wallet. m_wallets.clear(); emit walletsChanged(); diff --git a/src/FloweePay.h b/src/FloweePay.h index df4c9ed..6de9ba3 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -303,12 +303,18 @@ signals: void newBlockMutedChanged(); void fontScalingChanged(); void activityShowsBchChanged(); + void totalBalanceConfigChanged(); private slots: void loadingCompleted(); void saveData(); private: + struct WalletConfigData { + bool countBalance = true; + int maxFiatInstaPay = 0; // in cents + }; + void init(); void shutdown(); void saveAll(); @@ -328,6 +334,7 @@ private: NotificationManager m_notifications; CameraController* m_cameraController; QList m_wallets; + QHash m_walletConfigs; // key is wallet-segment-id int m_dspTimeout = 5000; int m_windowWidth = 500; int m_windowHeight = 500; @@ -340,6 +347,8 @@ private: bool m_activityShowsBch = false; bool m_offline = false; bool m_gotHeadersSyncedOnce = false; + + friend class WalletConfig; }; #endif diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index fa07443..76819cf 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -19,6 +19,7 @@ #include "AccountInfo.h" #include "FloweePay.h" #include "Wallet.h" +#include "WalletConfig.h" #include @@ -39,6 +40,8 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) selectDefaultWallet(); emit accountsChanged(); }); + + connect (FloweePay::instance(), SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); } QList PortfolioDataProvider::accounts() const @@ -136,6 +139,11 @@ double PortfolioDataProvider::totalBalance() const // skip archived wallet balances. if (!wallet->segment() || wallet->segment()->priority() == PrivacySegment::OnlyManual) continue; + WalletConfig config(wallet->segment()->segmentId()); + assert(config.isValid()); + if (!config.countBalance()) + continue; + rc += wallet->balanceConfirmed(); rc += wallet->balanceImmature(); rc += wallet->balanceUnconfirmed(); diff --git a/src/WalletConfig.cpp b/src/WalletConfig.cpp new file mode 100644 index 0000000..9aea97f --- /dev/null +++ b/src/WalletConfig.cpp @@ -0,0 +1,92 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "WalletConfig.h" +#include "FloweePay.h" + +WalletConfig::WalletConfig(const WalletConfig &other) + : m_walletId(other.m_walletId) +{ +} + +WalletConfig::WalletConfig(uint16_t walletId) + : m_walletId(walletId) +{ +} + +WalletConfig &WalletConfig::operator=(const WalletConfig &other) +{ + m_walletId = other.m_walletId; + return *this; +} + +bool WalletConfig::isValid() const +{ + if (m_walletId == -1) + return false; + + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + if (i == configs.end()) + return false; + return true; +} + +bool WalletConfig::countBalance() const +{ + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + return i->countBalance; +} + +void WalletConfig::setCountBalance(bool newCountBalance) +{ + auto *fp = FloweePay::instance(); + auto configs = fp->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + if (i->countBalance == newCountBalance) + return; + i->countBalance = newCountBalance; + fp->m_walletConfigs = configs; + fp->startSaveDate_priv(); + emit fp->totalBalanceConfigChanged(); +} + +int WalletConfig::maxFiatInstaPay() const +{ + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + return i->maxFiatInstaPay; +} + +void WalletConfig::setMaxFiatInstaPay(int newMaxFiatInstaPay) +{ + auto *fp = FloweePay::instance(); + auto configs = fp->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + if (i->maxFiatInstaPay == newMaxFiatInstaPay) + return; + i->maxFiatInstaPay = newMaxFiatInstaPay; + fp->m_walletConfigs = configs; + fp->startSaveDate_priv(); + emit fp->totalBalanceConfigChanged(); +} diff --git a/src/WalletConfig.h b/src/WalletConfig.h new file mode 100644 index 0000000..7c11a80 --- /dev/null +++ b/src/WalletConfig.h @@ -0,0 +1,50 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef FLOWEE_WALLET_CONFIG_H +#define FLOWEE_WALLET_CONFIG_H + +#include + +/** + * A set of user-level config fields for a wallet. + * This is owned by the FloweePay application singleton. + */ +class WalletConfig +{ +public: + /// creates an invalid wallet config + WalletConfig() = default; + explicit WalletConfig(uint16_t walletId); + WalletConfig(const WalletConfig &other); + + WalletConfig &operator=(const WalletConfig &other); + + bool isValid() const; + + + bool countBalance() const; + void setCountBalance(bool newCountBalance); + + int maxFiatInstaPay() const; + void setMaxFiatInstaPay(int newMaxFiatInstaPay); + +private: + int m_walletId = -1; +}; + +#endif -- 2.54.0 From 89e1b9402797d116e2e7d16f988f35e6b147d816 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 15:49:02 +0200 Subject: [PATCH 0496/1428] Tie the new config settings to the front-end. --- guis/desktop/AccountDetails.qml | 13 ++++++++++++- src/AccountInfo.cpp | 30 +++++++++++++++++++++++++++++- src/AccountInfo.h | 17 ++++++++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 46a4f40..2dc78da 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -186,6 +186,17 @@ Item { } } } + + Flowee.CheckBox { + id: balanceSetting + checked: root.account.countBalance + onCheckedChanged: root.account.countBalance = checked + } + Flowee.CheckBoxLabel { + Layout.fillWidth: true + buddy: balanceSetting + text: qsTr("Include balance in total") + } } Flowee.GroupBox { diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 42c8af0..b402502 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -18,7 +18,7 @@ #include "AccountInfo.h" #include "WalletHistoryModel.h" #include "FloweePay.h" -#include "PaymentRequest.h" +#include "WalletConfig.h" #include #include @@ -215,6 +215,34 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +int AccountInfo::maxFiatInstaPay() const +{ + WalletConfig config(m_wallet->segment()->segmentId()); + assert(config.isValid()); + return config.maxFiatInstaPay(); +} + +void AccountInfo::setMaxFiatInstaPay(int cents) +{ + WalletConfig config(m_wallet->segment()->segmentId()); + assert(config.isValid()); + config.setMaxFiatInstaPay(cents); +} + +bool AccountInfo::countBalance() const +{ + WalletConfig config(m_wallet->segment()->segmentId()); + assert(config.isValid()); + return config.countBalance(); +} + +void AccountInfo::setCountBalance(bool newCountBalance) +{ + WalletConfig config(m_wallet->segment()->segmentId()); + assert(config.isValid()); + config.setCountBalance(newCountBalance); +} + int AccountInfo::initialBlockHeight() const { return m_initialBlockHeight; diff --git a/src/AccountInfo.h b/src/AccountInfo.h index bf41921..00b1d16 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -65,6 +65,14 @@ class AccountInfo : public QObject Q_PROPERTY(bool needsPinToPay READ needsPinToPay NOTIFY encryptionChanged) Q_PROPERTY(bool needsPinToOpen READ needsPinToOpen NOTIFY encryptionChanged) Q_PROPERTY(bool isDecrypted READ isDecrypted NOTIFY encryptionChanged) + /** + * If true, please count the balance(s) of this wallet in the app-wide balance + */ + Q_PROPERTY(bool countBalance READ countBalance WRITE setCountBalance NOTIFY neverEmitted) + /** + * Config the 'insta-pay' feature, set in cents the limit to enable. Zero disables. + */ + Q_PROPERTY(int maxFiatInstaPay READ maxFiatInstaPay WRITE setMaxFiatInstaPay NOTIFY neverEmitted) public: AccountInfo(Wallet *wallet, QObject *parent = nullptr); @@ -145,6 +153,12 @@ public: int initialBlockHeight() const; + bool countBalance() const; + void setCountBalance(bool newCountBalance); + + int maxFiatInstaPay() const; + void setMaxFiatInstaPay(int cents); + signals: void balanceChanged(); void utxosChanged(); @@ -157,6 +171,7 @@ signals: void hasFreshTransactionsChanged(); void encryptionChanged(); void modelsChanged(); + void neverEmitted(); // to silence the lambs^Warnings private slots: // callback from wallet -- 2.54.0 From b0ba0f3173a4fd3b1db4a5a153dffe2a394eda08 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 May 2023 15:56:32 +0200 Subject: [PATCH 0497/1428] Fixlet where zero values didn't get shown --- guis/Flowee/BitcoinAmountLabel.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 28e95c7..b196351 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -25,7 +25,9 @@ import QtQuick.Layouts 1.11 */ QQC2.Control { id: root - property double value: 0 + // Notice that the default is not a valid value so we can be certain that + // we get the onValueChanged() callback. + property double value: 0.123 /// When colorize is true can turn the label /// green or red based on the value. Making this property /// hold a different number than 'value' will allow things like -- 2.54.0 From 38345aba5aabedbbad0457cbc02f34b9e07dea90 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 15:32:33 +0200 Subject: [PATCH 0498/1428] Further design the InstaPay dataset This is essentially the backend work for the instapay setup. GUI still needs to be done. --- guis/mobile/PayWithQR.qml | 1 + src/AccountInfo.cpp | 37 ++++++++++++++++++++++------- src/AccountInfo.h | 13 +++++----- src/FloweePay.cpp | 40 ++++++++++++++++++++++++------- src/FloweePay.h | 5 +++- src/Payment.cpp | 50 ++++++++++++++++++++++++++++++++++++++- src/Payment.h | 6 +++++ src/WalletConfig.cpp | 46 ++++++++++++++++++++++++++++++----- src/WalletConfig.h | 13 ++++++++-- 9 files changed, 178 insertions(+), 33 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 8321616..78c72ed 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -65,6 +65,7 @@ Page { account: portfolio.current fiatPrice: Fiat.price autoPrepare: true + instaPay: true // easier testing values (for when you don't have a camera) // paymentAmount: 100000000 diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index b402502..385968e 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -215,34 +215,55 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } -int AccountInfo::maxFiatInstaPay() const +bool AccountInfo::allowInstaPay() const { - WalletConfig config(m_wallet->segment()->segmentId()); + WalletConfig config(m_wallet); assert(config.isValid()); - return config.maxFiatInstaPay(); + return config.allowInstaPay(); } -void AccountInfo::setMaxFiatInstaPay(int cents) +void AccountInfo::setAllowInstaPay(bool newAllowInstaPay) { - WalletConfig config(m_wallet->segment()->segmentId()); + WalletConfig config(m_wallet); assert(config.isValid()); - config.setMaxFiatInstaPay(cents); + config.setAllowInstaPay(newAllowInstaPay); +} + +int AccountInfo::fiatInstaPayLimit(const QString ¤cyCode) const +{ + WalletConfig config(m_wallet); + assert(config.isValid()); + return config.fiatInstaPayLimit(currencyCode); +} + +void AccountInfo::setFiatInstaPayLimit(const QString ¤cyCode, int cents) +{ + WalletConfig config(m_wallet); + assert(config.isValid()); + config.setFiatInstaPayLimit(currencyCode, cents); } bool AccountInfo::countBalance() const { - WalletConfig config(m_wallet->segment()->segmentId()); + WalletConfig config(m_wallet); assert(config.isValid()); return config.countBalance(); } void AccountInfo::setCountBalance(bool newCountBalance) { - WalletConfig config(m_wallet->segment()->segmentId()); + WalletConfig config(m_wallet); assert(config.isValid()); config.setCountBalance(newCountBalance); } +QStringList AccountInfo::instaPayLimitCurrencies() const +{ + WalletConfig config(m_wallet); + assert(config.isValid()); + return config.fiatInstaPayLimits().keys(); +} + int AccountInfo::initialBlockHeight() const { return m_initialBlockHeight; diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 00b1d16..0b89b8c 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -69,10 +69,7 @@ class AccountInfo : public QObject * If true, please count the balance(s) of this wallet in the app-wide balance */ Q_PROPERTY(bool countBalance READ countBalance WRITE setCountBalance NOTIFY neverEmitted) - /** - * Config the 'insta-pay' feature, set in cents the limit to enable. Zero disables. - */ - Q_PROPERTY(int maxFiatInstaPay READ maxFiatInstaPay WRITE setMaxFiatInstaPay NOTIFY neverEmitted) + Q_PROPERTY(bool allowInstaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY neverEmitted) public: AccountInfo(Wallet *wallet, QObject *parent = nullptr); @@ -156,8 +153,12 @@ public: bool countBalance() const; void setCountBalance(bool newCountBalance); - int maxFiatInstaPay() const; - void setMaxFiatInstaPay(int cents); + Q_INVOKABLE QStringList instaPayLimitCurrencies() const; + Q_INVOKABLE int fiatInstaPayLimit(const QString ¤cyCode) const; + Q_INVOKABLE void setFiatInstaPayLimit(const QString ¤cyCode, int cents); + + bool allowInstaPay() const; + void setAllowInstaPay(bool newAllowInstaPay); signals: void balanceChanged(); diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8554a57..275b80a 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -68,7 +68,9 @@ enum FileTags { WalletName, // string. Duplicate of the wallet name WalletEncryptionSeed, // uint32 (see wallet.h) WalletSetting_CountBalance, // bool, if we count balance in app-total - WalletSetting_FiatInstaPayLimit, // int, cents + WalletSetting_FiatInstaPayEnabled, // bool + WalletSetting_FiatInstaPayLimitCurrency, // string, ISO-currency-code + WalletSetting_FiatInstaPayLimit, // int, cents. Has to be directly behind the currency. }; static P2PNet::Chain s_chain = P2PNet::MainChain; @@ -288,6 +290,7 @@ void FloweePay::init() QFile in(m_basedir + AppdataFilename); Wallet *lastOpened = nullptr; + QString currencyCode; // for wallet config if (in.open(QIODevice::ReadOnly)) { const auto dataSize = in.size(); Streaming::BufferPool pool(dataSize); @@ -312,6 +315,7 @@ void FloweePay::init() lastOpened = nullptr; } walletEncryptionSeed = 0; + currencyCode.clear(); } else if (parser.tag() == WalletPriority) { if (lastOpened) { @@ -339,13 +343,25 @@ void FloweePay::init() else logWarning() << "Setting seen before walletId"; } - else if (parser.tag() == WalletSetting_FiatInstaPayLimit) { + else if (parser.tag() == WalletSetting_FiatInstaPayLimitCurrency) { if (lastOpened) - m_walletConfigs[lastOpened->segment()->segmentId()].maxFiatInstaPay - = parser.intData(); + currencyCode = QString::fromUtf8(parser.stringData()); else logWarning() << "Setting seen before walletId"; } + else if (parser.tag() == WalletSetting_FiatInstaPayEnabled) { + if (lastOpened) + m_walletConfigs[lastOpened->segment()->segmentId()].allowInstaPay = parser.boolData(); + else + logWarning() << "Setting seen before walletId"; + } + else if (parser.tag() == WalletSetting_FiatInstaPayLimit) { + if (lastOpened && !currencyCode.isEmpty()) + m_walletConfigs[lastOpened->segment()->segmentId()].fiatInstaPayLimits[currencyCode] + = parser.intData(); + else + logWarning() << "Setting seen before walletId or currencyCode"; + } } } @@ -390,12 +406,18 @@ void FloweePay::saveData() builder.addByteArray(WalletName, nameData.constData(), nameData.size()); } - WalletConfig conf(wallet->segment()->segmentId()); - assert(conf.isValid()); - builder.add(WalletSetting_CountBalance, conf.countBalance()); - builder.add(WalletSetting_FiatInstaPayLimit, conf.maxFiatInstaPay()); + // each wallet should have a config file, lets save the content + auto conf = m_walletConfigs.find(wallet->segment()->segmentId()); + assert(conf != m_walletConfigs.end()); + builder.add(WalletSetting_CountBalance, conf->countBalance); + builder.add(WalletSetting_FiatInstaPayEnabled, conf->allowInstaPay); + QMapIterator iter(conf->fiatInstaPayLimits); + while (iter.hasNext()) { + iter.next(); + builder.add(WalletSetting_FiatInstaPayLimitCurrency, iter.key().toStdString()); + builder.add(WalletSetting_FiatInstaPayLimit, iter.value()); + } } - auto buf = builder.buffer(); // hash the new file and check if its different lest we can skip saving diff --git a/src/FloweePay.h b/src/FloweePay.h index 6de9ba3..9179a2f 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -311,8 +311,11 @@ private slots: private: struct WalletConfigData { + // a per-wallet bool indicating its balance should be counted in the whole bool countBalance = true; - int maxFiatInstaPay = 0; // in cents + bool allowInstaPay = false; + // per currency-code upper limit where payments are auto-approved. + QMap fiatInstaPayLimits; }; void init(); diff --git a/src/Payment.cpp b/src/Payment.cpp index 28277d2..1b5cbe3 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -22,6 +22,8 @@ #include "PaymentDetailOutput_p.h" #include "FloweePay.h" #include "AccountInfo.h" +#include "PriceDataProvider.h" +#include "WalletConfig.h" #include #include @@ -292,11 +294,44 @@ void Payment::prepare() logCritical() << "Wrote tx to" << out.fileName(); } #endif + + if (m_allowInstaPay) { + PaymentDetailOutput *out = nullptr; + if (m_paymentDetails.length() == 1) { + // insta-pay is used to fund a single output, anything more complex than that + // should be presented to the user for approval, no exceptions. + out = m_paymentDetails.first()->toOutput(); + } + if (!out) + return; + if (out->maxSelected()) + return; + + auto *fp = FloweePay::instance(); + auto *prices = fp->prices(); + assert(prices); + auto currency = prices->currencyName(); + assert(!currency.isEmpty()); + WalletConfig conf(m_wallet); + if (!conf.allowInstaPay()) { + logInfo() << "Payment-prepare. Insta-pay for wallet is turned off:" << m_wallet->name(); + return; + } + auto limit = conf.fiatInstaPayLimit(currency); + logInfo() << "Payment-prepare. Insta-pay for" << currency << "set to" << limit << "payment is" << out->paymentAmountFiat(); + if (out->paymentAmountFiat() > limit) + return; + + // schedule broadcast in a different event in order to + // allow multiple changes and prepare()s to happen and + // only send the best version to the network. + QTimer::singleShot(50, this, SLOT(broadcast())); + } } void Payment::broadcast() { - if (!m_txPrepared) + if (!m_txPrepared || m_txBroadcastStarted) return; // call to wallet to mark outputs locked and save tx. @@ -375,6 +410,19 @@ void Payment::addDetail(PaymentDetail *detail) doAutoPrepare(); } +bool Payment::allowInstaPay() const +{ + return m_allowInstaPay; +} + +void Payment::setAllowInstaPay(bool newAllowInstaPay) +{ + if (m_allowInstaPay == newAllowInstaPay) + return; + m_allowInstaPay = newAllowInstaPay; + emit allowInstaPayChanged(); +} + bool Payment::autoPrepare() const { return m_autoPrepare; diff --git a/src/Payment.h b/src/Payment.h index ca23cc8..fe111c6 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -55,6 +55,7 @@ class Payment : public QObject Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY userCommentChanged) Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged); Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) + Q_PROPERTY(bool instaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY allowInstaPayChanged) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared @@ -202,6 +203,9 @@ public: bool autoPrepare() const; void setAutoPrepare(bool newAutoPrepare); + bool allowInstaPay() const; + void setAllowInstaPay(bool newAllowInstaPay); + private slots: void sentToPeer(); void txRejected(short reason, const QString &message); @@ -223,6 +227,7 @@ signals: void userCommentChanged(); void walletPinChanged(); void autoPrepareChanged(); + void allowInstaPayChanged(); private: void doAutoPrepare(); @@ -241,6 +246,7 @@ private: bool m_txPrepared; bool m_txBroadcastStarted; bool m_preferSchnorr; + bool m_allowInstaPay = false; Tx m_tx; int m_fee; // in sats per byte int m_assignedFee; diff --git a/src/WalletConfig.cpp b/src/WalletConfig.cpp index 9aea97f..1acd159 100644 --- a/src/WalletConfig.cpp +++ b/src/WalletConfig.cpp @@ -18,6 +18,7 @@ #include "WalletConfig.h" #include "FloweePay.h" +#include "Wallet.h" WalletConfig::WalletConfig(const WalletConfig &other) : m_walletId(other.m_walletId) @@ -29,6 +30,11 @@ WalletConfig::WalletConfig(uint16_t walletId) { } +WalletConfig::WalletConfig(Wallet *wallet) + : WalletConfig(wallet->segment()->segmentId()) +{ +} + WalletConfig &WalletConfig::operator=(const WalletConfig &other) { m_walletId = other.m_walletId; @@ -69,24 +75,52 @@ void WalletConfig::setCountBalance(bool newCountBalance) emit fp->totalBalanceConfigChanged(); } -int WalletConfig::maxFiatInstaPay() const +bool WalletConfig::allowInstaPay() const { auto configs = FloweePay::instance()->m_walletConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); - return i->maxFiatInstaPay; + return i->allowInstaPay; } -void WalletConfig::setMaxFiatInstaPay(int newMaxFiatInstaPay) +void WalletConfig::setAllowInstaPay(bool on) { auto *fp = FloweePay::instance(); auto configs = fp->m_walletConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); - if (i->maxFiatInstaPay == newMaxFiatInstaPay) + if (i->allowInstaPay == on) return; - i->maxFiatInstaPay = newMaxFiatInstaPay; + i->allowInstaPay = on; + fp->m_walletConfigs = configs; + fp->startSaveDate_priv(); +} + +const QMap &WalletConfig::fiatInstaPayLimits() const +{ + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + return i->fiatInstaPayLimits; +} + +int WalletConfig::fiatInstaPayLimit(const QString ¤cyCode) const +{ + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + return i->fiatInstaPayLimits.value(currencyCode); +} + +void WalletConfig::setFiatInstaPayLimit(const QString ¤cyCode, int limitInCent) +{ + auto *fp = FloweePay::instance(); + auto configs = fp->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + if (i->fiatInstaPayLimits.value(currencyCode) == limitInCent) + return; + i->fiatInstaPayLimits[currencyCode] = limitInCent; fp->m_walletConfigs = configs; fp->startSaveDate_priv(); - emit fp->totalBalanceConfigChanged(); } diff --git a/src/WalletConfig.h b/src/WalletConfig.h index 7c11a80..2e8dfc7 100644 --- a/src/WalletConfig.h +++ b/src/WalletConfig.h @@ -18,8 +18,12 @@ #ifndef FLOWEE_WALLET_CONFIG_H #define FLOWEE_WALLET_CONFIG_H +#include +#include #include +class Wallet; + /** * A set of user-level config fields for a wallet. * This is owned by the FloweePay application singleton. @@ -30,6 +34,7 @@ public: /// creates an invalid wallet config WalletConfig() = default; explicit WalletConfig(uint16_t walletId); + explicit WalletConfig(Wallet *wallet); WalletConfig(const WalletConfig &other); WalletConfig &operator=(const WalletConfig &other); @@ -40,8 +45,12 @@ public: bool countBalance() const; void setCountBalance(bool newCountBalance); - int maxFiatInstaPay() const; - void setMaxFiatInstaPay(int newMaxFiatInstaPay); + bool allowInstaPay() const; + void setAllowInstaPay(bool on); + + const QMap& fiatInstaPayLimits() const; + int fiatInstaPayLimit(const QString ¤cyCode) const; + void setFiatInstaPayLimit(const QString ¤cyCode, int limitInCent); private: int m_walletId = -1; -- 2.54.0 From b218f9f0d4c8b5897605fa7b5738c9cda133d538 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 15:58:38 +0200 Subject: [PATCH 0499/1428] Fix inconsistent capitalization of toolTipText Follow the Qt decided usage with 2 upper case characters. --- guis/Flowee/CheckBox.qml | 8 ++++---- guis/desktop/AccountDetails.qml | 4 ++-- guis/desktop/NewAccountCreateBasicAccount.qml | 4 ++-- guis/desktop/NewAccountCreateHDAccount.qml | 2 +- guis/desktop/NewAccountImportAccount.qml | 4 ++-- guis/mobile/AccountPageListItem.qml | 4 ++-- guis/mobile/ImportWalletPage.qml | 2 +- guis/mobile/NewAccount.qml | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 58b68af..f58806a 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -23,9 +23,9 @@ T.CheckBox { id: control property bool sliderOnIndicator: true - property string tooltipText: "" + property string toolTipText: "" - implicitWidth: slider.width + 6 + title.width + (tooltipText === "" ? 0 : (questionMarkIcon.width + 16)) + implicitWidth: slider.width + 6 + title.width + (toolTipText === "" ? 0 : (questionMarkIcon.width + 16)) implicitHeight: Math.max(slider.implicitHeight, title.implicitHeight) clip: true @@ -78,7 +78,7 @@ T.CheckBox { Rectangle { id: questionMarkIcon - visible: control.tooltipText !== "" && control.enabled + visible: control.toolTipText !== "" && control.enabled width: q.width + 14 height: width anchors.left: title.right @@ -96,7 +96,7 @@ T.CheckBox { anchors.margins: -7 cursorShape: Qt.PointingHandCursor id: clicky - onClicked: QQC2.ToolTip.show(control.tooltipText, 15000); + onClicked: QQC2.ToolTip.show(control.toolTipText, 15000); } } } diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 2dc78da..f6eff1c 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -213,14 +213,14 @@ Item { text: qsTr("Change Addresses") visible: root.account.isHDWallet onClicked: root.account.secrets.showChangeChain = checked - tooltipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") + toolTipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") } Flowee.CheckBox { id: usedAddresses text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked - tooltipText: qsTr("Switches between unused and used Bitcoin addresses") + toolTipText: qsTr("Switches between unused and used Bitcoin addresses") } Flowee.WalletSecretsView { id: historyView diff --git a/guis/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml index 759d269..2b053e2 100644 --- a/guis/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -71,13 +71,13 @@ ColumnLayout { Flowee.CheckBox { id: singleAddress text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") } /* Flowee.CheckBox { id: schnorr text: qsTr("Default to signing using ECDSA"); - tooltipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") + toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") } */ } } diff --git a/guis/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml index d85f228..5cc48f4 100644 --- a/guis/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -71,7 +71,7 @@ ColumnLayout { Flowee.CheckBox { id: schnorr text: qsTr("Default to signing using ECDSA"); - tooltipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") + toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") Layout.columnSpan: 2 } */ Label { diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index eba1f5d..74b8c36 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -123,13 +123,13 @@ GridLayout { Flowee.CheckBox { id: schnorr text: qsTr("Default to signing using ECDSA"); - tooltipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") + toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") Layout.columnSpan: 2 } */ Flowee.CheckBox { id: singleAddress text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true visible: importAccount.isPrivateKey Layout.columnSpan: 2 diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index e672aba..be20aa4 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -236,13 +236,13 @@ QQC2.Control { text: qsTr("Change Addresses") visible: root.account.isHDWallet onClicked: root.account.secrets.showChangeChain = checked - tooltipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") + toolTipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") } Flowee.CheckBox { text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked - tooltipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") + toolTipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") } } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 5f69fe8..490783f 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -113,7 +113,7 @@ Page { Flowee.CheckBox { id: singleAddress text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true visible: importAccount.isPrivateKey Layout.columnSpan: 2 diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 6ffcba8..db17cb5 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -153,7 +153,7 @@ Page { Flowee.CheckBox { id: singleAddress text: qsTr("Force Single Address"); - tooltipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") } Flowee.Label { text: qsTr("Derivation") + ":" -- 2.54.0 From 26df1eba926dab7e0c0aee8fbdb44afb77a590f0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 18:06:10 +0200 Subject: [PATCH 0500/1428] Move EditableLabel out to its own class for reuse On mobile we should not just show a text edit on an otherwise labels-only screen, because the edit takes focus and opens the on-screen keyboard. Which makes the amount of usable space significantly less. So make the editing user-triggered. --- guis/mobile.qrc | 1 + guis/mobile/EditableLabel.qml | 71 ++++++++++++++++++++++++++++++ guis/mobile/TransactionDetails.qml | 48 +++----------------- 3 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 guis/mobile/EditableLabel.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 835931f..1dba6a3 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -69,5 +69,6 @@ mobile/NumericKeyboardWidget.qml mobile/AccountSelectorWidget.qml mobile/PageTitledBox.qml + mobile/EditableLabel.qml diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml new file mode 100644 index 0000000..1aca51c --- /dev/null +++ b/guis/mobile/EditableLabel.qml @@ -0,0 +1,71 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + id: root + implicitHeight: editWidget.implicitHeight + height: editWidget.height + property alias text: ourLabel.text + property bool editable: true + + signal edited; + + Flowee.Label { + id: ourLabel + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + anchors.baseline: editWidget.baseline + visible: !editWidget.visible + } + Image { + id: editIcon + anchors.right: parent.right + width: 20 + height: 20 + smooth: true + source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + // Editing is a risky business until QTBUG-109306 has been deployed + // visible: root.editable + visible: false + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + editWidget.visible = true + editWidget.focus = true + editWidget.forceActiveFocus(); + } + } + } + Flowee.TextField { + id: editWidget + anchors.left: parent.left + anchors.right: editIcon.left + anchors.rightMargin: 10 + visible: false + text: ourLabel.text + onTextChanged: { + ourLabel.text = text; + root.edited(); + } + } +} diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index c9dc3af..60aff69 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -99,50 +99,12 @@ Page { PageTitledBox { title: qsTr("Transaction comment") - Item { + EditableLabel { + id: editableLabel width: parent.width - implicitHeight: commentEdit.implicitHeight - height: commentEdit.height - - Flowee.Label { - id: commentLabel - text: root.transaction == null ? "" : root.transaction.comment - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - anchors.baseline: commentEdit.baseline - visible: !commentEdit.visible - } - Image { - id: editIcon - anchors.right: parent.right - width: 20 - height: 20 - smooth: true - source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - // Editing is a risky business until QTBUG-109306 has been deployed - // visible: root.infoObject != null && infoObject.commentEditable - visible: false - MouseArea { - anchors.fill: parent - anchors.margins: -15 - onClicked: { - commentEdit.visible = true - commentEdit.focus = true - commentEdit.forceActiveFocus(); - } - } - } - Flowee.TextField { - id: commentEdit - anchors.left: parent.left - anchors.right: editIcon.left - anchors.rightMargin: 10 - visible: false - text: root.transaction == null ? "" : root.transaction.comment - onTextChanged: if (root.infoObject != null) infoObject.userComment = text - } + text: root.transaction == null ? "" : root.transaction.comment + editable: root.infoObject != null && infoObject.commentEditable + onEdited: if (root.infoObject != null) infoObject.userComment = text } } -- 2.54.0 From 1636e1e6b1dcd85533006af2b0e285ec8c0bfcb6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 18:57:23 +0200 Subject: [PATCH 0501/1428] Redesign AccountList page (wallets). The simple 'wallet-information' page is now a general wallet configuration page. This has commented out the future archive / encrypt parts as well. --- guis/mobile/AccountPageListItem.qml | 169 +++++++++++++++++----------- guis/mobile/AccountsList.qml | 49 +++++++- guis/mobile/EditableLabel.qml | 13 ++- guis/mobile/TextButton.qml | 1 + src/MenuModel.cpp | 2 +- 5 files changed, 156 insertions(+), 78 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index be20aa4..d8cb208 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -34,78 +34,46 @@ QQC2.Control { Flowee.AccountTypeLabel { Layout.fillWidth: true account: root.account + font.pixelSize: root.font.pixelSize * 0.8 + color: palette.brightText } - PageTitledBox { - title: qsTr("Sync Status") - - Flowee.Label { - text: { - var height = root.account.lastBlockSynched - if (height < 1) - return "" - var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); - if (time !== "") - time = " (" + time + ")"; - return height + " / " + Pay.chainHeight + time; - } - } - } - - PageTitledBox { - title: qsTr("Wallet Name") - - Flowee.TextField { + title: portfolio.singleAccountSetup ? qsTr("Name") : "" + EditableLabel { text: account.name - onTextChanged: root.account.name = text + onEdited: root.account.name = text width: parent.width } } - PageTitledBox { - title: qsTr("Options") - - Flowee.CheckBox { - visible: !portfolio.singleAccountSetup - enabled: !root.account.isArchived - checked: root.account.isPrimaryAccount - onClicked: root.account.isPrimaryAccount = checked - text: qsTr("Primary Wallet") - } + Flowee.CheckBox { + visible: !portfolio.singleAccountSetup && !root.account.isArchived + text: qsTr("Hide in private mode") } - /* TextButton { - Layout.fillWidth: true - visible: !root.account.needsPinToOpen - showPageIcon: true - text: { - if (!root.account.needsPinToPay) - return qsTr("Enable Pin to Pay") - if (!root.account.needsPinToOpen) - return qsTr("Enable Pin to Open") - return ""; // already fully encrypted - } + id: instaPayButton + visible: !root.account.isArchived + text: root.account.allowsInstaPay ? qsTr("Enable InstaPay") : qsTr("Configure InstaPay") subtext: { - if (root.account.needsPinToPay) - return qsTr("Pin to Pay is enabled"); + if (!root.account.allowsInstaPay) + return qsTr("Fast payments for low amounts") + + var currencies = root.account.instaPayLimitCurrencies() + if (currencies.length === 1) { + return ": => " + root.account.fiatInstaPayLimit(currencies[0]); + } return ""; } - onClicked: {} // TODO - } */ - /* - TODO Give opportunity to decrypt here. - */ - - /* - TODO archived wallets functionality - */ + showPageIcon: true + } TextButton { text: qsTr("Backup information") showPageIcon: true Layout.fillWidth: true + enabled: root.account.isDecrypted onClicked: thePile.push(root.account.isHDWallet ? hdBackupDetails : backupDetails); Component { @@ -279,26 +247,91 @@ QQC2.Control { } } } + + /* TextButton { - text: qsTr("Addresses and Keys") + visible: !root.account.needsPinToOpen + showPageIcon: true + text: { + if (!root.account.needsPinToPay) + return qsTr("Enable Pin to Pay") + if (!root.account.needsPinToOpen) + return qsTr("Convert to Pin to Open") + return ""; // already fully encrypted + } + subtext: { + if (root.account.needsPinToPay) + return qsTr("Pin to Pay is enabled"); + return "Wallet is not protected"; + } + + onClicked: {} // TODO + } + + Rectangle { + id: decryptButton + height: decryptButtonText.height +20 + width: decryptButtonText.width + 30 + visible: !root.account.isDecrypted + color: Pay.useDarkSkin ? "#c2cacc" : "#bcc3c5" + Text { + id: decryptButtonText + text: qsTr("Open Wallet") + anchors.centerIn: parent + color: "black" + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.ArrowCursor + // TODO Give opportunity to decrypt here. + } + } + */ + + TextButton { + text: qsTr("Addresses and keys") visible: root.account.isHDWallet + enabled: !root.account.needsPinToOpen || root.account.isDecrypted showPageIcon: true Layout.fillWidth: true onClicked: thePile.push(backupDetails); } + PageTitledBox { + title: qsTr("Sync Status") + visible: !root.account.isArchived + + Flowee.Label { + text: { + var height = root.account.lastBlockSynched + if (height < 1) + return "" + var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); + if (time !== "") + time = " (" + time + ")"; + return height + " / " + Pay.chainHeight + time; + } + } + } + /* + Rectangle { + id: archiveButton + height: archiveButtonText.height + 20 + color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" + width: archiveButtonText.width + 30 + visible: !portfolio.singleAccountSetup + + Text { + id: archiveButtonText + text: root.account.isArchived ? qsTr("Unarchive Wallet") : qsTr("Archive Wallet") + anchors.centerIn: parent + color: "black" + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.ArrowCursor + // TODO archived wallets functionality + } + } + */ } - - - /* - Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) - Q_PROPERTY(int initialBlockHeight READ initialBlockHeight NOTIFY lastBlockSynchedChanged) - Q_PROPERTY(QDateTime lastBlockSynchedTime READ lastBlockSynchedTime NOTIFY lastBlockSynchedChanged) - Q_PROPERTY(QString timeBehind READ timeBehind NOTIFY lastBlockSynchedChanged) - Q_PROPERTY(WalletSecretsModel* secrets READ secretsModel NOTIFY modelsChanged) - Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) - Q_PROPERTY(QDateTime lastMinedTransaction READ lastMinedTransaction NOTIFY balanceChanged) - Q_PROPERTY(bool isDecrypted READ isDecrypted NOTIFY encryptionChanged) - */ - - } diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index f208427..52c3def 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -16,10 +16,19 @@ * along with this program. If not, see . */ import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee Page { id: root - headerText: qsTr("Wallet Information") + headerText: portfolio.singleAccountSetup ? qsTr("Wallet") : qsTr("Wallets") + + property QtObject newAccountAction: QQC2.Action { + text: qsTr("Add Wallet") + onTriggered: thePile.push("./NewAccount.qml") + } + menuItems: [ newAccountAction ] function indexOfCurrentAccount() { var list = tabBar.model; @@ -30,6 +39,37 @@ Page { return 0; } + Column { + id: topGrid + anchors.top: parent.top + anchors.topMargin: 10 + anchors.left: parent.left + anchors.right: parent.right + visible: !portfolio.singleAccountSetup + height: visible ? implicitHeight: 0 + + Flowee.CheckBox { + text: qsTr("Private Mode") + toolTipText: qsTr("You can hide private wallets when you hand over your phone") + // TODO + } + TextButton { + showPageIcon: true + text: qsTr("Default Wallet") + subtext: { + for (let a of portfolio.accounts) { + if (a.isPrimaryAccount) { + var defaultAccount = a.name; + break; + } + } + + qsTr("%1 is used on startup").arg(defaultAccount); + } + onClicked: ; // TODO + } + } + ListView { id: tabBar model: { @@ -44,7 +84,8 @@ Page { orientation: Qt.Horizontal width: parent.width - anchors.top: parent.top + anchors.top: topGrid.bottom + anchors.topMargin: 10 height: portfolio.accounts.length > 1 ? 50 : 0 clip: true boundsBehavior: Flickable.StopAtBounds @@ -71,7 +112,7 @@ Page { Text { id: tabName - color: index === tabBar.currentIndex ? "white" : "#c2c2c2" + color: index === tabBar.currentIndex ? palette.windowText : palette.brightText; text: modelData.name anchors.centerIn: parent } @@ -115,7 +156,9 @@ Page { boundsBehavior: Flickable.DragOverBounds width: accountPageListView.width height: accountPageListView.height + contentHeight: item.height AccountPageListItem { + id: item width: accountPageListView.width account: modelData clip: true diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 1aca51c..94e7d58 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -43,16 +43,16 @@ Item { height: 20 smooth: true source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - // Editing is a risky business until QTBUG-109306 has been deployed - // visible: root.editable - visible: false + visible: root.editable MouseArea { anchors.fill: parent anchors.margins: -15 onClicked: { - editWidget.visible = true - editWidget.focus = true - editWidget.forceActiveFocus(); + editWidget.visible = !editWidget.visible; + if (editWidget.visible) { + editWidget.focus = true + editWidget.forceActiveFocus(); + } } } } @@ -67,5 +67,6 @@ Item { ourLabel.text = text; root.edited(); } + onEditingFinished: visible = false } } diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index cd95d2b..a2c6c6d 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -38,6 +38,7 @@ Item { y: 10 width: parent.width wrapMode: Text.WordWrap + color: enabled ? palette.windowText : palette.brightText } Flowee.Label { id: smallLabel diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 0d2b70c..bf05120 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -22,9 +22,9 @@ MenuModel::MenuModel(QObject *parent) m_current(&m_root) { m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); - m_root.children.append({tr("Wallet Information"), "AccountsList.qml", {}}); m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); m_root.children.append({tr("About"), "./About.qml", {}}); + m_root.children.append({tr("Wallets"), "AccountsList.qml", {}}); /* m_root.children.append({tr("Settings"), "", { { tr("Security"), "", {} }, -- 2.54.0 From 033cb5962dec253618789fd7114dd6ca23c71d6a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 19:55:52 +0200 Subject: [PATCH 0502/1428] Add feature; private wallets A wallet can permanently be labeled a 'private wallet' which is a simple boolean. Then when you may need to hand over your phone to a clerk, all you do is quickly enable the 'private mode' which is sufficiently deep in the menu to make it hidden. The effect is that all wallets marked private will be hidden for the duration of that feature being on. --- guis/desktop/AccountDetails.qml | 10 ++++++ guis/desktop/SettingsPane.qml | 12 +++++++ guis/mobile/AccountPageListItem.qml | 2 ++ guis/mobile/AccountsList.qml | 3 +- src/AccountInfo.cpp | 52 +++++++++++++++++------------ src/AccountInfo.h | 15 +++++---- src/FloweePay.cpp | 31 +++++++++++++++-- src/FloweePay.h | 11 ++++-- src/PortfolioDataProvider.cpp | 36 +++++++++++++++++--- src/WalletConfig.cpp | 27 +++++++++++++-- src/WalletConfig.h | 4 ++- src/main_utils.cpp | 5 --- src/main_utils_android.cpp | 5 --- 13 files changed, 159 insertions(+), 54 deletions(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index f6eff1c..a4af7fc 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -197,6 +197,16 @@ Item { buddy: balanceSetting text: qsTr("Include balance in total") } + Flowee.CheckBox { + id: privateCB + checked: root.account.isPrivate + onClicked: root.account.isPrivate = checked + } + Flowee.CheckBoxLabel { + Layout.fillWidth: true + buddy: privateCB + text: qsTr("Hide in private mode") + } } Flowee.GroupBox { diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index 8f23dad..689f25d 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -132,6 +132,18 @@ Pane { buddy: darkSkinChooser text: qsTr("Night Mode") } + Flowee.CheckBox { + id: privateModeCB + Layout.alignment: Qt.AlignRight + checked: Pay.privateMode + onCheckedChanged: Pay.privateMode = checked + } + Flowee.CheckBoxLabel { + Layout.columnSpan: 2 + buddy: privateModeCB + text: qsTr("Private Mode") + toolTipText: qsTr("Hides private wallets while enabled") + } Label { text: qsTr("Version") + ":" diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index d8cb208..d9cf2f2 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -48,7 +48,9 @@ QQC2.Control { Flowee.CheckBox { visible: !portfolio.singleAccountSetup && !root.account.isArchived + checked: root.account.isPrivate text: qsTr("Hide in private mode") + onClicked: root.account.isPrivate = checked } TextButton { diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 52c3def..36b667f 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -51,7 +51,8 @@ Page { Flowee.CheckBox { text: qsTr("Private Mode") toolTipText: qsTr("You can hide private wallets when you hand over your phone") - // TODO + checked: Pay.privateMode + onClicked: Pay.privateMode = checked } TextButton { showPageIcon: true diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 385968e..dcae3c8 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -17,8 +17,8 @@ */ #include "AccountInfo.h" #include "WalletHistoryModel.h" +#include "TransactionInfo.h" #include "FloweePay.h" -#include "WalletConfig.h" #include #include @@ -26,10 +26,13 @@ #include #include +#include + AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) : QObject(parent), m_wallet(wallet), + m_config(m_wallet), m_initialBlockHeight(wallet->segment()->lastBlockSynched()) { connect(wallet, SIGNAL(utxosChanged()), this, SIGNAL(utxosChanged()), Qt::QueuedConnection); @@ -215,53 +218,58 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +bool AccountInfo::isPrivate() const +{ + assert(m_config.isValid()); + return m_config.isPrivate(); +} + +void AccountInfo::setIsPrivate(bool newIsPrivate) +{ + assert(m_config.isValid()); + m_config.setIsPrivate(newIsPrivate); +} + bool AccountInfo::allowInstaPay() const { - WalletConfig config(m_wallet); - assert(config.isValid()); - return config.allowInstaPay(); + assert(m_config.isValid()); + return m_config.allowInstaPay(); } void AccountInfo::setAllowInstaPay(bool newAllowInstaPay) { - WalletConfig config(m_wallet); - assert(config.isValid()); - config.setAllowInstaPay(newAllowInstaPay); + assert(m_config.isValid()); + m_config.setAllowInstaPay(newAllowInstaPay); } int AccountInfo::fiatInstaPayLimit(const QString ¤cyCode) const { - WalletConfig config(m_wallet); - assert(config.isValid()); - return config.fiatInstaPayLimit(currencyCode); + assert(m_config.isValid()); + return m_config.fiatInstaPayLimit(currencyCode); } void AccountInfo::setFiatInstaPayLimit(const QString ¤cyCode, int cents) { - WalletConfig config(m_wallet); - assert(config.isValid()); - config.setFiatInstaPayLimit(currencyCode, cents); + assert(m_config.isValid()); + m_config.setFiatInstaPayLimit(currencyCode, cents); } bool AccountInfo::countBalance() const { - WalletConfig config(m_wallet); - assert(config.isValid()); - return config.countBalance(); + assert(m_config.isValid()); + return m_config.countBalance(); } void AccountInfo::setCountBalance(bool newCountBalance) { - WalletConfig config(m_wallet); - assert(config.isValid()); - config.setCountBalance(newCountBalance); + assert(m_config.isValid()); + m_config.setCountBalance(newCountBalance); } QStringList AccountInfo::instaPayLimitCurrencies() const { - WalletConfig config(m_wallet); - assert(config.isValid()); - return config.fiatInstaPayLimits().keys(); + assert(m_config.isValid()); + return m_config.fiatInstaPayLimits().keys(); } int AccountInfo::initialBlockHeight() const diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 0b89b8c..5f2fe24 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -19,16 +19,12 @@ #define ACCOUNTINFO_H #include -#include "TransactionInfo.h" -#include "Wallet.h" +#include "WalletConfig.h" #include "WalletHistoryModel.h" #include "WalletSecretsModel.h" -#include "qtimer.h" - -#include -#include class TransactionInfo; +class QTimer; class AccountInfo : public QObject { @@ -70,6 +66,7 @@ class AccountInfo : public QObject */ Q_PROPERTY(bool countBalance READ countBalance WRITE setCountBalance NOTIFY neverEmitted) Q_PROPERTY(bool allowInstaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY neverEmitted) + Q_PROPERTY(bool isPrivate READ isPrivate WRITE setIsPrivate NOTIFY neverEmitted) public: AccountInfo(Wallet *wallet, QObject *parent = nullptr); @@ -160,6 +157,9 @@ public: bool allowInstaPay() const; void setAllowInstaPay(bool newAllowInstaPay); + bool isPrivate() const; + void setIsPrivate(bool newIsPrivate); + signals: void balanceChanged(); void utxosChanged(); @@ -175,12 +175,13 @@ signals: void neverEmitted(); // to silence the lambs^Warnings private slots: - // callback from wallet + // callbacks from wallet void balanceHasChanged(); void walletEncryptionChanged(); private: Wallet * const m_wallet; + WalletConfig m_config; QTimer *m_closeWalletTimer = nullptr; std::unique_ptr m_model; std::unique_ptr m_secretsModel; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 275b80a..2a87f46 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -57,6 +57,7 @@ constexpr const char *USERAGENT = "net/useragent"; constexpr const char *DSPTIMEOUT = "payment/dsp-timeout"; constexpr const char *CURRENCY_COUNTRIES = "countryCodes"; // historical constexpr const char *CURRENCY_COUNTRY = "countryCode"; // current +constexpr const char *PRIVATE_MODE = "private-mode"; constexpr const char *AppdataFilename = "/appdata"; // used for the default wallet @@ -68,6 +69,7 @@ enum FileTags { WalletName, // string. Duplicate of the wallet name WalletEncryptionSeed, // uint32 (see wallet.h) WalletSetting_CountBalance, // bool, if we count balance in app-total + WalletSetting_IsPrivate, // bool, will be hidden in priate mode WalletSetting_FiatInstaPayEnabled, // bool WalletSetting_FiatInstaPayLimitCurrency, // string, ISO-currency-code WalletSetting_FiatInstaPayLimit, // int, cents. Has to be directly behind the currency. @@ -189,6 +191,7 @@ FloweePay::FloweePay() m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); + m_privateMode = appConfig.value(PRIVATE_MODE, false).toBool(); m_prices.reset(new PriceDataProvider(appConfig.value(CURRENCY_COUNTRY).toString())); // Update expected chain-height every 5 minutes @@ -233,7 +236,7 @@ FloweePay::FloweePay() // forward signal connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); - connect (this, &FloweePay::startSaveDate_priv, this, [=]() { + connect (this, &FloweePay::startSaveData_priv, this, [=]() { // As Qt does not allow starting a timer from any thread, we first use a signal // to move the request to save to the main thread, after which we schedule it with // the singleshot below. @@ -343,6 +346,12 @@ void FloweePay::init() else logWarning() << "Setting seen before walletId"; } + else if (parser.tag() == WalletSetting_IsPrivate) { + if (lastOpened) + m_walletConfigs[lastOpened->segment()->segmentId()].privateWallet = parser.boolData(); + else + logWarning() << "Setting seen before walletId"; + } else if (parser.tag() == WalletSetting_FiatInstaPayLimitCurrency) { if (lastOpened) currencyCode = QString::fromUtf8(parser.stringData()); @@ -410,6 +419,7 @@ void FloweePay::saveData() auto conf = m_walletConfigs.find(wallet->segment()->segmentId()); assert(conf != m_walletConfigs.end()); builder.add(WalletSetting_CountBalance, conf->countBalance); + builder.add(WalletSetting_IsPrivate, conf->privateWallet); builder.add(WalletSetting_FiatInstaPayEnabled, conf->allowInstaPay); QMapIterator iter(conf->fiatInstaPayLimits); while (iter.hasNext()) { @@ -649,7 +659,7 @@ Wallet *FloweePay::createWallet(const QString &name) m_walletConfigs.insert(id, {}); connectToWallet(w); - emit startSaveDate_priv(); // schedule a save of the m_wallets list + emit startSaveData_priv(); // schedule a save of the m_wallets list return w; } @@ -812,7 +822,22 @@ void FloweePay::connectToWallet(Wallet *wallet) // the encryption seed is saved in the wallet-list. // Save as soon as the data changed. saveData(); - }, Qt::QueuedConnection); + }, Qt::QueuedConnection); +} + +bool FloweePay::privateMode() const +{ + return m_privateMode; +} + +void FloweePay::setPrivateMode(bool newPrivateMode) +{ + if (m_privateMode == newPrivateMode) + return; + m_privateMode = newPrivateMode; + emit privateModeChanged(); + QSettings appConfig; + appConfig.setValue(PRIVATE_MODE, m_privateMode); } bool FloweePay::activityShowsBch() const diff --git a/src/FloweePay.h b/src/FloweePay.h index 9179a2f..16ee11d 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -66,7 +66,8 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) // notifications - Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged); + Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) + Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) public: enum UnitOfBitcoin { BCH, @@ -285,12 +286,15 @@ public: bool activityShowsBch() const; void setActivityShowsBch(bool newActivityShowsBch); + bool privateMode() const; + void setPrivateMode(bool newPrivateMode); + signals: void loadComplete(); /// \internal void loadComplete_priv(); /// \internal - void startSaveDate_priv(); + void startSaveData_priv(); void unitChanged(); void walletsChanged(); void darkSkinChanged(); @@ -304,6 +308,7 @@ signals: void fontScalingChanged(); void activityShowsBchChanged(); void totalBalanceConfigChanged(); + void privateModeChanged(); private slots: void loadingCompleted(); @@ -313,6 +318,7 @@ private: struct WalletConfigData { // a per-wallet bool indicating its balance should be counted in the whole bool countBalance = true; + bool privateWallet = false; // is hidden in private mode bool allowInstaPay = false; // per currency-code upper limit where payments are auto-approved. QMap fiatInstaPayLimits; @@ -350,6 +356,7 @@ private: bool m_activityShowsBch = false; bool m_offline = false; bool m_gotHeadersSyncedOnce = false; + bool m_privateMode = false; // wallets marked private are hidden when true friend class WalletConfig; }; diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 76819cf..ff046af 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -25,7 +25,13 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) { - connect (FloweePay::instance(), &FloweePay::walletsChanged, this, [=]() { + auto app = FloweePay::instance(); + for (auto &wallet : app->wallets()) { + addWalletAccount(wallet); + } + selectDefaultWallet(); + + connect (app, &FloweePay::walletsChanged, this, [=]() { const auto &wallets = FloweePay::instance()->wallets(); if (wallets.isEmpty()) { m_accounts.clear(); @@ -40,8 +46,11 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) selectDefaultWallet(); emit accountsChanged(); }); - - connect (FloweePay::instance(), SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); + connect (app, SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); + connect (app, &FloweePay::privateModeChanged, this, [=]() { + selectDefaultWallet(); + emit accountsChanged(); + }); } QList PortfolioDataProvider::accounts() const @@ -55,7 +64,13 @@ QList PortfolioDataProvider::accounts() const // we filter out the wallets that are NOT user-owned. Which is essentially the main initial // wallet created to allow people to deposit instantly. // Such a wallet is migrated to user-owned the moment a deposit is detected. + const bool privateModeOn = FloweePay::instance()->privateMode(); for (auto *account : m_accountInfos) { + if (privateModeOn) { // skip wallets that are 'private' while in private mode. + WalletConfig config(account->id()); + if (config.isPrivate()) + continue; + } if (account->userOwnedWallet() && !account->isArchived()) answer.append(account); } @@ -110,6 +125,7 @@ void PortfolioDataProvider::selectDefaultWallet() { int fallback = -1; int current = -1; + const bool privateModeOn = FloweePay::instance()->privateMode(); PrivacySegment::Priority selectedPriority = PrivacySegment::OnlyManual; for (int i = 0; i < m_accounts.size(); ++i) { auto wallet = m_accounts.at(i); @@ -117,6 +133,15 @@ void PortfolioDataProvider::selectDefaultWallet() if (!wallet->userOwnedWallet()) { fallback = i; } else if (prio < selectedPriority) { + if (privateModeOn) { + // downgrade wallets that are 'private' while in private mode. + WalletConfig config(wallet); + if (config.isPrivate()) { + if (fallback == -1) + fallback = i; + continue; + } + } current = i; selectedPriority = prio; } @@ -134,14 +159,15 @@ void PortfolioDataProvider::selectDefaultWallet() double PortfolioDataProvider::totalBalance() const { double rc = 0; + const bool privateModeOn = FloweePay::instance()->privateMode(); for (int i = 0; i < m_accounts.size(); ++i) { auto wallet = m_accounts.at(i); // skip archived wallet balances. if (!wallet->segment() || wallet->segment()->priority() == PrivacySegment::OnlyManual) continue; - WalletConfig config(wallet->segment()->segmentId()); + WalletConfig config(wallet); assert(config.isValid()); - if (!config.countBalance()) + if (!config.countBalance() || (privateModeOn && config.isPrivate())) continue; rc += wallet->balanceConfirmed(); diff --git a/src/WalletConfig.cpp b/src/WalletConfig.cpp index 1acd159..7d2264d 100644 --- a/src/WalletConfig.cpp +++ b/src/WalletConfig.cpp @@ -71,7 +71,7 @@ void WalletConfig::setCountBalance(bool newCountBalance) return; i->countBalance = newCountBalance; fp->m_walletConfigs = configs; - fp->startSaveDate_priv(); + fp->startSaveData_priv(); emit fp->totalBalanceConfigChanged(); } @@ -93,7 +93,7 @@ void WalletConfig::setAllowInstaPay(bool on) return; i->allowInstaPay = on; fp->m_walletConfigs = configs; - fp->startSaveDate_priv(); + fp->startSaveData_priv(); } const QMap &WalletConfig::fiatInstaPayLimits() const @@ -122,5 +122,26 @@ void WalletConfig::setFiatInstaPayLimit(const QString ¤cyCode, int limitIn return; i->fiatInstaPayLimits[currencyCode] = limitInCent; fp->m_walletConfigs = configs; - fp->startSaveDate_priv(); + fp->startSaveData_priv(); +} + +bool WalletConfig::isPrivate() const +{ + auto configs = FloweePay::instance()->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + return i->privateWallet; +} + +void WalletConfig::setIsPrivate(bool newIsPrivate) +{ + auto *fp = FloweePay::instance(); + auto configs = fp->m_walletConfigs; + auto i = configs.find(m_walletId); + assert(i != configs.end()); + if (i->privateWallet == newIsPrivate) + return; + i->privateWallet = newIsPrivate; + fp->m_walletConfigs = configs; + fp->startSaveData_priv(); } diff --git a/src/WalletConfig.h b/src/WalletConfig.h index 2e8dfc7..25cd5ff 100644 --- a/src/WalletConfig.h +++ b/src/WalletConfig.h @@ -41,7 +41,6 @@ public: bool isValid() const; - bool countBalance() const; void setCountBalance(bool newCountBalance); @@ -52,6 +51,9 @@ public: int fiatInstaPayLimit(const QString ¤cyCode) const; void setFiatInstaPayLimit(const QString ¤cyCode, int limitInCent); + bool isPrivate() const; + void setIsPrivate(bool newIsPrivate); + private: int m_walletId = -1; }; diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 4647d7c..c29b39c 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -139,11 +139,6 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c netData->startRefreshTimer(); PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); - for (auto &wallet : app->wallets()) { - portfolio->addWalletAccount(wallet); - } - portfolio->selectDefaultWallet(); - engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); if (!cld->parser.isSet(cld->offline) && cld->parser.isSet(cld->connect)) { diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 401b386..4c2046f 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -102,11 +102,6 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) netData->startRefreshTimer(); PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); - for (auto &wallet : app->wallets()) { - portfolio->addWalletAccount(wallet); - } - portfolio->selectDefaultWallet(); - engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); app->startNet(); // lets go! -- 2.54.0 From 747ff27a1fba15ea5d759bb39a348c4c454f078e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 22:00:47 +0200 Subject: [PATCH 0503/1428] Various updates and UX features This brings the new 'privacy mode' to use the standard widgets and make the UX more smooth. The swiping between pages / wallets is more visually satisfying. We update the sync label to have a live counter of how long ago the last block came in. (both front-ends) --- guis/desktop/AccountDetails.qml | 22 ++++++++++++-- guis/mobile/AccountPageListItem.qml | 46 ++++++++++++++++++++++------- guis/mobile/AccountsList.qml | 25 +++++++--------- guis/mobile/EditableLabel.qml | 4 ++- 4 files changed, 68 insertions(+), 29 deletions(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index a4af7fc..91f3771 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -101,16 +101,32 @@ Item { columns: 2 Label { + id: syncLabel Layout.columnSpan: 2 + property string time: "" text: { var height = root.account.lastBlockSynched if (height < 1) return "" - var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); - if (time !== "") - time = " (" + time + ")"; + var time = ""; + if (syncLabel.time !== "") + time = " (" + syncLabel.time + ")"; return qsTr("Sync Status") + ": " + height + " / " + Pay.chainHeight + time; } + Timer { + // the lastBlockSynchedTime does not change, + // but since we render it as '12 minutes ago' + // we need to actually re-interpret that + // ever so often to keep the relative time. + running: !root.account.isArchived + interval: 30000 // 30 sec + repeat: true + triggeredOnStart: true + onTriggered: { + syncLabel.time = Pay.formatDateTime( + root.account.lastBlockSynchedTime); + } + } } Flowee.AccountTypeLabel { Layout.columnSpan: 2 diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index d9cf2f2..d30cda4 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -43,16 +43,11 @@ QQC2.Control { text: account.name onEdited: root.account.name = text width: parent.width + // this hides the edit icon for a smoother swipe + editable: index == accountPageListView.currentIndex } } - Flowee.CheckBox { - visible: !portfolio.singleAccountSetup && !root.account.isArchived - checked: root.account.isPrivate - text: qsTr("Hide in private mode") - onClicked: root.account.isPrivate = checked - } - TextButton { id: instaPayButton visible: !root.account.isArchived @@ -275,6 +270,7 @@ QQC2.Control { height: decryptButtonText.height +20 width: decryptButtonText.width + 30 visible: !root.account.isDecrypted + radius: 5 color: Pay.useDarkSkin ? "#c2cacc" : "#bcc3c5" Text { id: decryptButtonText @@ -300,20 +296,48 @@ QQC2.Control { } PageTitledBox { title: qsTr("Sync Status") - visible: !root.account.isArchived Flowee.Label { + id: syncLabel + width: parent.width + property string time: "" text: { + if (root.account.isArchived) + return qsTr("Archived wallets do not check for activities. Balance may be out of date"); + var height = root.account.lastBlockSynched if (height < 1) return "" - var time = Pay.formatDateTime(root.account.lastBlockSynchedTime); - if (time !== "") - time = " (" + time + ")"; + var time = ""; + if (syncLabel.time !== "") + time = " (" + syncLabel.time + ")"; return height + " / " + Pay.chainHeight + time; } + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + + Timer { + // the lastBlockSynchedTime does not change, + // but since we render it as '12 minutes ago' + // we need to actually re-interpret that + // ever so often to keep the relative time. + running: !root.account.isArchived + interval: 30000 // 30 sec + repeat: true + triggeredOnStart: true + onTriggered: { + syncLabel.time = Pay.formatDateTime( + root.account.lastBlockSynchedTime); + } + } } } + Flowee.CheckBox { + visible: !portfolio.singleAccountSetup && !root.account.isArchived + checked: root.account.isPrivate + text: qsTr("Hide in private mode") + onClicked: root.account.isPrivate = checked + } + /* Rectangle { id: archiveButton diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 36b667f..0edf7be 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -48,12 +48,6 @@ Page { visible: !portfolio.singleAccountSetup height: visible ? implicitHeight: 0 - Flowee.CheckBox { - text: qsTr("Private Mode") - toolTipText: qsTr("You can hide private wallets when you hand over your phone") - checked: Pay.privateMode - onClicked: Pay.privateMode = checked - } TextButton { showPageIcon: true text: qsTr("Default Wallet") @@ -67,7 +61,16 @@ Page { qsTr("%1 is used on startup").arg(defaultAccount); } - onClicked: ; // TODO + onClicked: thePile.push("./SelectDefaulAccountPage.qml"); + } + TextButton { + text: Pay.privateMode ? qsTr("Exit Private Mode") : qsTr("Enter Private Mode") + subtext: Pay.privateMode ? qsTr("Reveals wallets marked private") + : qsTr("Hides wallets marked private") + onClicked: { + Pay.privateMode = !Pay.privateMode + thePile.pop(); + } } } @@ -144,13 +147,7 @@ Page { cacheBuffer: 2 currentIndex: indexOfCurrentAccount(); onCurrentIndexChanged: tabBar.currentIndex = currentIndex - onContentXChanged: { - var pos = contentX; - let index = pos / width; - if (index !== Math.floor(index)) // its moving, ignore - return; - currentIndex = index; - } + onContentXChanged: currentIndex = Math.round(contentX / width); delegate: Flickable { flickableDirection: Flickable.VerticalFlick diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 94e7d58..4a69996 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -43,7 +43,8 @@ Item { height: 20 smooth: true source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - visible: root.editable + opacity: root.editable ? 1 : 0 + visible: opacity > 0 MouseArea { anchors.fill: parent anchors.margins: -15 @@ -55,6 +56,7 @@ Item { } } } + Behavior on opacity { NumberAnimation { } } } Flowee.TextField { id: editWidget -- 2.54.0 From 337d441781a028335100f71b156b6c7eb27e7e49 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 May 2023 22:40:05 +0200 Subject: [PATCH 0504/1428] Replace gear icon with pen icon for this Editing a text field is better indicated with a pencil. --- guis/mobile.qrc | 2 ++ guis/mobile/EditableLabel.qml | 7 ++++--- guis/mobile/images/edit-pen-light.svg | 6 ++++++ guis/mobile/images/edit-pen.svg | 6 ++++++ 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 guis/mobile/images/edit-pen-light.svg create mode 100644 guis/mobile/images/edit-pen.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 1dba6a3..7d11eb5 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -24,6 +24,8 @@ mobile/images/settingsIcon.svg mobile/images/edit.svg mobile/images/edit-light.svg + mobile/images/edit-pen.svg + mobile/images/edit-pen-light.svg mobile/images/tx-receiving.svg mobile/images/tx-receiving-light.svg mobile/images/tx-send.svg diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 4a69996..6ffa710 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -39,10 +39,11 @@ Item { Image { id: editIcon anchors.right: parent.right - width: 20 - height: 20 + anchors.bottom: ourLabel.bottom + width: 16 + height: 16 smooth: true - source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + source: "qrc:/edit-pen" + (Pay.useDarkSkin ? "-light" : "") + ".svg" opacity: root.editable ? 1 : 0 visible: opacity > 0 MouseArea { diff --git a/guis/mobile/images/edit-pen-light.svg b/guis/mobile/images/edit-pen-light.svg new file mode 100644 index 0000000..a4d86e1 --- /dev/null +++ b/guis/mobile/images/edit-pen-light.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/guis/mobile/images/edit-pen.svg b/guis/mobile/images/edit-pen.svg new file mode 100644 index 0000000..3b958bd --- /dev/null +++ b/guis/mobile/images/edit-pen.svg @@ -0,0 +1,6 @@ + + + + + -- 2.54.0 From df717aa9677bf90b8c8cd9ff536dccce8cc46694 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 14:28:24 +0200 Subject: [PATCH 0505/1428] Set minimum size of mobile wallet. This really only is relevent on desktop, though. --- guis/mobile/main.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index c27b085..aa1a43f 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -27,6 +27,8 @@ ApplicationWindow { title: "Flowee Pay" width: 360 height: 720 + minimumWidth: 300 + minimumHeight: 400 visible: true onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow) -- 2.54.0 From 02ea51ac73802211798da93387c653d418cc6060 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 14:30:05 +0200 Subject: [PATCH 0506/1428] Supply new feature to archive a wallet. This now brings the functionality to archive a wallet to the mobile interface. Archived wallets don't show up in your main view, do not synchronize with the network and any balance they may 'contain' is ignored. --- guis/mobile/AccountPageListItem.qml | 25 +++++++++++++++++++++---- guis/mobile/AccountsList.qml | 26 +++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index d30cda4..e27677b 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -47,6 +47,25 @@ QQC2.Control { editable: index == accountPageListView.currentIndex } } + Rectangle { + Layout.fillWidth: true + color: "#00000000" + border.width: 3 + border.color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" + visible: root.account.isArchived + radius: 10 + implicitHeight: archivedWarningLabel.implicitHeight + 20 + + Flowee.Label { + id: archivedWarningLabel + y: 10 + width: parent.width + horizontalAlignment: Qt.AlignHCenter + text: qsTr("Archived wallets do not check for activities. Balance may be out of date"); + font.bold: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + } TextButton { id: instaPayButton @@ -303,7 +322,7 @@ QQC2.Control { property string time: "" text: { if (root.account.isArchived) - return qsTr("Archived wallets do not check for activities. Balance may be out of date"); + return root.account.timeBehind; var height = root.account.lastBlockSynched if (height < 1) @@ -338,7 +357,6 @@ QQC2.Control { onClicked: root.account.isPrivate = checked } - /* Rectangle { id: archiveButton height: archiveButtonText.height + 20 @@ -355,9 +373,8 @@ QQC2.Control { MouseArea { anchors.fill: parent cursorShape: Qt.ArrowCursor - // TODO archived wallets functionality + onClicked: root.account.isArchived = !root.account.isArchived } } - */ } } diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 0edf7be..ad7cebf 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -146,9 +146,33 @@ Page { clip: true cacheBuffer: 2 currentIndex: indexOfCurrentAccount(); - onCurrentIndexChanged: tabBar.currentIndex = currentIndex + onModelChanged: { + /* + * When an account is archived, or unarchived, it changed the model + * as the account moves location in the list. + * The result is that the model moves back to index zero, which is a + * bit jarring. + * This code tries to move back to the one we were on before. + */ + for (var index = 0; index < model.length; ++index) { + if (model[index] === currentAccount) { + contentX = index * width; + currentIndex = index; + tabBar.currentIndex = index; + break; + } + } + } + onCurrentIndexChanged: { + var curIndex = currentIndex; + tabBar.currentIndex = curIndex; + if (curIndex > 0) + currentAccount = model[curIndex]; // remember + } onContentXChanged: currentIndex = Math.round(contentX / width); + property QtObject currentAccount: null + delegate: Flickable { flickableDirection: Flickable.VerticalFlick boundsBehavior: Flickable.DragOverBounds -- 2.54.0 From 55dbcdced1735be4cc4902ce5098b512a5f2d90b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 15:43:25 +0200 Subject: [PATCH 0507/1428] Add the new screen for default wallet This new screen allows one to quickly select which wallet is the 'default'. This is a nicer design than a checkbox on each wallet. --- guis/mobile.qrc | 1 + guis/mobile/AccountsList.qml | 2 +- guis/mobile/SelectDefaultAccountPage.qml | 85 ++++++++++++++++++++++++ src/PortfolioDataProvider.cpp | 3 - 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 guis/mobile/SelectDefaultAccountPage.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 7d11eb5..55a5c0c 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -72,5 +72,6 @@ mobile/AccountSelectorWidget.qml mobile/PageTitledBox.qml mobile/EditableLabel.qml + mobile/SelectDefaultAccountPage.qml diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index ad7cebf..00d56dc 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -61,7 +61,7 @@ Page { qsTr("%1 is used on startup").arg(defaultAccount); } - onClicked: thePile.push("./SelectDefaulAccountPage.qml"); + onClicked: thePile.push("./SelectDefaultAccountPage.qml"); } TextButton { text: Pay.privateMode ? qsTr("Exit Private Mode") : qsTr("Enter Private Mode") diff --git a/guis/mobile/SelectDefaultAccountPage.qml b/guis/mobile/SelectDefaultAccountPage.qml new file mode 100644 index 0000000..9cc0fba --- /dev/null +++ b/guis/mobile/SelectDefaultAccountPage.qml @@ -0,0 +1,85 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Page { + headerText: qsTr("Select Wallet") + + Flowee.Label { + id: introText + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: qsTr("Pick which wallet will be selected on starting Flowee Pay") + } + + ListView { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.top: introText.bottom + anchors.topMargin: 10 + clip: true + model: portfolio.accounts + delegate: Item { + width: ListView.view.width + height: column.height + 20 + + Rectangle { + anchors.fill: parent + color: (index % 2) == 0 ? palette.base : palette.alternateBase + } + MouseArea { + anchors.fill: parent + onClicked: modelData.isPrimaryAccount = true; + } + Column { + id: column + y: 10 + x: 10 + width: parent.width - 20 + Flowee.Label { + text: modelData.name + font.bold: true + } + Flowee.Label { + text: { + if (modelData.allowInstaPay) { + var cur = Fiat.currencyName; + var limit = modeldata.fiatInstaPayLimit(cur); + if (limit === 0) + return qsTr("No InstaPay limit set"); + return qsTr("InstaPay till %1").arg(Fiat.formattedPrice(limit)); + } + return qsTr("InstaPay is turned off"); + } + } + } + + Flowee.Label { + text: "✷"; + y: 10 + anchors.right: parent.right + anchors.rightMargin: 5 + visible: modelData.isPrimaryAccount + } + } + } +} diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index ff046af..e1e9744 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -204,10 +204,7 @@ void PortfolioDataProvider::walletChangedPriority() // as this just changed, through the UI, we have to mark any other // account that was the primary wallet as no longer being one. for (auto iter = m_accountInfos.begin(); iter != m_accountInfos.end(); ++iter) { - // for (auto &info : m_accountInfos) { - logFatal() << (*iter)->name(); if (*iter != wallet && (*iter)->isPrimaryAccount()) { - logFatal() << " setting to false"; (*iter)->setPrimaryAccount(false); } } -- 2.54.0 From edc6e5b7be74a47316a6a605b68b85bea4647633 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 18:47:54 +0200 Subject: [PATCH 0508/1428] Fix hiding items based on how many wallets we have. --- guis/mobile/AccountPageListItem.qml | 6 +++--- guis/mobile/AccountsList.qml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index e27677b..2a23564 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -44,7 +44,7 @@ QQC2.Control { onEdited: root.account.name = text width: parent.width // this hides the edit icon for a smoother swipe - editable: index == accountPageListView.currentIndex + editable: index === accountPageListView.currentIndex } } Rectangle { @@ -351,7 +351,7 @@ QQC2.Control { } } Flowee.CheckBox { - visible: !portfolio.singleAccountSetup && !root.account.isArchived + visible: portfolio.accounts.length > 1 && !root.account.isArchived checked: root.account.isPrivate text: qsTr("Hide in private mode") onClicked: root.account.isPrivate = checked @@ -362,7 +362,7 @@ QQC2.Control { height: archiveButtonText.height + 20 color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" width: archiveButtonText.width + 30 - visible: !portfolio.singleAccountSetup + visible: portfolio.accounts.length > 1 Text { id: archiveButtonText diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 00d56dc..8648d5f 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -45,7 +45,7 @@ Page { anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right - visible: !portfolio.singleAccountSetup + visible: portfolio.accounts.length > 1 height: visible ? implicitHeight: 0 TextButton { -- 2.54.0 From 10360f9c7f26b3c270652940c23bcf6fe897936d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 20:58:02 +0200 Subject: [PATCH 0509/1428] Catch exceptions from parsing user input. --- src/FloweePay.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 2a87f46..aaba421 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1117,6 +1117,16 @@ NewWalletConfig* FloweePay::createNewBasicWallet(const QString &walletName) NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const QString &password, const QString &walletName) { + // start by validating user input + std::vector dp; + try { + // this throws should the path not validate. + dp = HDMasterKey::deriveFromString(derivationPath.toStdString()); + } catch (const std::exception &e) { + logFatal() << "Failed to parse user provided data due to:" << e; + return nullptr; + } + // special case the first user-created wallet. // If the user creates a new wallet that is identical to the one we auto-created, reuse that one. const bool haveOneHiddenWallet = m_wallets.size() == 1 && !m_wallets.first()->userOwnedWallet(); @@ -1142,7 +1152,6 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const RandAddSeedPerfmon(); GetRandBytes(seed.data(), seed.size()); auto mnemonic = m_hdSeedValidator.generateMnemonic(seed, "en"); - std::vector dp = HDMasterKey::deriveFromString(derivationPath.toStdString()); wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint()); emit walletsChanged(); if (!m_offline) -- 2.54.0 From 469045259e2e4749a58d1580021d18648cd6764e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 21:18:11 +0200 Subject: [PATCH 0510/1428] Use consistent background color. This now uses the same background color as normal line edits. --- guis/Flowee/MultilineTextField.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 05c4564..f3de683 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -111,7 +111,7 @@ QQC2.Control { } background: Rectangle { - color: "#00000000" + color: palette.base border.color: textEdit.activeFocus ? palette.highlight : palette.mid border.width: 0.8 } -- 2.54.0 From 6d648dc6e7482637c78e580c8203837cccfe9362 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 21:21:33 +0200 Subject: [PATCH 0511/1428] Make new account pages follow our UX guidelines. This redoes most of those pages to look much nicer and in-place. This also disables the 'create' button when the derivation path is not correctly entered. --- guis/desktop/NewAccountImportAccount.qml | 5 +- guis/mobile/ImportWalletPage.qml | 213 +++++++++--------- guis/mobile/NewAccount.qml | 273 ++++++++++++----------- guis/mobile/StartupScreen.qml | 78 ++++--- 4 files changed, 307 insertions(+), 262 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 74b8c36..1be1035 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -27,7 +27,7 @@ GridLayout { rowSpacing: 10 property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool finished: typedData === Wallet.PrivateKey || (typedData === Wallet.CorrectMnemonic && derivationPath.derivationOk); property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; property bool isPrivateKey: typedData === Wallet.PrivateKey @@ -147,9 +147,10 @@ GridLayout { } Flowee.TextField { id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); text: "m/44'/0'/0'" // What most BCH wallets are created with visible: !importAccount.isPrivateKey - color: Pay.checkDerivation(text) ? palette.text : "red" + color: derivationOk ? palette.text : "red" } Label { text: qsTr("Alternate phrase") + ":" diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 490783f..cfbfe7a 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -25,61 +25,42 @@ Page { id: importAccount headerText: qsTr("Import Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: finished property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic; + property bool finished: typedData === Wallet.PrivateKey || (typedData === Wallet.CorrectMnemonic && derivationPath.derivationOk); property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; property bool isPrivateKey: typedData === Wallet.PrivateKey - onHeaderButtonClicked: { - var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - - options.forceSingleAddress = singleAddress.checked; - - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } - - GridLayout { + ColumnLayout { width: parent.width - columns: 2 + spacing: 10 + y: 10 Flowee.Label { text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") Layout.fillWidth: true - Layout.columnSpan: 2 wrapMode: Text.Wrap } - Flowee.Label { - text: qsTr("Secret", "The seed-phrase or private key") + ":" - Layout.columnSpan: 2 - } - Flowee.MultilineTextField { - id: secrets - Layout.fillWidth: true - focus: true - nextFocusTarget: accountName - clip: true - } - Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - Layout.alignment: Qt.AlignTop + + PageTitledBox { + title: qsTr("Secret", "The seed-phrase or private key") + Item { + width: parent.width + height: secrets.height + 10 + Flowee.MultilineTextField { + id: secrets + focus: true + nextFocusTarget: accountName + clip: true + anchors.left: parent.left + anchors.right: feedback.left + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + anchors.right: parent.right + } + } } Flowee.Label { @@ -97,17 +78,14 @@ Page { return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this return "" } - Layout.columnSpan: 2 } - Flowee.Label { - text: qsTr("Name") + ":" - Layout.columnSpan: 2 - } - Flowee.TextField { - id: accountName - Layout.columnSpan: 2 - Layout.fillWidth: true + PageTitledBox { + title: qsTr("Name") + Flowee.TextField { + id: accountName + width: parent.width + } } Flowee.CheckBox { @@ -116,65 +94,90 @@ Page { toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true visible: importAccount.isPrivateKey - Layout.columnSpan: 2 } - Flowee.Label { - Layout.columnSpan: 2 - text: qsTr("Oldest Transaction") + ":" - } - Flow { - Layout.fillWidth: true - Layout.columnSpan: 2 - spacing: 10 - QQC2.ComboBox { - id: month - model: { - let locale = Qt.locale(); - var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { - list.push(locale.monthName(i)); + PageTitledBox { + title: qsTr("Oldest Transaction") + Flow { + width: parent.width + spacing: 10 + QQC2.ComboBox { + id: month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; } - return list; + } + QQC2.ComboBox { + id: year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 9; } } - QQC2.ComboBox { - id: year - model: { - var list = []; - let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { - list.push(i); - } - return list; - } - currentIndex: 9; + } + + PageTitledBox { + title: qsTr("Derivation") + visible: !importAccount.isPrivateKey + Flowee.TextField { + id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); + width: parent.width + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" } } - Flowee.Label { - text: qsTr("Derivation") + ":" - Layout.columnSpan: 2 + PageTitledBox { + title: qsTr("Alternate phrase") visible: !importAccount.isPrivateKey + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + width: parent.width + } } - Flowee.TextField { - id: derivationPath - Layout.columnSpan: 2 - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most BCH wallets are created with - visible: !importAccount.isPrivateKey - color: Pay.checkDerivation(text) ? palette.text : "red" - } - Flowee.Label { - text: qsTr("Alternate phrase") + ":" - Layout.columnSpan: 2 - visible: !importAccount.isPrivateKey - } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true - Layout.columnSpan: 2 + + + Item { + width: parent.width + height: createButton.implicitHeight + Flowee.Button { + id: createButton + enabled: finished + anchors.right: parent.right + text: qsTr("Create") + onClicked: { + var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); + if (importAccount.isMnemonic) + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + else + options = Pay.createImportedWallet(secrets.text, accountName.text, sh) + + options.forceSingleAddress = singleAddress.checked; + + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + } } } } diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index db17cb5..1a8cc10 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,82 +25,100 @@ Page { id: root headerText: qsTr("New Bitcoin Cash Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Next") - - onHeaderButtonClicked: { - if (selectedKey === 0) - var page = newBaseWalletScreen; - if (selectedKey === 1) - page = newHDWalletScreen; - if (selectedKey === 2) - page = "./ImportWalletPage.qml"; - thePile.push(page); - } - - property int selectedKey: 1 - - - ColumnLayout { + Flickable { anchors.fill: parent + contentHeight: column.height + 20 + contentWidth: width - Flowee.Label { - text: qsTr("Create a New Wallet") + ":" - } - Flow { + Column { + id: column width: parent.width - property int selectorWidth: (width - spacing * 2) / 2; - property alias selectedKey: root.selectedKey + y: 10 + spacing: 20 - Flowee.CardTypeSelector { - id: accountTypeBasic - key: 0 - title: qsTr("Basic") - width: parent.selectorWidth - onClicked: root.selectedKey = key + PageTitledBox { + title: qsTr("Create a New Wallet") + width: parent.width - features: [ - qsTr("Private keys based", "Property of a wallet"), - qsTr("Difficult to backup", "Context: wallet type"), - qsTr("Great for brief usage", "Context: wallet type") - ] + Flowee.CardTypeSelector { + title: qsTr("HD wallet") + onClicked: root.selectedKey = key + width: parent.width * 0.75 + anchors.horizontalCenter: parent.horizontalCenter + selected: true + + features: [ + qsTr("Seed-phrase based", "Context: wallet type"), + qsTr("Easy to backup", "Context: wallet type"), + qsTr("Most compatible", "The most compatible wallet type") + ] + + MouseArea { + anchors.fill: parent + onClicked: thePile.push(newHDWalletScreen); + } + Rectangle { + anchors.fill: parent + color: "#00000000" + radius: 10 + border.width: 5 + border.color: mainWindow.floweeGreen + } + } + + Flowee.CardTypeSelector { + title: qsTr("Basic") + width: parent.width * 0.75 + onClicked: root.selectedKey = key + selected: true + anchors.horizontalCenter: parent.horizontalCenter + + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] + + MouseArea { + anchors.fill: parent + onClicked: thePile.push(newBaseWalletScreen); + } + Rectangle { + anchors.fill: parent + color: "#00000000" + radius: 10 + border.width: 5 + border.color: palette.mid + } + } } - Flowee.CardTypeSelector { - id: accountTypePreferred - key: 1 - title: qsTr("HD wallet") - width: parent.selectorWidth - onClicked: root.selectedKey = key - features: [ - qsTr("Seed-phrase based", "Context: wallet type"), - qsTr("Easy to backup", "Context: wallet type"), - qsTr("Most compatible", "The most compatible wallet type") - ] - } - } + PageTitledBox { + title: qsTr("Import Existing Wallet") + width: parent.width + Flowee.CardTypeSelector { + title: qsTr("Import") + width: parent.width * 0.75 + onClicked: root.selectedKey = key + anchors.horizontalCenter: parent.horizontalCenter + selected: true - Item { width: 10; height: 15 } - - Flowee.Label { - text: qsTr("Import Existing Wallet") + ":" - } - Item { - width: parent.width - height: accountTypeImport.height - property alias selectedKey: root.selectedKey - Flowee.CardTypeSelector { - id: accountTypeImport - key: 2 - title: qsTr("Import") - width: Math.min(parent.width / 3 * 2, 250) - onClicked: root.selectedKey = key - anchors.horizontalCenter: parent.horizontalCenter - - features: [ - qsTr("Imports seed-phrase"), - qsTr("Imports private key") - ] + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] + MouseArea { + anchors.fill: parent + onClicked: thePile.push("./ImportWalletPage.qml"); + } + Rectangle { + anchors.fill: parent + color: "#00000000" + radius: 10 + border.width: 5 + border.color: palette.mid + } + } } } } @@ -114,26 +132,10 @@ Page { Page { id: newWalletPage headerText: qsTr("New Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 - onHeaderButtonClicked: { - var options = Pay.createNewBasicWallet(accountName.text); - options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } ColumnLayout { width: parent.width + spacing: 10 Flowee.Label { id: title @@ -141,28 +143,36 @@ Page { Layout.fillWidth: true wrapMode: Text.WordWrap } - Flowee.Label { - text: qsTr("Name") + ":"; + + PageTitledBox { + title: qsTr("Name") + Flowee.TextField { + id: accountName + focus: true + width: parent.width + } } - Flowee.TextField { - id: accountName - focus: true - Layout.fillWidth: true - } - Item { width: 10; height: 10 } Flowee.CheckBox { id: singleAddress text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") } - Flowee.Label { - text: qsTr("Derivation") + ":" - } - Flowee.TextField { - id: derivationPath - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most wallets use to import by default - color: Pay.checkDerivation(text) ? palette.text : "red" + Item { + width: parent.width + height: createButton.implicitHeight + QQC2.Button { + id: createButton + anchors.right: parent.right + text: qsTr("Create") + onClicked: { + var options = Pay.createNewBasicWallet(accountName.text); + options.forceSingleAddress = singleAddress.checked; + var accounts = portfolio.accounts; + portfolio.current = accounts[accounts.length - 1] + thePile.pop(); + thePile.pop(); + } + } } } } @@ -172,38 +182,51 @@ Page { Page { headerText: qsTr("New HD-Wallet") - headerButtonVisible: true - headerButtonText: qsTr("Create") - headerButtonEnabled: accountName.text.length > 2 - onHeaderButtonClicked: { - var options = Pay.createNewWallet("m/44'/0'/0'", /* password */"", accountName.text); - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } ColumnLayout { width: parent.width + spacing: 10 Flowee.Label { id: title Layout.fillWidth: true - text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") + text: qsTr("This creates a new wallet that can be backed up with a seed-phrase") wrapMode: Text.WordWrap } - Flowee.Label { - text: qsTr("Name") + ":"; + PageTitledBox { + title: qsTr("Name") + Flowee.TextField { + id: accountName + width: parent.width + focus: true + } } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - focus: true + PageTitledBox { + title: qsTr("Derivation") + Flowee.TextField { + property bool derivationOk: Pay.checkDerivation(text); + id: derivationPath + width: parent.width + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" + } + } + + Item { + width: parent.width + height: createButton.implicitHeight + QQC2.Button { + id: createButton + enabled: derivationPath.derivationOk + anchors.right: parent.right + text: qsTr("Create") + onClicked: { + var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); + var accounts = portfolio.accounts; + portfolio.current = accounts[accounts.length - 1] + thePile.pop(); + thePile.pop(); + } + } } } } diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 3aea28c..1b4cd9a 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -55,12 +55,13 @@ Page { Flickable { anchors.fill: parent contentWidth: parent.width - contentHeight: column.height + contentHeight: column.height + 20 clip: true Column { id: column width: parent.width spacing: 10 + y: 10 Row { spacing: 20 @@ -105,25 +106,36 @@ Page { } Item { width: 1; height: 20 } // spacer - Rectangle { - id: startImportButton - radius: 20 - height:buttonText.height + 20 - width: buttonText.width + 40 - color: "#178b3a" - x: (column.width - width) / 2 - - Flowee.Label { - id: buttonText - color: "white" - text: qsTr("I already have a wallet") - anchors.centerIn: parent - } - MouseArea { - anchors.fill: parent - onClicked: thePile.push("./ImportWalletPage.qml"); - } + Flowee.Label { + id: instructions + text: qsTr("Scan me to send funds to your HD wallet") + ":" + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + width: column.width * 0.8 + x: column.width / 10 } + Flowee.QRWidget { + width: parent.width + qrText: request.qr + } + + /* + Row { + spacing: 20 + height: 20 + anchors.horizontalCenter: parent.horizontalCenter + Rectangle { + // copy to clipboard icon + width: 25 + height: 25 + } + Rectangle { + // 'share' icon + width: 25 + height: 25 + } + } */ + Item { width: 1; height: 5 } // spacer @@ -149,17 +161,23 @@ Page { Item { width: 1; height: 5 } // spacer - Flowee.Label { - id: instructions - text: qsTr("I want to send funds to my new wallet") + ":" - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - width: column.width * 0.8 - x: column.width / 10 - } - Flowee.QRWidget { - width: parent.width - qrText: request.qr + Rectangle { + radius: 20 + height:buttonText.height + 20 + width: buttonText.width + 40 + color: "#178b3a" + anchors.horizontalCenter: parent.horizontalCenter + + Flowee.Label { + id: buttonText + color: "white" + text: qsTr("Add a different wallet") + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: thePile.replace("./NewAccount.qml"); + } } Item { width: 1; height: 40 } // spacer -- 2.54.0 From 9c5476d2ccecb117b76a7832ee609035226c9488 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 22:18:38 +0200 Subject: [PATCH 0512/1428] Remove dead code. Seems to have been forgotten on refactor. --- guis/mobile/NumericKeyboardWidget.qml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 63fa29c..75ea6c9 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -20,10 +20,6 @@ import QtQuick.Controls as QQC2 Flow { id: root - anchors.bottom: slideToApprove.top - anchors.bottomMargin: 15 - width: parent.width - enabled: !payment.details[0].maxSelected Repeater { model: 12 delegate: Item { -- 2.54.0 From 4c96b5ee44e0d8fe91217307cb8130646e5e46dc Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 23:26:54 +0200 Subject: [PATCH 0513/1428] Make numerickeyboard have a static height for keys. This makes the layout look more square and not so cramped. Make sure that the layouts of the payment pages still fit at the same time. --- guis/mobile/NumericKeyboardWidget.qml | 2 +- guis/mobile/PayToOthers.qml | 5 ++--- guis/mobile/PayWithQR.qml | 23 ++++++++++++++++------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 75ea6c9..a4245ac 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -24,7 +24,7 @@ Flow { model: 12 delegate: Item { width: root.width / 3 - height: textLabel.height + 20 + height: 70 QQC2.Label { id: textLabel anchors.centerIn: parent diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 2f097c5..c4afae8 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -283,7 +283,7 @@ Page { anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right - height: Math.max(destinationLabel.height * 3, implicitHeight) + height: Math.max(destinationLabel.height * 2.3, implicitHeight) focus: true property var addressType: Pay.identifyString(text); text: paymentDetail.address @@ -319,7 +319,7 @@ Page { } Flowee.AddressInfoWidget { id: addressInfo - anchors.top: nativeLabel.bottom + anchors.top: nativeLabel.visible ? nativeLabel.bottom : destination.bottom width: parent.width addressType: destination.addressType } @@ -327,7 +327,6 @@ Page { id: priceInput width: parent.width anchors.top: addressInfo.bottom - anchors.topMargin: 10 paymentBackend: paymentDetail fiatFollowsSats: paymentDetail.fiatFollows onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 78c72ed..e73cdf9 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -96,15 +96,24 @@ Page { anchors.top: commentLabel.bottom } - Flowee.Label { - id: errorLabel - text: payment.error - visible: payment.isValid - color: Pay.useDarkSkin ? "#cc5454" : "#6a0c0c" + Rectangle { + visible: errorLabel.text !== "" + color: mainWindow.errorRedBg + radius: 10 + width: parent.width anchors.bottom: walletNameBackground.top anchors.bottomMargin: 6 - wrapMode: Text.Wrap - width: parent.width + height: errorLabel.height + 20 + + Flowee.Label { + id: errorLabel + text: payment.error + wrapMode: Text.Wrap + x: 10 + y: 10 + width: parent.width - 20 + horizontalAlignment: Qt.AlignHCenter + } } -- 2.54.0 From 76ea56bb2f4deb8e4dfc21ed74187e97a02bb37c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 23:27:08 +0200 Subject: [PATCH 0514/1428] Cleanup --- guis/mobile/PageTitledBox.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index 40a4c74..11df606 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -20,8 +20,6 @@ import QtQuick.Layouts import "../Flowee" as Flowee Item { - id: pageTitledBox - property alias title: boxTitle.text default property alias content: contentColumn.children -- 2.54.0 From 452fbe3e44cf3b754d17d20f46fbff12f9627b58 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 23:27:47 +0200 Subject: [PATCH 0515/1428] Allow AccountSelectorWidget to be 1 line in some cases --- guis/mobile/AccountSelectorWidget.qml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 5273751..5e58f06 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -30,13 +30,19 @@ Rectangle { property var balanceActions: [ ] height: { - if (portfolio.singleAccountSetup) - return currentWalletValue.height + 20 + var w = 20 + currentWalletValue.implicitWidth; + if (currentWalletLabel.visible) + w += currentWalletLabel.implicitWidth + 10; // right spacing. + if (hamburgerMenu.visible) + w += 10; + if (width > w && root.stickyAccount) + return currentWalletValue.height + 20; // all on one line return currentWalletValue.height + currentWalletLabel.height + 25 } color: palette.alternateBase Flowee.HamburgerMenu { + id: hamburgerMenu x: 10 anchors.verticalCenter: currentWalletLabel.verticalCenter visible: root.stickyAccount === false && portfolio.accounts.length > 1 @@ -49,6 +55,7 @@ Rectangle { width: parent.width - 30 text: payment.account.name visible: !portfolio.singleAccountSetup + color: root.stickyAccount ? palette.brightText : palette.windowText } Flowee.BitcoinAmountLabel { -- 2.54.0 From 913fdd63a7f428b788bf5928bd7056b9926ac946 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 May 2023 23:42:07 +0200 Subject: [PATCH 0516/1428] Add InstantPay configuration page. --- guis/mobile.qrc | 1 + guis/mobile/AccountPageListItem.qml | 7 +- guis/mobile/InstaPayConfigPage.qml | 111 +++++++++++++++++++++++ guis/mobile/SelectDefaultAccountPage.qml | 5 +- 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 guis/mobile/InstaPayConfigPage.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 55a5c0c..8e293e5 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -73,5 +73,6 @@ mobile/PageTitledBox.qml mobile/EditableLabel.qml mobile/SelectDefaultAccountPage.qml + mobile/InstaPayConfigPage.qml diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 2a23564..81a869b 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -68,9 +68,8 @@ QQC2.Control { } TextButton { - id: instaPayButton visible: !root.account.isArchived - text: root.account.allowsInstaPay ? qsTr("Enable InstaPay") : qsTr("Configure InstaPay") + text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") subtext: { if (!root.account.allowsInstaPay) return qsTr("Fast payments for low amounts") @@ -83,6 +82,10 @@ QQC2.Control { } showPageIcon: true + onClicked: { + var newPage = thePile.push("./InstaPayConfigPage.qml") + newPage.account = root.account; + } } TextButton { diff --git a/guis/mobile/InstaPayConfigPage.qml b/guis/mobile/InstaPayConfigPage.qml new file mode 100644 index 0000000..9bbc1f2 --- /dev/null +++ b/guis/mobile/InstaPayConfigPage.qml @@ -0,0 +1,111 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +// import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + id: root + headerText: qsTr("Instant Pay") + + property QtObject account: portfolio.current + + Flowee.Label { + id: introText + text: qsTr("Requests for payment can be approved automatically using Instant Pay.") + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 10 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + + Rectangle { + anchors.top: introText.bottom + anchors.topMargin: 10 + width: parent.width + radius: 10 + height: warning.implicitHeight + 20 + visible: root.account.needsPinToOpen || root.account.needsPinToPay + color: "#00000000" + border.width: 2 + border.color: "blue" + Flowee.Label { + x: 10 + y: 10 + width: parent.width - 20 + id: warning + text: qsTr("Protected wallets can not be used for InstaPay because they require a PIN before usage") + } + } + + Flowee.CheckBox { + id: mainToggle + anchors.top: introText.bottom + anchors.topMargin: 10 + width: parent.width + text: qsTr("Enable Instant Pay for this wallet") + checked: root.account.allowInstaPay + onClicked: root.account.allowInstaPay = checked + } + + PageTitledBox { + id: priceInput + title: qsTr("Maximum Amount") + anchors.top: mainToggle.bottom + anchors.topMargin: 40 + width: parent.width + enabled: mainToggle.checked + property string currency: Fiat.currencyName + property int instaPayLimit: root.account.fiatInstaPayLimit(currency); + property alias editor: priceFiat.money + function shake() { shaker.start(); } + + Item { + width: parent.width + Flowee.Label { + text: Fiat.currencyName + font.pixelSize: 20 + color: enabled ? palette.windowText : palette.brightText + } + Flowee.FiatValueField { + id: priceFiat + value: priceInput.instaPayLimit + focus: true + Flowee.ObjectShaker { id: shaker } + onValueEdited: root.account.setFiatInstaPayLimit(priceInput.currency, value); + fontPixelSize: 20 + color: enabled ? palette.windowText : palette.brightText + } + } + } + + NumericKeyboardWidget { + id: numericKeyboard + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 + width: parent.width + enabled: mainToggle.checked + } + + + + /* + List currencies a limit has been set for. + */ +} diff --git a/guis/mobile/SelectDefaultAccountPage.qml b/guis/mobile/SelectDefaultAccountPage.qml index 9cc0fba..a51e849 100644 --- a/guis/mobile/SelectDefaultAccountPage.qml +++ b/guis/mobile/SelectDefaultAccountPage.qml @@ -56,6 +56,7 @@ Page { x: 10 width: parent.width - 20 Flowee.Label { + id: mainText text: modelData.name font.bold: true } @@ -63,13 +64,15 @@ Page { text: { if (modelData.allowInstaPay) { var cur = Fiat.currencyName; - var limit = modeldata.fiatInstaPayLimit(cur); + var limit = modelData.fiatInstaPayLimit(cur); if (limit === 0) return qsTr("No InstaPay limit set"); return qsTr("InstaPay till %1").arg(Fiat.formattedPrice(limit)); } return qsTr("InstaPay is turned off"); } + font.pixelSize: mainText.pixelSize * 0.8 + color: palette.brightText } } -- 2.54.0 From 68dcf6a42fac0ef20d6d413e6cc372d2d2c70f1e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 18 May 2023 17:06:33 +0200 Subject: [PATCH 0517/1428] Move logic to backend. make the front-end simpler by having a new property on the PortfolioDataProvider which shares all existing accounts to be shown in this Page. --- guis/mobile/AccountPageListItem.qml | 6 ++-- guis/mobile/AccountsList.qml | 46 +++++++---------------------- src/PortfolioDataProvider.cpp | 30 +++++++++++++++++-- src/PortfolioDataProvider.h | 11 +++++++ 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 81a869b..ea239f8 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -38,7 +38,7 @@ QQC2.Control { color: palette.brightText } PageTitledBox { - title: portfolio.singleAccountSetup ? qsTr("Name") : "" + title: singleAccountSetup ? qsTr("Name") : "" EditableLabel { text: account.name onEdited: root.account.name = text @@ -354,7 +354,7 @@ QQC2.Control { } } Flowee.CheckBox { - visible: portfolio.accounts.length > 1 && !root.account.isArchived + visible: !singleAccountSetup && !root.account.isArchived checked: root.account.isPrivate text: qsTr("Hide in private mode") onClicked: root.account.isPrivate = checked @@ -365,7 +365,7 @@ QQC2.Control { height: archiveButtonText.height + 20 color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" width: archiveButtonText.width + 30 - visible: portfolio.accounts.length > 1 + visible: !singleAccountSetup Text { id: archiveButtonText diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 8648d5f..743a753 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -22,7 +22,7 @@ import "../Flowee" as Flowee Page { id: root - headerText: portfolio.singleAccountSetup ? qsTr("Wallet") : qsTr("Wallets") + headerText: singleAccountSetup ? qsTr("Wallet") : qsTr("Wallets") property QtObject newAccountAction: QQC2.Action { text: qsTr("Add Wallet") @@ -39,20 +39,26 @@ Page { return 0; } + + // this is a special interpretation of the property-name in the context + // of these pages where (unlike in the rest of the app) we take archived + // and private wallets into account. + property bool singleAccountSetup: portfolio.rawAccounts.length === 1 + Column { id: topGrid anchors.top: parent.top anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right - visible: portfolio.accounts.length > 1 + visible: !singleAccountSetup height: visible ? implicitHeight: 0 TextButton { showPageIcon: true text: qsTr("Default Wallet") subtext: { - for (let a of portfolio.accounts) { + for (let a of portfolio.rawAccounts) { if (a.isPrimaryAccount) { var defaultAccount = a.name; break; @@ -76,21 +82,12 @@ Page { ListView { id: tabBar - model: { - var accounts = portfolio.accounts; - for (let a of portfolio.archivedAccounts) { - accounts.push(a); - } - if (accounts.length === 0) - accounts.push(portfolio.current) - return accounts; - } - + model: portfolio.rawAccounts orientation: Qt.Horizontal width: parent.width anchors.top: topGrid.bottom anchors.topMargin: 10 - height: portfolio.accounts.length > 1 ? 50 : 0 + height: singleAccountSetup ? 0 : 50 clip: true boundsBehavior: Flickable.StopAtBounds currentIndex: indexOfCurrentAccount(); @@ -146,33 +143,12 @@ Page { clip: true cacheBuffer: 2 currentIndex: indexOfCurrentAccount(); - onModelChanged: { - /* - * When an account is archived, or unarchived, it changed the model - * as the account moves location in the list. - * The result is that the model moves back to index zero, which is a - * bit jarring. - * This code tries to move back to the one we were on before. - */ - for (var index = 0; index < model.length; ++index) { - if (model[index] === currentAccount) { - contentX = index * width; - currentIndex = index; - tabBar.currentIndex = index; - break; - } - } - } onCurrentIndexChanged: { var curIndex = currentIndex; tabBar.currentIndex = curIndex; - if (curIndex > 0) - currentAccount = model[curIndex]; // remember } onContentXChanged: currentIndex = Math.round(contentX / width); - property QtObject currentAccount: null - delegate: Flickable { flickableDirection: Flickable.VerticalFlick boundsBehavior: Flickable.DragOverBounds diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index e1e9744..8418632 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -84,6 +84,15 @@ QList PortfolioDataProvider::accounts() const return answer; } +QList PortfolioDataProvider::rawAccounts() const +{ + QList answer; + for (auto *account : m_accountInfos) { + answer.append(account); + } + return answer; +} + QList PortfolioDataProvider::archivedAccounts() const { QList answer; @@ -184,11 +193,28 @@ bool PortfolioDataProvider::isSingleAccount() const void PortfolioDataProvider::addWalletAccount(Wallet *wallet) { + assert(wallet); if (m_accounts.contains(wallet)) return; - m_accounts.append(wallet); auto info = new AccountInfo(wallet, this); - m_accountInfos.append(info); + assert(m_accounts.length() == m_accountInfos.length()); + + // we store the wallets semi-sorted. Basically just doing an insert-in-place + // that makes the archived wallets all be at the end and the non-archived ones + // at the start of the list. + // Please do note that we don't re-sort later, so this is not a required internal + // state. + int index = m_accounts.size(); + if (!info->isArchived()) { + for (auto i = m_accountInfos.rbegin(); i != m_accountInfos.rend(); ++i) { + if ((*i)->isArchived()) + --index; + else + break; + } + } + m_accounts.insert(index, wallet); + m_accountInfos.insert(index, info); connect (info, SIGNAL(isPrimaryAccountChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(isArchivedChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(balanceChanged()), this, SIGNAL(totalBalanceChanged())); diff --git a/src/PortfolioDataProvider.h b/src/PortfolioDataProvider.h index cc665b2..7058f04 100644 --- a/src/PortfolioDataProvider.h +++ b/src/PortfolioDataProvider.h @@ -28,8 +28,18 @@ class Payment; class PortfolioDataProvider : public QObject { Q_OBJECT + /** + * List all active and visible accounts. + */ Q_PROPERTY(QList accounts READ accounts NOTIFY accountsChanged) + /** + * List only archived accounts. + */ Q_PROPERTY(QList archivedAccounts READ archivedAccounts NOTIFY accountsChanged) + /** + * List all accounts, active, inactive (aka private) and archived. + */ + Q_PROPERTY(QList rawAccounts READ rawAccounts NOTIFY accountsChanged) Q_PROPERTY(AccountInfo* current READ current WRITE setCurrent NOTIFY currentChanged) Q_PROPERTY(double totalBalance READ totalBalance NOTIFY totalBalanceChanged) Q_PROPERTY(bool singleAccountSetup READ isSingleAccount NOTIFY accountsChanged) @@ -37,6 +47,7 @@ public: explicit PortfolioDataProvider(QObject *parent = nullptr); QList accounts() const; + QList rawAccounts() const; QList archivedAccounts() const; AccountInfo *current() const; -- 2.54.0 From 280678457e73ae322571db3790e8e622fcb3505f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 18 May 2023 17:44:49 +0200 Subject: [PATCH 0518/1428] Cleanup code and make behavior follow docs again --- src/AccountInfo.cpp | 1 + src/AccountInfo.h | 3 +++ src/PortfolioDataProvider.cpp | 47 ++++++++++++++++++----------------- src/PortfolioDataProvider.h | 6 +++-- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index dcae3c8..fb45467 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -228,6 +228,7 @@ void AccountInfo::setIsPrivate(bool newIsPrivate) { assert(m_config.isValid()); m_config.setIsPrivate(newIsPrivate); + emit isPrivateChanged(); } bool AccountInfo::allowInstaPay() const diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 5f2fe24..bc94692 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -174,6 +174,9 @@ signals: void modelsChanged(); void neverEmitted(); // to silence the lambs^Warnings + // for the benefit of the portfolio data provider + void isPrivateChanged(); + private slots: // callbacks from wallet void balanceHasChanged(); diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 8418632..e5cd819 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,36 +51,33 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) selectDefaultWallet(); emit accountsChanged(); }); + connect (this, &PortfolioDataProvider::accountsChanged, [=]() { + m_isSingleAccount = accounts().length() <= 1; + emit singleAccountChanged(); + }); + m_isSingleAccount = accounts().length() <= 1; } QList PortfolioDataProvider::accounts() const { QList answer; - if (m_accountInfos.size() == 1) { - // We have a 'mode' where having exactly one wallet will - // hide the wallets-list to avoid confusing the user with multi wallets. - return answer; - } - // we filter out the wallets that are NOT user-owned. Which is essentially the main initial - // wallet created to allow people to deposit instantly. - // Such a wallet is migrated to user-owned the moment a deposit is detected. const bool privateModeOn = FloweePay::instance()->privateMode(); for (auto *account : m_accountInfos) { - if (privateModeOn) { // skip wallets that are 'private' while in private mode. - WalletConfig config(account->id()); - if (config.isPrivate()) - continue; - } - if (account->userOwnedWallet() && !account->isArchived()) - answer.append(account); + if (account->isArchived()) + continue; + if (!account->userOwnedWallet()) + continue; + if (privateModeOn && WalletConfig(account->id()).isPrivate()) + continue; + answer.append(account); } - // if the only wallet(s) are not user owned, share those with the GUI. - if (answer.isEmpty() && !m_accountInfos.isEmpty()) { - for (auto * const account : m_accountInfos) { - if (account->isArchived()) - answer.append(account); - } + if (answer.length() == 1) { + // We have a 'mode' where having exactly one wallet will + // hide the wallets-list to avoid confusing the user with multi + // wallet features. + answer.clear(); } + return answer; } @@ -188,7 +185,7 @@ double PortfolioDataProvider::totalBalance() const bool PortfolioDataProvider::isSingleAccount() const { - return m_accounts.size() == 1; + return m_isSingleAccount; } void PortfolioDataProvider::addWalletAccount(Wallet *wallet) @@ -218,6 +215,10 @@ void PortfolioDataProvider::addWalletAccount(Wallet *wallet) connect (info, SIGNAL(isPrimaryAccountChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(isArchivedChanged()), this, SLOT(walletChangedPriority())); connect (info, SIGNAL(balanceChanged()), this, SIGNAL(totalBalanceChanged())); + connect (info, &AccountInfo::isPrivateChanged, this, [=]() { + if (FloweePay::instance()->privateMode()) + emit accountsChanged(); + }); emit accountsChanged(); } diff --git a/src/PortfolioDataProvider.h b/src/PortfolioDataProvider.h index 7058f04..e2317a3 100644 --- a/src/PortfolioDataProvider.h +++ b/src/PortfolioDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,7 +42,7 @@ class PortfolioDataProvider : public QObject Q_PROPERTY(QList rawAccounts READ rawAccounts NOTIFY accountsChanged) Q_PROPERTY(AccountInfo* current READ current WRITE setCurrent NOTIFY currentChanged) Q_PROPERTY(double totalBalance READ totalBalance NOTIFY totalBalanceChanged) - Q_PROPERTY(bool singleAccountSetup READ isSingleAccount NOTIFY accountsChanged) + Q_PROPERTY(bool singleAccountSetup READ isSingleAccount NOTIFY singleAccountChanged) public: explicit PortfolioDataProvider(QObject *parent = nullptr); @@ -67,6 +67,7 @@ signals: void accountsChanged(); void currentChanged(); void totalBalanceChanged(); + void singleAccountChanged(); private slots: void walletChangedPriority(); @@ -78,6 +79,7 @@ private: QList m_accountInfos; int m_currentAccount = -1; + bool m_isSingleAccount = true; }; #endif -- 2.54.0 From 7806914fc36413ab02ef431045ed7f90a868b7ad Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 18 May 2023 21:52:51 +0200 Subject: [PATCH 0519/1428] Various small fixes and UX improvements. --- guis/mobile/AccountSelectorWidget.qml | 6 +++--- guis/mobile/MainViewBase.qml | 7 ++++++- guis/mobile/PayToOthers.qml | 8 ++++++-- guis/mobile/PayWithQR.qml | 5 +++++ src/Payment.cpp | 11 +++++++++++ src/Payment.h | 12 ++++++------ 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 5e58f06..d0fddf9 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -35,7 +35,7 @@ Rectangle { w += currentWalletLabel.implicitWidth + 10; // right spacing. if (hamburgerMenu.visible) w += 10; - if (width > w && root.stickyAccount) + if (width > w && (root.stickyAccount || portfolio.singleAccountSetup)) return currentWalletValue.height + 20; // all on one line return currentWalletValue.height + currentWalletLabel.height + 25 } @@ -45,7 +45,7 @@ Rectangle { id: hamburgerMenu x: 10 anchors.verticalCenter: currentWalletLabel.verticalCenter - visible: root.stickyAccount === false && portfolio.accounts.length > 1 + visible: root.stickyAccount === false && !portfolio.singleAccountSetup } Flowee.Label { @@ -73,7 +73,7 @@ Rectangle { anchors.fill: parent onClicked: (mouse) => { if (mouse.x < parent.width / 2) { - if (root.stickyAccount === false && portfolio.accounts.length > 1) + if (root.stickyAccount === false && !portfolio.singleAccountSetup) accountSelector.open(); } else if (balanceActions.length > 0) { while (priceMenu.count > 0) { diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index e80dd3d..1e71f5f 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -113,7 +113,12 @@ QQC2.Control { MouseArea { anchors.fill: parent - onClicked: accountSelector.open(); + onClicked: { + if (accountSelector.visible) + accountSelector.close(); + else + accountSelector.open(); + } } } diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index c4afae8..d7bd78f 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -416,9 +416,13 @@ Page { width: parent.width spacing: 6 - AccountSelectorWidget { } + AccountSelectorWidget { + visible: !portfolio.singleAccountSetup + } - VisualSeparator { } + VisualSeparator { + visible: !portfolio.singleAccountSetup + } Repeater { model: payment.details diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index e73cdf9..f673c32 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -114,6 +114,11 @@ Page { width: parent.width - 20 horizontalAlignment: Qt.AlignHCenter } + + MouseArea { + anchors.fill: parent + // just here to catch mouse clicks. + } } diff --git a/src/Payment.cpp b/src/Payment.cpp index 1b5cbe3..69f1a71 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -421,6 +421,17 @@ void Payment::setAllowInstaPay(bool newAllowInstaPay) return; m_allowInstaPay = newAllowInstaPay; emit allowInstaPayChanged(); + + /* + * InstaPay is typically enabled together with auto-prepare and that gives + * great UX for, well, instantly paying. + * BUT, if the prepare() failed, we should stop trying to do the 'instaPay'. + * It failed, now let the user decide when to send. + */ + QTimer::singleShot(10, this, [=]() { + if (!m_error.isEmpty()) + m_allowInstaPay = false; + }); } bool Payment::autoPrepare() const diff --git a/src/Payment.h b/src/Payment.h index fe111c6..8008bd9 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -44,27 +44,27 @@ class Payment : public QObject Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTargetAddress READ formattedTargetAddress NOTIFY targetAddressChanged) - Q_PROPERTY(bool preferSchnorr READ preferSchnorr WRITE setPreferSchnorr NOTIFY preferSchnorrChanged); + Q_PROPERTY(bool preferSchnorr READ preferSchnorr WRITE setPreferSchnorr NOTIFY preferSchnorrChanged) /// If input is valid, tx can be prepared. \see prepare() - Q_PROPERTY(bool isValid READ validate NOTIFY validChanged); - Q_PROPERTY(QList details READ paymentDetails NOTIFY paymentDetailsChanged); + Q_PROPERTY(bool isValid READ validate NOTIFY validChanged) + Q_PROPERTY(QList details READ paymentDetails NOTIFY paymentDetailsChanged) Q_PROPERTY(BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) /// The price of on BCH Q_PROPERTY(int fiatPrice READ fiatPrice WRITE setFiatPrice NOTIFY fiatPriceChanged) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY userCommentChanged) - Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged); + Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged) Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) Q_PROPERTY(bool instaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY allowInstaPayChanged) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared - Q_PROPERTY(bool txPrepared READ txPrepared NOTIFY txPreparedChanged); + Q_PROPERTY(bool txPrepared READ txPrepared NOTIFY txPreparedChanged) Q_PROPERTY(QString txid READ txid NOTIFY txCreated) Q_PROPERTY(int assignedFee READ assignedFee NOTIFY txCreated) Q_PROPERTY(int txSize READ txSize NOTIFY txCreated) /// If Prepare failed, this is set. - Q_PROPERTY(QString error READ error NOTIFY errorChanged); + Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_ENUMS(DetailType BroadcastStatus) public: -- 2.54.0 From 9ab4e63de0b4990b35144676e5b5595f21f67d2a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 May 2023 11:30:57 +0200 Subject: [PATCH 0520/1428] Small fixlets. --- guis/mobile/AccountPageListItem.qml | 2 ++ guis/mobile/AccountsList.qml | 1 + 2 files changed, 3 insertions(+) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index ea239f8..7923e97 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -360,6 +360,8 @@ QQC2.Control { onClicked: root.account.isPrivate = checked } + Item { width: 1; height: 10 } // spacer. Make the button not to close to the clickable checkbox for fatfingered people. + Rectangle { id: archiveButton height: archiveButtonText.height + 20 diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 743a753..97c8ff5 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -57,6 +57,7 @@ Page { TextButton { showPageIcon: true text: qsTr("Default Wallet") + visible: !portfolio.singleAccountSetup subtext: { for (let a of portfolio.rawAccounts) { if (a.isPrimaryAccount) { -- 2.54.0 From 97a4b74bfafd3ac386117687036f2208f8d5bd9d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 May 2023 15:47:02 +0200 Subject: [PATCH 0521/1428] Include our font. We ship our own font to not have to depend on the host system's installed fonts for 'special' characters like arrows and stars. This is a font forked from the Gnu-free font people, only since that one is 2MB this is a very much stripped down version with just all the fun stuff left. --- guis/images/Flowee-Symbols.otf | Bin 0 -> 237064 bytes guis/widgets.qrc | 1 + src/main.cpp | 8 ++++++++ 3 files changed, 9 insertions(+) create mode 100644 guis/images/Flowee-Symbols.otf diff --git a/guis/images/Flowee-Symbols.otf b/guis/images/Flowee-Symbols.otf new file mode 100644 index 0000000000000000000000000000000000000000..6f3207224680d733fbe350ede5bf948a05ac9a63 GIT binary patch literal 237064 zcmeYd3Grv(WoTevW(aV0b5n@8wLOP{fol%~bBw*4TZnJVfsa2Km>VWAFfhotySTcs zHu4-`U{3qPz`$VS9^fB5DQeMY2IhPo1_r?v_ux>c-!+U|?XsywAnG6gpFBlkDw`8Oyrf`Ox6=h&xxWmA}V3v`Qn#j4Gr;LGt zQG|hkK_w%%q~iazGbN=+$xf zAMx)BTM2Uu0|NsG0~1ITqKCQVzXd}ITmS!v|1oSOV3VMdAc{SRfgyl_J(_`mftjhA zaRLJ?0|QeRQv?G8!wCq@*v4SU$i&FX#KgwT%EHRT%<^>p`u~R*{{D9kkk({jU~qQ# z3sqonkY)J)pIHH97jp}vP68w2|Ns9P7#I&RO=D1CPym_BAjEJFZoenHi)QQ=x1Y26n~?P&O+AC*v9@n~i~w@d}j9!NA7&5z6LcP+@XnU}j)s z;bdT7m<943n9ax_!|)0!&cwjSC=O*aGng>yL)k10e2kz}1k%gOAjCKWD$d3r!?+E~ z=3wAqd<|ROAu*j049wTKvC*el$x5BpI4%goS#=x zl$lgol3!G;$H3smki(GAP!6_Vmm!#;k|CENi6NgMhoP8(!7V4hJT+A}xH2~>Kc|?1 zA&4QBA)TR=A%`K6p@@MYC^fw_C$R|KkYXf56d3%#PS0gXWXNH#f(D%tgE8FjVzA)~ ze)&bYi8)pZMg~U43=GId6f-bD)D$x?gfXOo!yywKJ_-zm3=CnZMa7xD+YZAeTH%{)MH3z$YUsF&;xrUok5=g6g!~Qkpqsi5(a&SbcO~xNsm!LVqwJv^pd79or+i)| zQ*Ex=9<{^j`!pN1gZ}>l_2K4+n;vd_xc=d~hie|Lez@x4{D*TN&UrZTq4z_thn^2T9y&dAe5n3V z;i1$+kq1j3h~BHeS9`DOp6#9LTWcYuA2h^3lnaQU0P`_0FbXm-FbXj+FbcCVurV_5 zfJ|dj0FmHOXOLxJV36Gpv00vhfk7TrBOGL4ke6U!kOxU#XJC+5U|^7UWnf^q%fKM- z!N4Hz%fP_M%)lTYz`!6M&cMLP$-p2V3Be%yp3=E9M3=Hzy7#QS_FfcG?Ffhm;V_=ZK%)r1{&cGmlje$Y_HUk4=D+7Z( z$nN(H42<0j4Dz2C805b&Ffh(zV37aDz##vRfq`)$sM29zP)K25U|hw(piscTpis!b zz{JnMpisiVpwP#_z$D1PpfG`fL17951CtH|gTev^28Epr3`~X$42ld442m`k3{2h( z42pIP42s?i3``vi42r%C42pgX3{2A)7!(5-7!*4h7?@@-Fepx7U{GdbU|_n$z@W^* zz@V(dz`)|kz@Y5Tz@Y5Gz`&Bnz@Que!7K#~49ejU%u>j}pd1ImEJX|q%I6pul+QCT zuoN>es3b5jsDNtdQU(S!1_lPTxeN>}^$ZMZ3m6#G_AoH8$}%vh9bjNkJIuhqYQ?~y z&d9)^zK?-{)sBHdvk`(>|1dCU2QV;b2Qe_P{$*hJ|C52?|1SmxHUjKP3=GU&%pM><;{^~2!;E_w_c0!0 zJkEHC@i1KM9g{8NW5y>SKI3Ty2F4?dCm3&mcw}HuY0bdE@V^<3WGL-5rZXz1GFg-!;s97#Zbsl!BEf8 z#?Z?!g<&?sB8HU=8yL1T>|;2}aE9SB!!3q~3@;eoGkjzC%gDmW%_zhu$*91n&Zx&| z%4oyr%;?1!$QZ#G&zQ!T%UHr#&Dg})$vA;=I^#UXrHpGBH#6>HJji&0@jT--#=DG9 z7+*7fV*JU(z{JkP$0W)m!=%il#bn52!DP?m#^lQs!W7Mv#FWWYz*Npu$JENy!!(&` z7Slqe6-?`ywlVExI>L0C=@QdTrUy*VncgvdW%|R+%*@3s$SlDu&#cC*%WT4I&FsYN z$sE8O!5q(=#+=Js!d%VV#N5d|fq6RfJm#g$YnV4P?_xg4e1iEr^EKwX%ukqKGk;?K z$-=shw3>}5H^a+>85%T1OCEYDfqv3zCu!^+Ic#VW`u!79(H#;VI|!fMUx%Id=!%o@d- z$eO{L&sxS>%i7M`$2ygD4(nppRjeCXcd+hfJ;r*L^$P24)<>-GSiiFVVPj_FViRPO zV3TK4W7B0bVY6m)V)JARWQ$;nXG>$tWh-H;W@}>WWShV?ooyc5Qnod0o7r};9cDYl zc9HD{+kLiYY;W1Vu>EFdV&`NRU>9eXW7lFgWVc|qXLn=wWe;JGW=~?zWG`SZXRl*# zW$$61%sz{KA^QsU_3Yc&_p%>hKh1uL{U-Zk_E+p5*?+MA=V0UD}q9^*XAd4=;f=OfOSoF6#9bN=IE<>KKI=91!)=hEde;m z=St(sH+6c@lW?cp7**c)EG|c&76#;aSVGo96`2Rh|bt z?|8oRvha%WO7hC^%JVAms`Bdd+VVQ{dh&+wCi3R<*77#-_Vdo*UCg_hcQfxc-h;d+ zd9U)m=40X$;8W%^<8$Hj+@Uj zJMjncC-P_WSMWFSPvD=)zkq)Y|4#m6{FnG|@;~E$%m0P{w*Zp>r+}z{tbme$nt+yo zzJRHKm4K~)vw*vRw}78Oh(NSJl0ce3u0V-EwLrZg{*~Kg#3g; zg<^!_h0=s_g-V2~g_?vqg(e717n&!uRA`ORW}#g|2Zc@uofo<$bXVw!&}*U3LVtwW zg$0Erg_VW%h0TTSgk6PwgoA~ngcF4`g!6^VglmOcgu8_&3C|Q>AiP3&gYYil!@_5T zuL$20ej)r(_@@Yq2)~G!h^&Z;h_;B4h^2^wh`WfNNT^7RNU}(lNTEoTNTW!%$P|&e zBFjY9ifj?tEpkZYq{s!4>mv6=o{GE?`7H8Flu?vJlwVX#R8~|))Iii$)LS%2G*UD{ zG+nezv{tl5v|DtN=uFY&qB}*8i=Gy}B>GVFjp%35Ut)}69Af-pVq&slDq`AVMq-v? z4r1nl3!9GDu9UTutCWvam{gopl2pD_rBt(2m()b5SyC&c z)=TY@Iw*BQ>b%r-se4jSrQS$=lje|?l-80qk+zfeln#=Pl}?e)mM)R5mTr}vAU#)l zsq_Zv?b7?Ck4s;cz9s!o`i1m+>2K11WmsgmWrSoTWfWx8W%OiBWo%@eWxQkpWg=wa zWzuAFWlChKWtwCmsu>cQD&FSL7B5Mw`Cs5yp;JM^IhhjEUPS!tgx(wY>8~OY?Ew{>~z_svg>4b%N~|JEqg=uq3kQ!&$7Sd80EO+ z1m&dVl;kw!jO1+OJmo^=QsheHYUCQ_I^|}{EtFd#w?b~U+zz>;a+l>E$bFFeDbFC! zF3%?~DlaXsDQ_WfFYhMrD<2{sEuSQxDPJI8E?+0#Ek8+qru+i=Xi{d`T!-{7VFDu?se5m+B@x9^?B_<^v zC1E8gB}F9-C4D6`C0ivIC2yr5rAVa&rF5k{rBbCDrDmlrrHM*2l;$ffQ(CLEO=-W< zF{QIgSCnomJyLq9^g-#n(m!QZWgcZ=WhrGvWesJ0Wiw@4We;V4n6(m3Wmjm0Xn)l`54+l~$D=l}RcyRTii$RavF7L1mlD9+mwnM^sL#Tu`~K@assursDs&1;@s*$Rhs=2Dwstu~`s#8?wsjgJrqIyX6 zn(8gphpNw1->QC5{jJ8N#;GQtCaxx@rmCi+W~}C`mY`On)~wd0Hc@Sc+I+QTYHQWD zsO?req;^v6g4%VpdumVB-l%<6`=!pP&Y{k)E~YN4uA;83ZlrFh?x60j?x!BA9;2SD zo~2%>UZGyE-lpEGK1F@D`Xcp}>KoLztM5}ks(wcOvidFchw3lX->ZL9|Es~G!L1>r zA*rFDp{}8)VX9%H;jH1M5vUQN5wDS^k*iUnQLWLW(Wx;(W4gvXjinlEG&XDO(m1Gb zLgT!~HI2I(Pc&X@eA4)-$)L%u$)_o*DWj>ZsikSCX`yMa>89zc8KN1jnWUMiS)f_2 zS*O{m*`qmGbC%{p%@vyKHMeQ*)jXnkTJw_TP0a_I&o$p^e%1V=#jM4pC8#B#C9kEX zrK@G4Wv%6;<*5~*6|NPhm8zAaRjgH|)u`2>)vq;8Yp&K3t<_qaw03G8&^oSlPV1`H z9j(V&ue3gD{m}Za&8E$(Evc=bt*))7ZK`df?X2yk9jG0l9j~3HovU41#vBcr3Nqorf0W1(ZO zQ>W9a)1xz4XO_-FofSIkb++m3)j6VbTIZ6^O`Qii&voAE zeAW4*%dE?#E2t}>E3d1jtE+3GYpv^~>!}-{8?GCto2r|mTdZ59+o;>2+pjxKcdqUd z-Br4KbkFNv*S)9vO!uAc58eNIta`k9B6>1Qb?y<>Xk^segN)qAS%Z3jr2or+$$;H}$3W0P z+CafT&A`OK#=yzI#~{cc!XVBd%^=U9%%Ikw)u7j4y1_hyB?fB@HXH0VIBamj;H<$V zgF6Nf4W1i(G5BN1WXNMEWGG=MXQ*aqW9VZTWtd@DV%T8VZ8+I*mf<49Rfby(cN-ow zJZX5r@P^?7!>5L?4Bs36H2i18VkBfFWu#!FWn^GvW@KaJV&r2KViawZY?N(OYE)y? zWYlFe(P*a8LZf9ytBp1o?K0YLbkyjQ(Ji9~Mz4%M7=1JPW6WYKWvpdvVeDocWE^Ll zXPsRa*KM4Hj7@1$riIM z7F(>c*l4lcVz0$fi!&CNEpA#ow0LRp!Q#8cUrT07Zc8CcNlST4bxVCqGfM|cPsIMiBdqAodwS>@$McX9Tt{#V84EgHbYs zg4kySvCjx>Uy3o8AI$dhS+BevCkM{pE1NfV~Bmm5c`ZF_8CL$ zGltk_46)A`VxI}bJ`;$2CJ_5fAoiI+>@$JbX9BU$1Y(~F#6A;@$PdX9ls)3}T-d#6B~KeP$5*%pmreLF_Yw*k=Z@ z&kSOpImAA5h<)Y|`^+KsnM3R|huCKhvCkZ0pSeYPW?nI9egvF0%pn$Ui+IoLuM3$TSQ7GMiqEWj4JSb!~bu>f1>Vga_$#R6=hiv`$17YnclT`a&Jbg^&* zS?gi}$q*Lc4B=t{&VVi!t{}M-3$T4DmSFo*EW!4rSc2_Ku>{+fVhOe{#S&~^iY3^- z6icvuDVAXSQY^vtrC37zX9@A2CB%Q05dT?1{AUTVFA-v2BE-H#h<%9=`w}7cB|_{= zgxHq|u`dx~Un0c5M2LNf5c?7#_9a5>ON7{$2(d2-VqX%(z9fi!Nf7&zAoe9e>`Q{! zmjtmd31VLo#J(hmeMu1ek|6daLF`LPEy+kNNh~fcNG$?|tQ({Zb%T_lZU$g0-5_PC z8>9?%gOs6ekTTTGz}zvXAR{p+H7~OmVglG!Hv@1ix4XKh&7PX(+yI3x*0;Ofs~hS zkn++EQeL`2%1bv$dFcizFWn&JrJIqtYYCE1AO)tI5yT23aC&qzg80M;Y6Zk6Mi8Gs z3QRXhf$0V*Fx?;prW>Tdbb}O_Zjb`g&Dh*CAK6Aok?Cd(vC$Y}qcOxrV~CB$5F3pl zHbRO_H%O7`1}QS#AVsDdq{wtLG4;>QOfEtWJ4i|CW&*Ls1Y(T|#2OQbHITB=4N^9` zLCQuqNZIHHDI47&WuqIUY;=Q^jc$-a(ajWMrYY1gh+`q;pBtq7bAyzBZjkcN4O0HO zLCQZjNcrbxX6~PxnvN7BkRs5{4B~Jzh$Utahnqp{GIIqdDl>?ckRs3xQUtm|ia<9= z5$Faf0^J}*pc|wJbb}OuZsy<|@%jOXK%pvxfL+mq$*as=|+#qG18>Gy0gOqu0 zkTTB=Qs%iq$~-qnndb&6^V}e1o*SghbAyz5Zjdt14N~U0LCQQg3y6;(Wu6B%LJ7qnFXmunZ*V9Abv__VoqgAW_BWy7&r|%Lo9cOSndq5+!@@ z%bg*XJ3}mYb_Uy2mROXT2xjG1KpE&3xj_8v0`ap8#99}KwJs3*Tp;$jK)asLxk0RRgV^H+vBwQ!j~m1uH;6rMkl=K4 z1{nvoE;X;PG%+PL7fj|A7bGU9rsjgFl8mC%)PmF^C^s#?vv3m1JZ>7#WF~MPL zB;|nB6s4A>78R$afH^5iIYp^uB}JKudFeT+Nu@bCsU;9&Q}Ro4GV@Z4QwtJ{5=-)n zKva5BVnGI6B&R5~C?~ZbB`v2YzbGd^FR8d7FR`d7zq}x|s3bM7BqP7HI596JH#4ua zB(*p-IX^F@q$sl>C$*p`GZ#X@+yWLUN-aytFHM5VL!4ERlbW1Zlv+}hnwDCWnwOlK zn^=^cT9KTSn46N6lbKhNT2z#ul$e*EUzS<~axd7wC7G!unGlVcdBv$kC5go)pbQBO zI*2!uiV~BvQ%gV#Pl^jm6N^#{OEYtFprYV)L`cGgrK!cB2!uEg#)CKn9J5GFm~2UA zQGQ-#VqS4(dR}TtL1s>JQGR(*Y8k|OkiAJciOJazF4#gaAL`cZoc!|Q)ZEPE{G9x} zqSUhFoc!X{(t?ume3*h_P&9)=w>UXJH#fB?IWsW_lmLp$OY_oU;gFeEmX?!Wo?3*I z3c;y3CBFno2$a-IVNnB0)~M2HnPsV{A|>Vdr~*(;p!iA2FDXe)NiNDSE-op8cmOI3 zb}}esf)jdfYD#8lZf;^pMtOcw3Mhf67G>s@WfrG_a!h7^9+*>-nVVV+X2DY)m{*Wf z3ThD=8JHV_nBX>MW_}SkKwzBI!qUVX7zd;(GcPT*C^H|_7&S7ma01Jth=J=rBLi^# zXJi1b|BMX4^`DV}rAcaOQGRl15hx{ACKr`frDhi8rxhjUCFf)oCFW-4 z7N?dZ7L^vHrk3aD6=$RtrskyN=cT)r7UgGW7L=5x7iDIr6eT8SjrWd3Z z=ckt@7A59oCYBbL6eZ?l6jhexWhN%4rex-1mKCMOm*kfg<>iB0!A1s2<|V0l>8Y7{ zDVd3R@nEYGlaupH^Gfp55>rx&Qj3d=OOvuvlS`bFl2TIBi&9gQQ}aqP(~A;QQk{K3 zc`83GIWZ-%G%wM$GzU~f6oKL&qBJeBC^au5HM68NFVo1-Akm{ZxhS)s!~>i@b5cu6 zQj0v2k_vJX^OCb6Bq;kC6=Zrs6ndr=C1#hD7WqK9IhiFzsX0kWiA8=%NqMEYsYUrk zsmb|8Dd0q3o|>6j1oComaX?a1U{X>LL{|{hkf5Zbf}&K2SSBbd6c^;@6{i-b7L{c} z%7c>BoYaDh{5){MmYJNB8kLlkUz(Sk>YtIDn~@)dDi z(u+%q@^hUab~qy`2W9%)#FEq$sQs=GC2mj(WRfq0U1|WP6G1KkrD#yk2Slf5=BDO= z7%7RMq7}pf2P(KJZe(bj=;jy!5dd|Zj10j;7Dk5PAqyizaQoZH5ZwMYG6c83jSRu< zZzDr+``gG6JY->H2p+O9G6WA<7#V_xEQ}1n4RIqw@Q{U(A$Z8b$PheMVPps%t1vPI zH^hw$!3}XELsO&tywtSJw33XH@_aB|3@$7nOi(63;95JqMh#8JhW z6<~ql)Uwn(Fe??bW)#fI168&lmzTh#a^R{;%2Pr0Do7UWFK{#6$PnC2H!?H_H8PD1 z%|VS!BSUbb-N+E!Xg4whH`kl+Tl%8d-ct#TtnaI4(N5Zo#^G6c8E zjSRu9aw9`kK48g5(BSUbj+{h5zDmO9&x5|wS!L4#5LvX9y$PnBr zH!=jb%8d*i&Ed{SDozEnGxGDZ;a&i7!0rc!9k`utWC(7j8ySMz=|+Z*t{`t!Wu{jp zrh^RtD+RaLjSRu$o++H^_bTUa!EGo)OOiwLJ1!YCBuHw|(#JrNs~fe0gqM+@3mX40GE9O5Fq%4uB8aV^9)yvh6Q~DaWatEmTqj86I)fUyMuyIyMy`<| zxDjt;2yVn18G;+}MuyIyMy`<|xGisF2yV+88G_sLMuyr0#Xh!E)~>; z1GjjJOOrE7G7?Mj@=HJ~(i1cDiu23!Qj3an6LWG>^9u@!@>5EaOOo@UjAC#TDlL3Y3*!1a9XS zrzV4}EX@Nq3`;=$ZEy!5y(lpU+7|#ZONt6|K;f2O3f2Y^DlRQb0~rBgm*vAXCFhjG zM3ZyML3-d^G=51&YEf!heo<;tYI0&}F(|MyLBS6Vg_QgfaM-6-B&QY^r>ExUB0?x5 zKQ|vV2AY{%oSB=MlUP)gS_Wn1Bvw^|8n(rmd8y?^sfi^S&=}3jFM%on8(Wf@lLAhN zMv$7{$iyT$KQFzgG&Qdz5tJM%K*M{5do)kBk*{fk&%fLC>K;2Z`M0CPau9Nb$0i-HL- z7nH&>^T2!%6U<1?F9P+B^Yc=`SR4O??ujMlfL(5yoRXRb)eB8hAcq%1 zdt+duV4er{-^xIPdC8E#g0wh{%#3qVi;KZ3(?Nlp3hL-VjfXTmj35mUBS^!;2-5H{ zf;2pgAPo;ANW;Sj((o{XG(3#JgMdax;6XqmBk&-gkr8+h(8vfp2xw#k9t1Qp0uKTj z8G#1@jf}v9fJR2(K|muT@F1X(5qJ>L$Ot?LXk-LwT^K=H7e3ms=x|TiwaWnQZkc4^G}JzpuvI6{JfGPP>M<{Ni9w+$t+IG zOogOM@aUqEk)vZ;eo=C2YDEdS-)Q9M49cNkl@MQmhZv2F9Kp+)i;5ESvNJN%Gg84U z*w_jrf23Q=49q3rl*3#EH@EUj^<{9s)elF#FC=S z3Q&Pnl$@ATkzbUTn3s~5Uj#M)T+SvIC4)H-gL6{z5{oLKB`QcMf(O!DR0%c;TE>Eu zg3DQOTsWJ63b@3Q3?EsjrxGzh>Rbu$M^l_h~l*SySPaI?h7%_%Rl7@Py*VT}cdYKV1i zVC#&*Q*B1Z;HfqvV*?{l1y_`x1M2#hWagED#^_QCit<5Y1es-^LCxX&cMfv4<#W|%#NvS#c?GXoRw@CB$E1vj}t43L-2z>86g%)pCLjLZzcO#(9maO2m^0NnUB1J9xw znSp0fjm*Has77W6V3(VLXHkvJz_X}EX5d*=BQr=GnnB{w3=)TCkT^7h#G#p?gdMM3=+X+kO($|M6ekog3Z9|RgBEQ)2~Km;OSQ*Gw^y9BQr?Eni+wU zm6;JZ4$Z*RuSRCz=~p8&@bs&Z8F>2D$P7IFYGekUel;=!Prn+Ofu~=M%)skajLg96 zRgBDxEsJwAb5he1lT$PE%HZtcQgBgV2A+R4G6T=Q8krfprKRR3=75v6nTa8Y=~Y^g zS&~{53?9D%hmwh@Uur=~XQgNaB_Z8L1un%VtQstNn%lEX|Z!+0ceP}D9;fz2A)}39FSR@45|Liz!R}X zX5fifBQx+stdSWcd7FV3sTi3-+HGd0ps5riGw`&mk(nvD5HthN&Kj9PGJ=_@TX8{R zN@_-GVo^zPX=VvHI#V(ebHQyuC_g!;GzqK>t^%wcstha()0&ZAQd|IPFM#X)lGGwl zJ21Dj7}R$_W2PkKB&8~s!~BS6Cee}X*r1{d7#it%qh-D1Pzd98ks>Fb7qjn zoEfAsX9j7^nL!$JW{}358Kg01W)ALDnn4SwidsuY55w z1Fw8BG6S!CF){AT(^I~KUDXq;RrL{Svv^Iy7*5;7X+8k0^n?p)#b4Y1z4ykd?AvLZ!q@*^7 z)VSv0#V+m_x?l%^@9dbMS%~BXjW5K_heUf*2!n$Z)*5 z2_!rrO$l?zsJuCNd5n=cczKMGIiyrJhm^|ZkW$$kQYxE6N@a6Msca4@mCYfgvN@zw zHiwkT=8#g^9K3YU$Q;rxF^9BE%pvU(b4a_y9MUc^hqOz~&CHSP}Ie7h`kvVw%ppiLv{h*OKc>SP}Ie7h`kvXKOHHQ?n=8&S+98%PpLyB5+ zNKtDJDQe9jMXfocs5OTawdRn1vpJ;SY!2x+n?w4|=8%4~Ii%lg4k;=GX$~ni%^}66Iiz=N4(T16Lwd*NklwL5q<3r%=^dLxicND! zv1tw|Hq9Z$ra7e8G=~(M=8$6398zqWLyApvNU>=S8MHTt4BDGRI>hFXL3?w^puIU{ z(B2#}Xm1V~v^R$g+M7cL?ad*B_U4d5dvnO3y*YR(jgdKcDUFdiq_b-dUP@zR4jH01 zhYZo1Lx$+hA^lu)@KPEhb4Vd-4jGy^hYZb|Lx$$f!Aoh3%$>jk(B|M3HAd#(6*WfY z;1xAS=HL}IM&{rZHAd#(6*WfYPT;14Ib`7795V2335l^JNTx`FWQrtkrbtTzkK(4K zr6s1vC+DQ5LCE<0l9I%-#Q4OV5~w&#Bq_1DI1#*}Fg_{8DYYaqJ}JdLF*g@PgGXuO zlTx5-0ppWWq96)f!OB8FD;eXHQb0@U;*(Om6AKDJx_lCIQ$VzDX?#+OABc#^j895| zE{cm!N(lgQK}%VZQou$)s{Ht*l;F&Cu)QIPAYGx50br1Z43G|IFd0w`VuP2d#V4iY zftnTZNhyh-C6Vz-DM=9br9(ZC0vcwHPfCG~CC4YFR6!I#NYF4O$fck;(Dt1t4xwMt*!!N*ZWHq8RFK7+nH(e<|Gk1z?vagUJH0 z`}0A|>_8?VjhPpLrlS*c5YB~%3)r1-9@v?1UO`4CoL!WW4`&x3gdwq;SptsT%o0fK zWQ0!)wfTJcpvjiNwnI(|e%`Ab$Zf1#ZX?$jhABc#^jL$5A z#cpOv0Ei2UU8oUI7iE?}VmGq{6uX%vu-MHk3CIBH0L5-*2`F|!hNY$_#%Gp5VmGq{ z61$lt;MmP9fy8cR2_$wiOJK2^SptsT%o0%SW|n|MEk3gZ9J`q%;9!f-EP=#sW=Sr{ z?Rg-g0%Sidb~8%~KwMDlW|knuZe|H2c0t_)kS18{W|n|rH?ss3yO|}R*v%|~#BL_a zj0h}t;m(DJ3)r1-9@v?19wK%#OAxUOawA*`BzBWB!Lgf^35ngLOlaQCgvD-BCOCGJ zGC{GMlnII5q)c$^CS^imHz^YmyGfa#yqgKiyP2T8n+c2Eq)brW%>>0R)Cj0Ek}@H& zo0JKP-K0!d>?UP`VmB!h6uU{8px8~ygy!8$NbDwMLSi>56CArqnUL5`%7nyjQYI{R zlQO}vo0JKP-K0!#sKqB`f@3!cw3rZ7_+&z2Hz^a8cQZkGHxrb1GhwltlnKhanV{HB z%0!A?(0CHK?8yY@-Aq{QCS`(RHz^YoyGfvhjPXgCkl0Pig!WNVK=WO&*o8Y69xh;a z!gy7Rf#Ms?fJ8Z%366Cz z3li~QCM52`OyAOYFzpAWBQha;SULbp20#TtsR1kiwgBpKumB`YfSI6V0cOBb2ABg% z9AE}0eSjIDBm!nYQVEy|Nhn|@B&~p%;N${kK~fBu2}v|yCM?~6IpCxNW`R-+hZ zLJAv@Ahf&zGZASKUh06w;b{?E_JD=JNd+9vkP-+ih)9djatJI6O^cw?2+W73MR=J6 z7KElnaLEJ~09%0MQE2%DkxPWcBfOLXi^9?(xU7PR6hopET4I645NQ!wZh=MNX%Sj_ zfdvt15nhIY#o=iYT#|u>!0`kL4tQFG7HMF4L|OzDY+xa1S_BnuU_NqM1Ql{%L0DQu zF6zJ%h_nbU@W5ioX%SrPfkk0y5nT9zMG$EbYY_-m16qHaVxA0Id|;Sjo(yXAf@m|S zIB0|j#0SlFfoMw?Xuu_MyX)sD?TX+Y)TT;lqB#H zsQ9EL&;qFVq$IE@#h^7^@x{d;Q;H$Wz~YOG!KM@!gH0(e2ANV^3^Ap+7-C9sG1!#i zVyG!lPk{mo>@Nrdw2&H{1X7?T!aN9;hL{WYCRhw+I?S_RL9iXU`SJNhnIJzySRfZe zSzv1*Y_L60CMYB!Opr}b7C1y9Y=~VDHrO{1rXf|27=+~4V(_G4sxJH}K?cTl#tz0# z#xBNg#vaCA#y-Y=#tDoQ87DDLW}Lz}m2n#5bjBHsGZ|+w&Ssp$IG1rA<9x;ij0+hT zF)n6Y!nl-i8RK%s6^ttxS23<;T*J7QaUJ7&#tn=c88%Dz<7>t@jBgp= zF}`Q~!1$5z6XR#bFN|LqzcGGi{K5E>@fYK7#y^aI8UHcMW>R5NWm02OXVPHOWYS{NX3}BOWzu8PXEIB_3=@ipxrZY@una(ktXS%?2k?9iC zWu_}kSDCIcU1z$%bd%{8(`}|ZOm~^?G2Lf+!1R#m5z}L)CrnS7o-sXVdcpLP=@rv! zrZ-G)ncgwIXZpbOk?9lDXQnSqUzxr!eP{Z?^poiq({H9fOn;gFG5u#|U}j`yVrFJ$ zVP<7!V`gXOVCH1zV&-P%VdiD#W9DZTU>0N+Visl=VHRZ;V-{zYV3uT-VwPr>VU}f< zW0q%DU{+*SVpe8WVOC{UV^(L@VAf>TV%BEXVb*2VW7cOjU^ZknVm4+rVK!wpV>V~D zV76qoVzy?sVYX$qW432@V0L7*V#sIcWOib9W_Dq4WZ+;ZWp-uAWGH7SV|HUGU?^lL zVRmPD$MBY+lG%gVli7>eo7soim)VcmpE-b`iaC%uh&h-!ggKNsj5(Y+f+3qBhdGit ziaDA&hB=lwjyaw=fjN;OnK_9$nK^|yl{t+$ojHR!lR1kyn>mL$mpP9)pSgg!khzGt zn7M?xl(~$#oVkLzlDUexnz@F#mbs3(p1Fa!kCFmH8U;b>3mT;B`mPnQ;mS~n3mROcJmUxx~mPD2$mSmO`mQymSUC?mQt27mU5N~mP(c?mTHz7mRgoNmU@;3mPVE)mS&a~mR6QF zmUfm7mQI!~mTs0FmR^=VmVTBAEE8EKu}o%}!ZMX*8q0K+87wndX0gm>nZq)dWgg3X zmIW*eSr)M@W?90rlw}#qa+Vbg%UD*jtYTTsvW8_X%Q}|zEE`xhvTS16%(8`LE6X;P z?JPT3cCzeZ+0C+tWiQJnt}|ZnE5Bxy^EiQ?<_x9ezN>x`OWf&61zCkyg;_;dMOnpI#aSg-C0V6d zrCDWIWm)A|s2Em^HttyyhYZCUMD?O7dI9a)`NompL2U0K~&-B~?YJz2e2y;*%&eOdij z{aFK816hMugIPmZLs`RE!&xI(BUz(Zqgi8EV_D-^<5?3}6Iqj3lUY+(Q(4nk(^)fE zGg-4(vsrUkb6N9P^H~d63t5X;i&;xpOIgcU%ULT}D_N^pt66JUYgy}9>scFE8(EuJ zn^{{}TUpy!+gUqUJ6XF}yIFf!ds+Kf`&lQjPGp_LI+=9}>r~chtkYR%u+C(i#X6gH z4(nXjd93qU7qBj5UBtSWbqVWI)@7{ASy!;GWL?F&nsp89TGn-}>sdFjZe-oWx|wwg z>sHontlL?4uN@WIe@tn)M9p zS=Mu`=UFeXUSz$*dYSbK>s8iktk+p@u-;_7#d@3d4(nakd#v|aAFw`TeZ=~h^$F`! z)@Q8GSzoZeWPQc@n)MCqTh@21?^!>veq{Z``kD0$>sQuqtlwFGu>NHI#rm7|59?pn zf2{vOC)u(wu`#o;u(7hSv9YspuyL|+v2nBUu<^3-vGKDBunDpWu?e$@u!*vXv5B)u zut~B>u}QPZu*tH?vB|S3uqm=Bu_?2uu&J`Cv8l6ZuxYYsv1zmEu<5etvFWoJuo1Ru*I^)vBk3`uqCo3u_d#mu%)u4 zv8A(Ruw}Akv1PO6u;sGlvE{QBuobcuu@$qGu$8ivv6Zt`uvM~Eu~oCxu+_5FvDLFR zur;zZu{E=`u(h(av9+^xuywL^v30Zcu=TR_vGub}V4KJ`iET356t<~s)7Yl7&0w3! zHj8aG+Z?vJZ1dRWvn^m-$hL@WG20ThrEJUCmb0y3TgkSHZ8h5(wzX{Q*w(XcVB5&H ziET667PhTy+t{|V?O@xuvmIbN$aaYBFxwHfqio06jpivpryY$o7cs zG20Wir)SZ2#E)voo+WvNN$Wv$L?Xva_+XvvaU>vU9O>v-7a?vh%U?vkR~bvJ0^bvx~5c zvWu~cvrDi`vP-c`v&*o{vdgi{vn#MGvMaGGv#YSHva7MHvum(xvTLzxv+JQ(aWHeRaIkW)ajaL97VamaHha42#paVT@BaHw*qaj0`>aAa5!=}aX53haJX`~ zakz7MaCmZfad>n1aQJfgarkota0GG$aRhUOaD;M%afEY3a71!MaYS>(aKv)Nal~^Z za3pdhaU^r3aHMjiainu(aAb01ab$DkaO862apZFpa1?SBaTIfuaFlYCag=jZa8zaWr$ZaI|u?akO)EaCCBXaddO^aP)HYarARc;F!oUiDNRy z6ppDJ(>SJc%;1>GF^gk1#~hBi9P>Elb1dLk$gzlHF~<^)r5wvRmUFD&Sjn-9V>QPb zjF8pFW)o`Fq>flY;h&5VI9m4WRx1G^ssdkO=4B?J2j1`Y=X zj#~_zS`3_444m~0oaY(1;uyG&F>nhra3?Wv?`GgpX5hKYz#GZHTh73Hl7Y{hfvD3L1-?6 z&}Igq!wf<%8H5WMgfBCQG%$$VVh{~t5Y1r_?O+gNV-Ra#5WC1AuFoL8g+U^XLE<%o zBnN|J6oX_AgJeB}JSGEGIKq z$uU@MV6e7eus*|J6VG6Ckik}n!FB?J?LP*)3I@BA4E9nC_Sp>f&lns$7#w;T9F8+M z@-a9zGdP}PaB^U9>S1tpWN?1R;8Muo@|D3gfWh?~gIfrL+jjFJcJ3$q*vQ5aP}dGKC@J2}39kL#R1JXc0r`T87Y<3}Gq^VI>S< z#~8w1GlUB=gnKZA*D-`|We9)45Mja)(Zmq3lp*2-L&Q&pNG*oQXokpchRCZ7Q3?!E zUJOyi3{e{xqTVt@YcWKpF+@*ch(62^!^{xl%n;Me5Oatj<~u{IE<m@8Ip||lJgmo4>6>OGo*wwq)cTakolb<%akFji6QGeL$(YoFxo7?-_C}7;>8!a!)ekX)xq#C@Eqn+0RhQ!%!N|P`ZPmOq!uA zhM{a0L)jaKay^FfI)?I#3>6j(74ZxeZ44C~7%FZtRI)HsN-$KqF;qq|RMs(6ZeytY zz)&T@P!+&X)xl78ouOKgq1v0Fx|pGQ4ny@3hU(`GHCzleb__L@3^j8YYA!L<@-Wmo zG1N|EsNKj=dzqp3D?^(7ukLeIrB1 zM21dJhR$w=&Xo+E=NUR5GIaiD=u&0qa%AW#W$0SP(9O=!t;Nv2fuZ{VL-$#R?pqAq z{~3Br8G3vfdZHM5HZk;aGW2>d^wu);Zer-Y%h2b@(AUV&caWj)3PZmZL%$_8f zJ463%hW_Ua6L}aW8Zb;uVwl*+F!3zI#3u}s1Q{j;F-$68m^7VX(kzBaa~UQrV3@R+ zVbU^&Nh=v99b=gEoMF;`hRGrflT8>VyD>~IVwgOMVe%4&$=etvpJ$kShhg$dhAC+b zQ<@p3Ol6qzfnlm3!&G&Ksn!frqZy{=GEAMpFm(sR)DsL-Z!k=K&oK25!!%BYX?hIP z+!>~&Fid;RFrAHI`e}w4OBiM*G0gnHFzW!r>{f<3CJb{c8Ri8t%y(m$zno!#BEy0m z3=5+f7T#c3beCaq62lS^h9%1xmRd6`ea5gXjbYgvhUKprR){jJSkJIhnPKHNhE+)n ztL8AQ=4M#EoMBBr!&+X3wR0HOX)vt2&9Fg;VZ#lEjV~ECwK8m0XV`p}Vao!Btt%L| z$un#_&#*n0VTUBcj>QZ+qZoFXFzi-f*yGKxSBGJr4a2@)4Ey&n90*}Jn9p!VHaT#R72q{MJpn&EN>!{r4Gmv=K^E_m(i+JHl}93d6lG4EGfn?nf}( zFJ`#k%y54m!~Mey4+Iz<=rcTsWO#6#;lWFWhl&gj%NQOmWq7!i;o&oeM@9^frZGG^ z%<$Nk;c*Sa<6{g@SQ(zgGCVoT@U)uY=_`h3i44#67@p5zc>a~)MIOV8X$&td8D7OQ zyjsEVY8%6={S2?pFuc0T@S2C=wJ5`DRfg9t46j2OUY9Yv-pKI!EyEi(hBpfs-rQ$+ zE6MOSoZ;<6hPS5~-mx;gGiP|0!0>J&!@J!K@6I#4=Vy2y!0>(z!v`jY52Xwr4l{iC z#PCs(;bQ>9#|DOv8yG%bW%&4+;gbu)r>P8|HZpvA%<$Qf;qxMf&l?y%?_>CUh2irn zhR@#_zOXQSkz)8_!0^S5;Y&Efmpq0qYZ$&fW%#Ph@HK|vYazqePKK`+8NTT-e5+^p zww~eJb%yV34Bzz_z85ikU&rwMF~bibh94dbKgtO_s!_Nl{ztkCiX`HU=Y8ChK!Sz{Pk?=rHPGqO1`vQ1@Vd&hUCkwc!5!;_IChLIzMk>esGCnqE41V%0iMlKyjE;mN5IgDKQ7`Z+& za!WCCTQPFiFmg9Da`!NDA7bRb#>nH$$dk>;vzU>$l96v4qrgf=!9YgA&x}G-8HKho z3O!&H`pzh<#VG8;D4feEyo^zVkx@jHQACSTWFDi)V@6R4Mp0cxQBOwENsMBwjAB}h zVwH^I%#7j+jN(zl_pajM8C@(iM!-s~Kf>Gs>1S%6?;%vtpDBWt3aXD0hNU?k1z$TSj>vMtN;U z`2t4yt&H-g808-_Dikm(JYZB*WK>LIRNTU-#L1|n#;D}VsI;F^*^*J&pHVrLQTa5Z ziaDc-J)?>{qe?iVN*bd|BcsYJMwS1Jsws@BrHrcWjH*u=)w~$h5*XF07}Z`gs;4rl zH!`YkV^n{}sKL&tVa%xE%BT^+sFBa8(Z#4Sn^9vuqoyFEW+bEL1x773MlBykty)H{ z6O39v8MPG{wWAreI~lbvF>3#3)RAG-v1HTT1LY+j7HjwMuCh*NsLB?j7Db}jRhEu-58B)8I9*K8t-N_zR769 z!DwQ_Xwt`M@`BOSn9;PJ(exannGBoJ;# zGMYbRw8&<(IK*fv#b{~9Xc^3CS;%NPfzfgmqvbJ1D-K30FGj1yj8?xGt)&>PZ5XXH z8Lg)=THj~1(PXqKW3-vhXtRUSmYLC3h0(Tx(e@yt9XF$01fyLgqupUfyH||%&W!dM zjP}zR?RPWU-)FS{#^@l)=wQg`;LYfe%IHwd=rDuPVJ)M>VMd3?j1IpT9m^OU*E2f4 zVRVvbbZTUD`oZYz&gjC%=pw`DQp@P-!sr^$=vu(&+Q8^Khtc&8qnk9Nn>(Xh8Kc{3 zMz=?d?lFw+rx?9YGkU*Z^kHT6sblnc#^`I$=sSzi_Xne2Fr(ipM*ng~{~3(_ml*?C z7z5lH1L_z9HZTT!XAD$k3@l>|Jj)oQ!5C!C7&L`3=nP}19AoG?#?YUPVH%8K)r?_} z7{mD(!xu9~STaWRGe#OQMwu~2zhjKyVvI>(j9JAPbDJ?XkuhG3G2WCh-jOl>8Dqi; z#>95UBzMM?B*rvx#tcoy%nOX!L5$gV8FSV#=7lg8e`G94Wh~jiSUQ)n^fY6c5Mx;# zV}&7OWh-NqD`V9b#_B-E>TJg935?ZS7^|N%)(A4zxG>fQbc5Gzq3}NgNXY4w_ z*u8+UN1w6hDPwODW1k{pzXoIfSH_7PjFaavPKjrn`jK(E3*+=Jj5CE9XEQU-E@PZi z!8kXFaqdpWd0dS1rZUbu!Z_cBasCy?1w4!kq!<_cXIv!3xX77tQ6J-?2aJo&85g%O zF5bboM2vCCZ^or78JFubF0W!-{)chJ6vh=#8CNDVu6)n9%9?T2ZN@dyjB6Vh*RwFL zzr?t~mvKV{ykKneog8#j306tKRjmq7|Zx^KjX)DjGycnKh-gQ7GV6`#Q4R8@ykrcFJBqIIxv2n z&G=26@!J!|?@JkfSTg=(VEpOG_;ViPFMGz{%#42+82=n){HM+MUz~|y7ZY;@6H5dW z>l7w7CnolJOdS49oD-S2e3-Zmn0RWKc(*X|zG33?X5#B+;yb~_&&b4Y#KfP>#D9TF zpq@$K1e2f`lVBK=UaV;kCcqZ|wOyZlF#LqK{zhaVLW0Fv0lCWWth+~pyWRjF-lH9^1 z#m^))pGi8ON%}UEOfr+q3ntk#CfPqsaidU&y4sk4gP5lZGslMlqAd1SXA*Od1!NG@dePDl%y%GHKQ_X>MlH z{KlkZ&7@Vwq_vMp>nD@8HIsG&llC7b9XlqSZYEs^Cf!v`dMZqMy-fNXO!|>b`WKlD z?3fIeF&QQ?8F4ciWiuJoF&XV+GCIj*^p45sBa_h=CSxBaP%4DI$WRbySagE7RkI8Z&ljSWYD`zIFElgHFnXEmTtfw*A zh%wo$WwP~PvJ+;qo5y5#l*!(m$^JQ$gBz2>NhU{MCdZFVPQFY|JDHrGGC2n`Iqza} z@nmvY!Q?uD$@LbKTO*U(btd;4CFwCGtz$|SXG)&Nl)}N3Qp=R` zmnk)uDfK&3T0K+RU#4_hru5HD8J$d->`a*rOj&wNS)EMT`b^nfOxdfLa_pIM>X~wd znR0uXa&Iu@V9Qi6o2gKMsc;rk;TxtRTc)CZreYSR;`vM^W=y4w zOr?{UO1ChT88emLXDW|kD!ThG}-@!CNfoVb|(}ZbE6Q!6Y#xPA>!!+?9)1+voN!ytweP)`h#WZ;m(-bMDDLG72 zo-s|0W16~G((GN#ww;6CzxjZVw$PXG&7ND<^-mhKbU6m zG0n1Knib77tA}aU7N%M6nPxjM%}!&Qy@_d#9Mha2ra5Iyb7nHl6=s?n$uxH#(>y(< zd5KK(4l~X7XPQ5OY5r%X1;tDY&ND4^WLmhIY2gQ^MRH7wnwS>dV_NiwX>ldf;-gGU zq?nczGA(()wA7AiX+P7_8%)a_n3io|S}x4Ad@9olZKf4fOe-0gR^~IUe8RMN(s+Lp+)-Ir56<0#WkMW&tIOgncm?XqCnwUlYs z2d3RpOuKWKcE4xZqsg?Vi)qhiroC!Rdsi{-6Jpwz&9rY5(|!e}{fn9Q|6n>0%XHu} z(?MybgVUG}asSZgv+SPrU3ks+x$_ynPx+A`s=)Po`Fqi8-(4^Nc4g(e*gdOe$#<^R zJ6C_VTwOCu==+)b-;HK3nj>u7@cqmWW6|F&zh&7!es2@~o%H)NtMKLqMK0muhVLca zAoh2??v>x!E4o>Qs~eQKgts>+bMftMT>M>h<@bVf(YuAdpSt^9^uE_4VTK=bkBS!O zmZwBEt~n-miv7FhsfVk(A9kOK-r?RI*_~PC9-O&9bwN20jLFK$|P zr29Kt_oUyfJqjGZXR%HDExJ4OL-)?^<H*fc>n&l7o99jL{ep$^dp@;XsYdp1oCv45|-R}DYQ57!XqK4AnC8DZa-%DSK zs&NThGpKV3Z)?!t60T~{V--mw3i0X1d6n^(!{oNg* zw(NWD_jXaA?~-hrCr(`W-D2Bs27})k<}ttd3p*!lm-{Ytiv9ciIUvK<{Z6i7+tI$N ztviC_w}gH6_207W-I3XC@$DQn?80R_TztQk!099cno1tt|Ly=vB+DOO6-6ja2Pcc= z-)&&2qFj$lc)dOs-*3Oh_1}HYMlb&EyIAP^>gQ(9g}2QA&LFzl_+0Lj?%Ul@w{$=6 zJ{_!s-$T5#5gy3kM6WJg~vJgexLpw{qZ~R z{B^T_aL*EY`%&$_FvItpXQIVTv$o5BuVL@LJayWIevVnc8*A9lwoPvAcIN2*{k^|~ zb^n5W^Y+i;n8jYxS=rUp&Effbo9MDdi%$>}C}%sLiPf`pw&6Bz3=G?cTd+;{HC4S-n@3&s_vhPeYzpq;%G<#Ol%$*Ktb2EFpMSWU7MvoduBIl|IBHVXLK*_URHXgo8!CSyO-Y;<@qePqeRp5H|N8iMx$i9JV;xO@a|QpFljpPi-uA;p^tYI<^=~Hm z?%!PBIRdwqa!j7d>bkplefLq0r?*dk=lFOz)KacHy}mxZk)yPgHNUyCvb&U{aBk`1 z!#gkD|IVWSo#D5h9N+yPOP+~-S8(dy@SFb(>+E$C7Im-d{w}Qa`nP=dZ}#p`&ysA8 z--2hlQ@%3>vDRg@6?bQK{}z2?@Li_+J9qcN(@R%#{C@U5TlCP1?bA+of9L4F5Z~?4 zompR!Tf~uFCK^-_Snt-Y+wHrn`%?F+sSB4b#o!57Vqxn_|DWl>$iBn5y$u7-+H1>N%y{Me&<in%O zroU}}i}+_p$^Q=eE+E=vQIHhY&0*hh;gCGv@ARg{KddKqPZe5p+2z#t(6hN~gc-i; z{)iH_`d)Sii!Aojp2|a!I zT^*Di7ybwn_4v(Ftt#KG|E7F?C&zDv-;S*1C2j2`ogCHQIoQ9Oe@|P|)%{zVwL7w; zHN1nP=6n4t_W1tn3Eg`+z8_@o-nVMPj((0=?7vMSMSokdtM$gt>AuZzdiC-@|4JJ%if1NbGyoEVj7*oC)3gz=rHyJz+bjB&e@p-`}yQwL6ld`?qva z*KhOhX}>wTyQ^6jbxvrXU&8Sn?7B{^@@{<&)$c6T=j0CV6|HD(EwId9ep;^k_RcvU zdO7%hTXoO+p|*x`>g?}w_rJ@{VSX%T&&B6ayXJ?pMAh`rdAi@AYigr~hu!UC7 zJS<=RyJ$9B@4=}vH+OScIjVn`%w{_}cjigC z?-SUz=FZLTj^zj}tTdPVoyHc}-8{MVC`a}0yxDBMOIdqY&Yixoo8x;D+jp+xo3G1v zA1&M;c<|`lrE9wnc5km;?t7QxYIpBs*2=y0Yikd4@Yyvl_+d4R@ki^AFwx&;HEbI@ z=eBlNbT_nBG?#Pe{;t&jp2k|bw{=$c)b0iIJC=b;*`ylwHC=O>m-%sgxB0_4wfno- z4?)&NhvrRK)62p4ovC)=5Bph+KOKIAi8^xe{Z{Q>`9oqg_+-?LKmw?p^$ z9Y6HCr~cvXuKmq9x0`kH&Z(Q;baQ-H@BY2}k4|^v5B2WZ-+5}fS!?6!g!pzgq+L0*3EI_ z)RBifO0K)eKm8u~dtP_@Z?@Upth2Aoo_-Bvo@(QYAHL<^lU4|Ax?%Tf=d+!!>~08u z`|j~0LG(92yLF0RWVb%YZ^;K)-=*$f+;wAXU@vhK>t=6sIUQr5<_x`y;_&F<9g z-M70BF5b3zHHXi$@7(OdZ*OlpvA6p<$9GAyHNU0IU1Nev7FH~kThKjs?&Reh6Bn{h z-7sU?`tG;g8{@mJyF-f;lCwF^vGYA|`Ofy^csb*Do*%x<-(UVT68$Y^p#57;zWX=t zdz;6R(NRYBdCd(I(|0F#a}<>3m1J(oUn762`^(j5pI$3`a6Q@0v3B$FwTntu6v(G^ zr$^Vf=1n%e7PaTY-pBX5zYB7F7uSFFTSSiUcVzdBADT<*X9%4?|2=z-FvAa)?;k`v zU;qBm-Cf7Jt9@E?cQnWE?d;u=Z8c$?9ChCVX0S*1)=lZ&!}0wZ``o?>bLF0~o#|H9~ zJ*9VY|74Eu+~2o-XZyW^bwd5*s_r_D;*zdPxz;*1uyLv@>SqXDIR8EVyfDL$3^9=9 z4O6>!b9~>&-o0n)q&@u{GycTYvF~o51}cF-4vlE54F%caH-kNm~^RH5Bx+gCh0|7=CuSz!i-vplAC(T+3$_?C2>kFU>{btz7>1S@!PWmgevdj=JxDGuR{hn&S+HK!^qwACIhSR21ohy?Xo?lpD zzPCId)V7CMdqh9Q2=|C=z^+B>x%~JC>TIC(Tz>0;lrH&>*|qqQLR8-ZP5-7ji)Md! zxG${#{qv6$QKW{%A2Ie$?K$17!sXLX%<8+*&9Rzo|DvWyc||RDpYHa#%{w@%LAB+S zBU2|H?&f&&Tb3=RzCK*8l1;NaZ+7NY4p29D`O4qytitKvZMs>7*MBdW#RhG!@M-?w zS^hnJ*7wk5LQ|i9pL{=Ow(ytlvwofyebT+we^D^U?C!SjDmC4#?R9lcwcW+tISbso zIeyD1tNoV$?fcyz>V>@Uj|<(mR<2&g@tvQ&dvD)_t)OO0BD`PlTZp};HoGCCo8vdL z@2&5`^7og27yT|J$L9-5h*7J*2hC!PedhZ};Rb3_h4+N?+bodibA_j-#1}%((i-p!W%I9UbWDk7vCPJ755iTAxBC0BmBJO@HGbX^{jE^Lw!dRi?d)of-*?!%qe@%CKy6d+S?sa>xs$th zaddy*$tt|#`{_U2OS-#%h_H6=UNs5SQ2p&)!+xM+VM})uNAK^+tbCDh>q^q)yM+UUO-LF!?x z^oRGqt3MQe^S$Kf9Z}&szq7EF8b~Rm`@8g#p6}+r)4p?bch6=m>22#TU&QfS;kzU2 z{N6Y7yPtD>{LM1kLoPH@RQS!ji4&LIU7hbO*KHkJrPj&8_uB&${Az0$C(nj9n;-s= z61C^z^ZCI8ZaF`Mw4CjJ>;$!(zy9EXHpG735d}5GcKi_9{C&!Np#`VIkH0!D{9?h6 zXi+CF;Tz6e->nyjx^M}Uj8RVTSK3ezJ;g&03Wtm)Bif zla$f1u6kBMcS(0`Q%Mm=Is3FJQ~IZNbIj^(&XfNw#_q8&>Bgx8Yq!p-oLN&{T~{O5 z-8!MZyNRQ^thOMlcK#Z@O>dfvr-HU3wle<$& zORe%a_nTGL+FP}f{I zvtmk4H-}+>m6_Xe>$~!sySFV_xQ3%UxVMXS)wGq8JRzErS)buudS(BSg;y@@ zm{%Gv$M;+FhtTXt^S`U_$X+e}5D!wE-)8NPq~;UHRFUR#*ix^T1HR`z#Wk1p&! z+Py7%Wo&m=cS%EBYRS67nW^0&-AQ@rb{v!2Sy%5`I(Jhy$Mp&JF7m&f*(RB+PCwth zs(aq#l?#`Y&Tn4R&2eJqs;%=HW>i)-)K|)PSN1k{cW|hGPY|8HZNZWy^9!@(x|1ua ztnxV4v3KvRTiV^vk@Q<#v^}7@IHfziyJkt}QAl_!{@%Ow+w`gP8MmMKu6COF%g+a* z?Y*qo^%;%X%^daY{XKo%6S_H;^|j^8|9;0_cC6&U<4f0G-3>eCA(zyhUR;;Ok6wI0<2M*~?-zP2lGGoeo#@Q#otDj*0UhzXn^xgXI8LTZ`tzDhn z9L1dzSIKw(;A`z+&2A`eNoe4xW1rA7p=Uxj$CCcm0(oWjq6O7c3c5M$yzO<&_WNIv z-_X5y)}mD$3G9LEMME<8-o3VR*S>YDax&z9Yp^$z=cUSl6WZSI982CVW1Mw>`TUO^ zqHbJ0OGMqlNl;+f+a=#Q_A-92I>7wB>c=us53a5vQBN+u-=}^EE&eXBJ$v?df!RVc zP6nP7X81nwN1|xeWY)^$;*z*-j@Z0aJ611PzE*yA_sse^-5fKgOrANTX-b`ZRd;=L zQ8!0k&8nsAX06^VzrVZDYhyRZtSQrG?sA@z5$RnTAm5$3rFd-zM<;u7Yf5f+Lbu=X z{Da*b$GUgSUwDjz@Av&5f(YBcd!7tBDax({|AU*3J9dt1uN}&hBEbiXOam;&RWQKE7Ozo70S#le@8_(?T=GZ(dJEO2BCr`epyMA_6H%DDlQ(f(p z=9%(yx@S(G-OaIfL4I~zUQVohTzB!J%FP^nzvq2#-~ROI!TH}6mUk`^nzN_mc<}b` zLPw@A68?B#*AF|5na_COIP-*`J47etRi=FKkI_xf zkcf=%of$tBMW^&m0J&sNe`B`%Z!7lh^p>WyE{^)|8q?V`dYUG6FXi~|$1c2Q!OHcM z=XZ0gm{ArZ|67HbxF@MIYZjOypt3%{}E3kK` zcC@Foae$qj-CN(&y@BJq4EwFTEfI1l-8tpyDfJtR7NmFQb(c4UCvtS}ZRuqdzLQy3 z+LG1?%H`iX7JcWR`hDuOmJLD&4}a$Z6?_rj=ZhA0w`|Vu=BTM}s3~1mv;OL)eXr!d zr?8*O+Yl+2(_PjOm%!0|psj~BtG=`)t%0L~y}x&2_vCJlc@rD6z+OykZp!T7X#B1; zl|8esadP)!j_(2Nt5?pNys(>N&Fu0x`QHufKJB^rkOE=R_eF~s=bZU&bB6i5^-n+1 z04}rNf}(+3e82bq5L)!T`2gdh^WQ}eF@InFW4q|1&{K|bHr**UwFMm0T3A=_S~O#0 zH^+(1QFd-w@xk)N-F1DPOF6n`vbN2un_1n>QC?e9leRni@YSm)Kgshc{#*~?*e132w#&7)cDA;)1ejIN?5^wX?C$98;#m7Tnk{%??k2f4-Am>yT*uKk znYFg6v9+d~qo`%hR{8INXV}37?$*Ys-zQBM+Hw3l`*Gol@0Wkrhz4;9?+E4+u2?f; z>EyLjIQV|g{=V#IuxM6$YnI&aN`>icy;~+tT+_|*U7v03qLA9cvhsZS-?jLX zb+&c2baPa-PhBnl-HN??{iG=ydN`*4)~IJ+*EOZ3yO86zAA4DDdUJUm z*t<7On79#CIB3?hZ|s`f*qy}j+k(A3ue>g&n+dg>^3Ws_Q`GPUNwLHrs=6m%GP(U=$=1q|5lFf$Vr{7YiBH+v|%a--|zW9 zgcf{XKAmyK_oK|;Fa2~A4dLSZt^Pys>i4fR7;k>x%>2FR=P6OM03Sp7Zrd~AN3wIW z1EVThTlz~@7j|=0)>hP%E~{7|zoq;9o?~ZExt)pK(#^4O>AZ!rYUWkR7j+kBH@8<# z4Bnl+`plZ62fOcbJn%nhA;y8|EOZY$*I>SV3!tL>}n z=CJb%aI!e&{Z#(hvGe!ked|Oc>fPKN%O4!sw|Mu73ky6$d%sOrOG9y{dX* zR<}!chJCkN_ixqjk|paqIXZu<9PaM@uEP4d|Ioee7aWHwZ#&3c{VgRL4)XK&nEl^h z94Vh6G-LMn`O}0MzMuTQMl=Fcf&A72)gIs3FAMFx>~~!F#g^~PqLEzNzek7)e~9AZ z`@;lMFkw66?&HkgLmrFHo;h>g!n#={^7-9W)mbqd-N##dSu<*in^WpI>e#3BPVAZ7 z%`vCHF;D)tC3|;PM|%ROaxj_B9@pD6rF$dCcMbNrOE=7wo7O$8a4u-nV$yFT^nCQ2 z12Z2@`fY@in7(U)6H~))J?v3k35n^qs1f~p5;&q8ej9+oU1;OM$iv^QL9u*mlIWw* zvxdsadIoacPW>+3PTdu)m7NtFhuJ2s=vmplqI+4_^6q7eDpoeG=;nBN@ahwJzar77 zS~nlJf@g=1OuKyL%52qOP?Y}G0~w7RrQc0Sic*JeS0bbI_g3^M{hbPm((jgyM*}Z> z&s+VSeSPsvp-cC_yWJNy`(gh5q-ZqP@1!@P!t1vGHmYJj*)hGbJCfsfJ$rY4TWxJa zL1|8XZZ}6t_S&uT-*5d^t7YHaF|(#SmgDyXcAMQ1SL8NyZ<@Vq8OOG5-4^|wtgB`$ zow9r~hj7`7@^uYcx?go)JGS*Z14s2F)@b{LJnwF=ZnMp;-`zOApJ1!`&Kq`8r8~4c zrXoFqgU_gO^$+e-)Asc4**}}{!u{{%kD0&ge!nIf!)5-%U-VBrd(MNb+utQVerNiw z{GH$arMjG7cVK#X6vun^sU6e0rgyLHUOa2 zqkTpj$W{A)NG*rDYT3i@<@cGt_iq)A<$4h=`dxj0_iWb6uFmd`ZjSWkX*=b=|7M@u zHMwgFhFe;?TY6f0IJ&c*G#PJFUN4m-0sd2<)7k6-F9|68IvQMdEAJcniWT}`&up4RRb47(3P%(>$+j5r*H&x~nEtP2u=1@ttq=Z~pHb>__LV z+9-FVyTxI5HwWMM!0uV!4fgcz`7S)CWj|=ufY1H!KKDh~g}=}Hp(Hw^Z*tGHZjPl> zs-xv?*-M*C8nP=mR;04dnlgLRoCzGidDy!{YiokLIBLEZ&0-Jht(n%niQ~H-`;49$ z-P5}db?<1}*~symlf65*p&(8 zn*&=oYQE>sVlV7v&F!t53o?v}eZk~KQ&-O9DBQtX+f>z3)ylDjUHJWXu9V|;a)I3u zg`t6!M>3a%cE@+;*IM{+bYBCt>%K=+WHyAGhF3k*yAThP4eyL_+cF_TGLt6RoflboiI6m3deUQ_LbY#%-z||ab#|Rg*=~8 z_pBena~cl`-M;_b<-YKZ**}y-C-zMR18pRR&u%&{bnX84(ucy| zzZ-vlBpSyh{QI}o_oJdydM0&G>E>8FsWC?W_doW=-o~B=jI=kkdrIfzPL5#q?(l|& zfL4yG?`?D09eV1fb)Vvx%09DgR>xe7lvmwd+g~@4<2C!bti_F6yE(pdJ^#+~T~%KA zdpwu$?*uMLrZ58~KAuyI*Fh^2zW)UjA;c2GWP#c=C{?tXASQD*OsY-#bX=cxJKGK<~4uWDBJ5svrl zb6V!LF2-0I@x!K)|GM0fVI#9L+yU+lVX>Fn#|IPhDUt)-{ArwJp-CUsBh zn%Kqhn~~joWx_GJJ>44@ui4L0H;uKdsH(BFn52hV>3W&XZorRZx+}+&M+{^J@_IpzI ztl#guJAQM`?Pi^FW!9|Y-5lTLx>bK0uvSg3m{5Y@Y)}T7(#i2#_IGM`%@5x0nct-= z`&n!3Te1{E>4@)l-tq~XzGp3YaO=C;5uv@ym)-d8de8fK(M4efh96$v14O$Q)K5>E ze`)`+HQgIH4%lwj`OWoP$z4sZ+jm#u)eesDqOGh4I+rwcS9h0ZWfU|w^knombX#}Z z2Xt$9=S?V?nayF5a)a$e_kyfz-7mVYUhaO@eX3xi=NgV%SEo+tUdrKkSTt*r^~9)d z4uh;2>yKXF^quwb(Y$#1?vj?e#7K_cUccvy9$v9-=av&`zl8(xGh&TvzDuo<<5T=0 zHRF5KitoWQ7|(;I2bsUi{Nxt>F2>#sn|1Q5W8dF4sRcHThdDzf_dWi%ID2<+YjYTA z?#h1#dqjUTeC}$}r2U||D}RJ3V(fgsm434x`BAV^X!%9Axl50KpSE21$#?ZnqH9_< zw(jZXIC8EoJuoxnH>+{ritX~vWrSmrW1YQqCd# z#3j5St2>P&>}LF{?_BTqf9I6z-sinPsGq}nBCGJ7u>S11-FrBWf4BY4b3|_Y%0KLE zDACUM-M4Xn;;bLi2NPF+cbqMB@&5PF```WU3(qGenIB~9uJ38+Y2>idJm0-~BkQcv zh4rht(NlF!cg?)EtsLJ&zSngBu4NU@?{;l%3T)=6`JOY2J#b><^zI8B-&@(Y?p!%% zQ#Z%nCF!p6zoXc@-5cuyTRCdJXUt*`o!UAXq$ryGOX5K*xq$A(;#hxec#P}VuwJ?nel`tMS+ z7+>9Ip8kEV=)K?OY~9X{4Q`+ct8Es$TYuxU?#mqC&7Z6{+QklEt=3kh|+UBf!7cW&>LZjQyEvfR$v=e8Zb9DnjI zJGeUg9<=(q&-(A$(CVz@k?^-EKV(rVF&&TebHAUmW2vdW8$#=%Z`}``&H66Lv1EPe z1UBJ!@m#{+61aYF|Be^!`pp#W=-kZ_6SRJ%{C6()? zoB5rQW9|A}HsNnQ3wsuH&*zxTzNhn@Uw&;-kz9A+#<1Ce9KVy;dRFwV>|Vifyzr8X zO^vUo++wy>6HjbiF)^c5t~;?Ku``k5H!Ir@{d7^`pFQo}ZQbo0_VETLPIX64$aOF1 zncuSj)Yhn8{5=xXN%(F)Tj;|5?@V{sJ$70m{14hq`08{`~N?*QbSWe>mS-*vpdb&2rrAN9X=M8Bu8H+MC6w{~-6HcZxZxjF8(GIu%|5jU)F$FsKC>}V{-|s0Przg+)p$_gUe7U=K3G??^zjZ{r zzxy{#V{L1#Yiw%Hn%q9W`yj{nbJ$w-SbG(}&#}R~4JPbIe(SIi5x#tnepfGUIPzWX zmiMCXLQB49ZJMxLX!*swdp5_vN8DJpeD&SEON9CMe82mBu4r&xMy!A0nb&fC?8la` z+bQ>W)^DjSd2qcVoNu$Hb zt^4NE{aZJ#nzOolWB1(lIn6UUzPtR^VH2KLLxlSwLDt&R(%jh0@!N;3Y(e()lwJ7qX?w-G9-LlD3x+Zn?bv@NP#&2ev&9b0*K zO<(yWj?&4j1yc(q6n1k6$L-N`bT*Lh_F0yCHjBf?v9YPUkb}<~8grI2zdO!mynFw< z7bx<6^ui;rrK!2Oxg~RY!P3&@9KUtHhp}Ebef7!5ZjM>M6Kea}XRqw4lR8sjilh6-%l2N@@T$<-Fi>`x(KDlGYB$Hq8I2M0e+t>V z14|l0LG#V&zjfG&vG;o>G}w^3pnjl)b^m9@T+!kQjdQ!#aePl=C(z}45AE_*u_O0d ze^12PMO_E)65dDd68_F*$I?~(J^`gW_&XiZSB?3t!%lQMEGz2hZ0+LUvxNH>(xtS# z>vCuF65-oFs$k(=Qp}n&t@~*AvhKYT_I2;=e$epsH$%5ycS2%f3bYIOI<6|FCbo{F ziXAiyJhhu+bzgRo{O`%^-I2|u;PB6w#h%`qJE?mc$M?1D!nc<$S+{y)H^<3Y38C`8 zQ`x&CTJykNav#)a;t;-@%mpq!8|Qovf_EH0-`(`kVTrIl1FZ8{)_~D_{F8yV(%s*^ zYvKC!9KU1Oh1WF@5oq75*oE~OR_&N~touy&cLCQYTHV3j@kQ~W9Njlsds&6c8o+%> zXkZGR61slh5tMAcd~b(^BP%4`rcZ91*S($Ndnroqj`*JD?>5qtOE;x+#3Ilz6e z+YO0a!mk@Zy|6@3VZHXd&!V3^%Y{DOUkD0}x=%mCME^9it2C!4%cXWV98c=zD5)(~+zw3d@(|tr(jarxLQDPxw!TUIQ~IZId>8qC zm37&L$rD%haX=iG3|c+I`28B#amDa-)6&w?+|nGq_&4+S@QM{2_rCM6&Rx2C&Z-`c zS-*{H*k^Yw?&>bRcEo+6?D&)@1B46JJ+10bBb>NX6`QMC@k+Tm#YEQYD5*6zg0nLEqUb+ zsg*(p=6-j%yLUWgdho65R5GV<4MlkaEmKDK53 zci!zBzx#gMi~4MjKOwihd&`0o`zM6YE!)z)qyjzn(-Slwr$zCW-F|LC)Zs* zt8#uvQ)@$eLpMiW!IG8o-}Bh3YU|r-x;aW(W^R}N9?t$g|M+hXIpO!Zj!`}(^UD`4 zUNC>5eD{>LIo&fkmaSg6dC#)EP&q&L-vW`ph0?lzGjQyt<0y5^bvwMcarf!bkcmAvswFtTWo&P)Qo~zdn?1>4J|6Rpik(m@C z$JYZ6OX!pZ$KKuqHjzTJ?{C49FbpeZ1( z+q+x;Y0~ZPi`_@&t>4G-JA)Y8`F@+DTeW2W-S?oqU-uf$9v597airoR#@?^{3 ze(!?rh27ufoFD#X@7C#d2uS?Rz%jdpb@%mcD^7Qx>3)zr@wXet?-OjZe)Asi{nX7N z{QE`sl_M)&a%^Y&eM0Pe0$YP^W}08OUw85HxC@IO$Us$qy#iE5PH$%9Hc_y;W5BJGi6&yM`S;j*#(NhnQ&iZq4pF zp?PZeWsdIe-_ZsS_#mZWN^d~X!}E@t!MH?!PtuI|9! z9A!xyGn-g=@VnmM zoy0`YcYpTf1-mNucXNDa{?72?yPiDX?@CbMu&@0YyIg3?lKppG?hAkUE()rJS&?hu zlJ3f??$Yj@O(pxgIQ+rkASe6-xe%1cECf%m3x9b%b>Fq_?-Cq8d?1+>v-VBU{Vgxg zw+CFT9Q`3NlMy_f2rle?M6D40?E$t8*(z-1Z7h3ib^ihR@2{ya@3>{o>ZzNiae&%M zRg1qfZ2S>=LTKWWhB-U$eiyzd{OKG|apv!re%ui4ex7~S?X>}=wRl>sZyW3bdC(kOF8kxDOa!PhLhn1h3q0uRidryx)`pzu> zyP;L|DChKtL&R*&RZzAzPlx2J!lf(8E6Tk#FFoGr!#&RKhC`BM~rAqU3E?Iit^3! zkGof%>UqI&U`g2bB)0B76DREkt@3fIXFt?Ap#?PKpJY>X+mSt`wxU!ny?fHWbdZ76 zUYYoN;`he+Q)YbG^nL1d#_h+y^BiaXUh*SVG(V@Zp|G1Ht7hRYdC-FT9TO&R>gSmL zTdAIXeM@gUXt{v|yX(rdgL0d?m(E$WnWOu6+{8}S4KwCV+&GnE275A zvB2-U1bauXa$0vI>(t)qJrlb*`2O(z5Lx$q{tU)5hnctiREC)GTa7(JhD?1mocV}|&{m};LG+Xp~D&xr`%+r1x6W#ZlpRGHy zv@;K`zorkQpMTGys3Yv%YiISX>*bjG+o+L!WygY!?o5v02zI{TzrQyu`OdK5`^;&J zKq1w;;1W2jzMK6#C_2A-Rs}4;M>ch2jc z&(Zxu1#CfjQ$`cm0`PF`{3$J&^1rXJcc)ahq<3;Oepggwuc)ppmuu;6>1*kq**kT5 z_Y98N_0y|s>g%iXmy|AFv1suMInZinsi}<%zAu_8bQrXz)9r`8XbxL$+r%~U-$U5D z*UW-Ml_5N;e#f$RXO?z=qh%KR!rS%Ttc_)jsW#mleERT3qHXJh_8kAD$pX-lDX;KS>5S%8L1rId)s?hGayS) z8`vlJP6VxdnG0SxYRlf8-q@Jd#nJd(V=8+_Pvd0J+Sow$1xpsrT;I*HVpd6v{BKqE z?$nl+R8Ruan#!Ki(>SerE5~;m_5~{zPG8y0v1xi`h&-SF_qo%)GpzeQdn)50u#<%k zGXI$VeT8UqCuo@oM`i2OHS*stv#*~zb%Wg0-$|3cmmdbQSLK)2CxRAkFWxEt zU5&ka!=%X@L5WhUk$vOT`Xu?^w(RA3rS&=89I5ql56gd7WbfWMapJ~)j;X)Z8rkP` zvCi+BQV&|mD93KSG$vS;-*PwrmB z@m-JoRPKf-v?aSGE$N^f0bQ~S4tc)czQ|742VDwW`XffvlU-*<#JTlL!Ku4OuAqBz za#=S=W!K^h^52`NxgwbFcj*t&nN6UQM(EIj?}_t;8Q%W%5Y=m7Ti4m&G_8T-cRqV} zR!v(5C?D`mWzXp=p8#IO9q~Q&cmJI3?(b7syI0Sjuo6@V@inlo>zvizoyF1f+n$y0 zcMGWg=3UydALZzrF4{G-YkK#z?mY_+9qB&S zz0_}VNJC>)dqa0tckSHV?vn1Zs>XT_zTd9j7ft=nHShbf`QL@6GA=ms@$mb@%-Q+bX8FF1{d(PE=kADZzjROU2997c&#CEWyH9ps z+Hl~^bPm2hjG!pynDu?ljOJNFlcqJS$lLmzX;=1CVFrfp=06=pYq}d|q;!XMr)G3V zcc(2VIFik=sg!kb?X>FdvhKXnip*~J?uK)%%Qza8*n0G4L|*CUfE9Z3$c5fI_PL!a z8kcqN@7}hldsp|`>V?URIh?k!R!nW2(>=F)*@Ahix;J*uFPZJnF}1tnc}q8Idq+om zJE(GB`F+=-AIIh~ewR56nscub{r!O5t*y9DuCRLsJPRzVSr5(vKls@{3TAVjGS>CR@#ivt`gYS>b_s&J%Io5rjuz+#z;e#ufzdQZ(5Ou%FHmR$( zU9PRQy|ul?-?VCGcYSwfPiJoz$NJxKY~Y%4e)sH&v!-(-f$T1jE9!3ET+q!?4O;dtEML(b z+}FjrcE)1RDs9d0qM|W^;GGhsW!#z@i($B z?wZ*#sjT(vtotT8cOZZ_M3{w>Sr z_ZQd7bw{;Ebw_jfY=}D}_nmdlcY#Ope81;_8oA3CfY*D^_#q|wVmez{Z$p3gjP6N2 z6ZFh;49TW2|a9Dp2W^J0^G^L}vy}PBU zxtzoFcNS}9YY4>mE-}AcbzxQ`feOlMY+8EJZ?gGjN5ulvPKmYs8`9d?Ne~(|$FkP78 zyXg--QN8Z(p5GUDPy1cl-Dy1?WYE;9o4Yw)bpLkyy|}yadrdd1&J2{cNON)#v@O#8 zou?jTM_Vb_j)Om>=D(QpU3)&`%<13r=Q4kv{i8y3N#(Q>xuote+p=tq6g0agY?(B7 zJHoCv-K@1`@Xpl2nTuvF;@HEs`_PU(@|7K;snJ;(a%pS}*X=yIyqjajw3ZrqzTaoQ z_bvIsvxRZdwC@hPn7=1|{~?-OmJ}zK+MT;4r<?1v+GHNb zyYD((Q+_9Ox7$qXV4b*X^3>I!QHO4g-}r zWd45c$3M}hncpRTGyi7J{4HUqv+%dVcmD7E3%@J8lJlJ|`rC@_NY8iKyLWqj%Ld6+ zvy`$2b^VsHwCMgWb3|?-J6~(#io7d7Smsx(IKJpR^Btir&%dkgICT8OJmI7RKMs8p z&EOKAbRd&UIO#wZ*Kd#SLZZTx4rFr)^D>0|&=bw!LehT}s{h!I?}|j||LylfM>H4N zfMegYe`i1q*|lTQcdqFmV-gLZ&X{PB$t9d<0CUDfgKREg4u{`jKjK95xP;jnN@CI7 zQ~EvI66B-M9pAOTbJY+ujm1F)Vi;G$?}$iL#~nd(+=Cs5?-dg?jjiD~J2;GlIUK&r z?-R{O_Uq9_<=?Z-L5|Db@!jEG2|?3X92S9kYFxrx4ZjZrp}K7fDA1-JZagHk<$2oo zd%rax5tuLo8gdh6WO4~7%z%a5gc;df!b}eHew-04;1XtZC0@*PgVn_1#d%x%H`0fI7G+s-16n+vCWz82_wnO;D&#-w2 z6~ZQeEWSSlh0ymBh_P9)xXYL?^a+%(gpd5(fUHCD57Q4(P%4swB%<#XP>qYgNho{1 zP%}6Q2_N~n1zDfq@2@|^K>DE*i<*F5Y6Ed zR)nUE?=C&y5J-chi+r$+4j_F^^O3wJ_&fTCq-Z{uuog5Cd>4mU7!L}KviZ|M5!3`u z0>UR4eug63$;$9s@5d@o6fiRsatX6C6mbbNGZceZC0xP=4bUiu#YGgz#OnEzVJ6Po z0WvWh*+heeKeFGifTEX`0T#Vj%;x)TjyPNmR?3(?|IW5UcoPF`ME@c~8kg`!hIB6B zO$-%W!aNP?KURZMAhKRq31jwrIbN;5tADHn#XquMp&^Y+cmrrY zVI#8}mdyc@B9M(b3P}}0TXw`fH+%l(J4h$Y8y6fv205gI#45OiSrmSo zK@8$%fF==SlaLD)v*&X_2?eK{*c^UGf`deuL*YB5I6*cFQJ|Rah=2ZF;GQs&NpMGj zeaE5@{^JoS5y6X+@1EWBDmHxIw&mlz7oUAMR?HLnu<1L`$M16%tyv;0$?)jMEl^HW zdGLMtpDxi#F5yKFspS^Q`Mo3PNLic2`jqMA!sz~T43AICtE!vihzzu$wJ z^bry>ejsxoIrcNcoGEDLa5((F0Wn7eR@{I83^nL0%t`Jbqdx99eDAv|!ladGCh;`< z5&JP4l$`}&CH?pJP?NquGPF0yoMuo_|6K}U&=NF*I2?XA{I~#$8WC72AJ)C#hw>J1 zu3?`{#F17J(>%WDby$)*auq?hBuVDiSvO9sctxD8M1= z5E%iawh|QD3+@XabATyVX!tGvOC1!vk_?cv577rp^|Kal-qCpf#&@pP`@%=zx&#`2 zvp{rd!4f(|8zPB=ted~%@Vy#j+awu&3xItgtk3{W)ewEK^gL_v$sO7Ezw_LyMYV3` zFFjB=X)!#``$+~&A{Pz)z5>V0+1740Aewi@B6gw zdEfVJ|IRk=#pl?0LXS6n{`mdyM`4qzkSgx<_vydOL~FVDzAu8PV+7fpJWr@@$M;OI z%G2N9fB!97%Oz~`SNnT4C^CO2LX>jCQfkCJA#m1t4AyYuz>ge=4n>AP-@k7G#itZB zK7Uw3w6Z{QOTs*8?e-L=vkjtCkm1kI?;Aim;c@!I6QY#~W^4RBA#m;xg6W+G(JRI9 z*XVmaC`%|ZK;!j?4@55;BuB)7t#!Efogb!kCPb?s!=EMJccMh@_q$-NpFzQrF>fiT zN-6_aa&N(UPbB=%g6L&s_~ZD)9TdM<>LH{$?g7}mHz4!!=Pdx4R}N{@RB?wJ*Z z>hFbMZRa78D%7;&)cyYNT*Zrp4>f#03ehA8*Q5i>q`$XLP z6vJR0=ii*mHl=J&0Y~A({jke%xot=MvU|WzgR%!CDrAJP*#H zjodwjW^}H z?VnuHdM-Z8?s-2VK(-~!6KdV@z3!v%lC^=&I(d*0a35?yZQ-8eF(JWaJGBi zk0_W8(a?-11JYmx(O~j>`3_K)bAsl$OJG-nI~oaK8yxO^mjbD`hp1QjE%gSJ&qAPO zXlnPoA08lI7R;M_xbcus2{?~^XZt8Tq2c=_h#oG6SA|8dAj5YPh+cU43DJBM$>vWx4&N)pqFIXJy9U@>ASXbY zdl1dXpm}b|(^^n-q6mv#L5A;}eprKI7G6DmFRxwyBXr)2&oS$TK5eS`&i2FZJDYIE zlOMN1ExT#ofB$(Y+QRic|NAS^RxZBp4ItH^fQ(%))Vkv%RQa?YNwo-7!Wn;^zt06F z{T~7#vS3zML0xQ`Z8rT0&UJq`f2|+e(oZsLNwtZI&(p(_uzKn zV+t@Ek1Dit2_IAF;1X7F_?-jkqe?(q%MdeQ&AEAt*Y9Y3V0Zt_cdljIg^$8bII^If zOZX_r1ObQNfe;hm$rxe;qG<?R4HgHT_#F{uMZFTpS?wP%FmUPeTp4L3Qn`8OB*~`}C&M5TAii?)#`+lT*?vG$l zL123kT#(m-E7~8DpM@th{HOq%ES$*j$L;$rkk|3ov!5GR{4iS#$tPQ$f46?&x_j!5 z?^4f&Pc{5h_+BB}1+bJ?&xKFIwVi6{ z<`O>H(8DEc!tnd&4?|EGLbM^W%9iKfwRYscaNS-0o$J|i;Uf!Rb{UBC>+VV4xlUs@LBQenGKdNAyaF)-kypSzcixe(d*8i7 z$c7w)J42G;_a?B{5t#*I2rRQ~d46ffz89{$_ue~J07gT#cxj_dql&G5nVJQ47)xud#lJcHj9S4YM0s zVPv@vnW&nlK{P8dO#RUcN(b=D>idQ6J3jr;K7@5fM+u(;iDB|%K7KnL?ec!W%wmk3t&h=dwG~k@t03Fqy+K|a5oZ0{z)t=gr z%_Ypm@VoOzm}nmIQ2Tzc{_leKHV~mb?MDn~pdNW-Z8uoI+4Gq1T*6ym<9wIWxP&*Q za|v&$;1cHieeA~tke$f7_CR$#yoW=R;g4$|n;=6CJHKZOfyPIQzH`04FMJC&TA}hg z>z5SBWQeLg-?PDEK=1EOgpUEW{!#)N1MONK`<^Y-_nj*k)Db%K9oqdg_`To8DtKmjR~@7$1HKBKZ&jz*0mG7-X zSN6T^hc3uGNL9M)dp5LX6!D$wyYhD7o3N_+YCD(k%?>VMrQb(?aDjXZ(Xbt^VfVdT zSe5ht-~pKrDSLK+1N%GQcP=MrnR6Ldlm{b$9Q* z&(DRg!qngF<`TZz19DKrk4TUWt-oV7eAh*vBs+Iy!j5@Mrh!sa!-5~ozga~4xr7ZR zz6XEL2ThrIN@Rk#u(atZkALopFnu^roycI5)p!~`NuY=BRqp%{r=>FwH4 zbfxG!S25IN4VcLWZ7{cbv}JM$8??dP>d}_XCCnlKn^$9o4uGQu1ENL@-EsX&?{}^U z(#&TPfX=kBIzZbmsOBSAm*=j8>^OYSA8LUIaUNh+fK9%!2ta$os5T(#((D}vuk5%N z05zY;0Adn=PRFr2Ks(Q<#v@v#kSVz`r1T;J_kuG#4xB-8;0%ug=WH%vMumAlPJ;?x zh6RNnUJ;it!-8TEtAtCKjRV@#Mzt2X5{Asub;1)B4{_GA3H&bn{sUA%Gb+F)b+EXA z@B4o62)8@(tlf{bS1v3OmSTX--7R52oxBrvZ1`dK+g7xni|_jZn0CZO-cNpz_9YCc z(|J-1(D^)J$A+Je|MY=$|B#32MxNVy59$Z~m;o|m5yQ{5$Wv7U4X`ObUPz}?*dgKP znLiW2hN-~~gVg8n`Mw|BP_s~G{Wuz6(|)4R9;mQG!mpe^y&%KBe}fr@JO}u0$KiWF zilBzoAv=th0X7vV0384axlHoU5wKyuyEmj`n-~0%2Qn*(;U~)MAV&jidQcSFHx+J3 z_~rPg3vAZz?s=6PzPoMtjyzXb10Le}Q3o<^3Byl+WT!DTz$OeC8$ff!4MkkSj18bU z;)W6~VF~D9xv+!5Pt`w-U@P{*{Ej?%SOIPg{P+d6!WY>J2?y8=B2xn_v0<~!!QjV{ zzX2dS!ayaRIebn8RBK*wx_@*@$BxtYg_|0_uZ0xFsS4kNezS3j;O^977EOFefFd zz&yv&F!k3oP(jB8N@gseWX1$aW-Q=j1}o?gu0$+FfX>TYak`(h;~*#w8ayzKmk{_} z_45#@Kx2WYF)TLm{cr%M-c{(+G;cr&*CF9o-D6N4FU9cBO5V(I;-6L-n?U^)7ArtMh#^Xc|WfG>8S6$2kGB75hxiORxzNGAu=4BNiq6)q@xz z&;Xm)ftF}|KOVu1!aB19H>n5BB#s8yJP$l^{rChk3j0hC+^jihX7R%3ePG2I-w#NM z2ntu|5F5(O&nuV*7r>0thj@^q0X7c=PhvmXq0YiF69h4f=hsoNQA-T|LXBc-fK3Zw zD_meD86VtuToXf(=tMFf8l4gjuvsF!#TegbP&uNDG74gF#TVQMpRnZfbKxe1A2E>f zD`mp>i0>~zLn5gH-CV*c6M8@;*>Bj82;3Yr+78 zdP&3>3EUh+xdt95Nddd6{5w}F$hd|Mn6r{pV8$i6baM$Osq}CO3kdv%j+(#{BEp-n zG7dCyauRIjr0-l_gw5m-fQ_TTlO)1WM0p4Hx9?*q`$nIGX0y`T_5 zAD`I^)jSof+55mhr8A(h8WZ?f%}++KW_+VHN5E$A$gr#i8{+cepC;H4MflJSY*6o~ z2-q+@gEvQEX6=KSr3E%i5I&LvAKv?MA8Z(&v7AFN!#ZJxse=uZf)DJ#r&WKv1REv= zDJQWE?HquaH5q1>D%dPR*f8p;9rxJ{wcrHI0tK)I1`V(=ApDc+h%ump$8XFR+VZ?>#rIrLmz$U2hsaM8(Fves zsoz!qoCI~bv8+->TAX^}pwO1*Ip0rbfV6WleEe}obRuZ<=)3BlLXd7OgGqd-T`S0l z!PM_ue()aE0a%a9$N<)n^8zhWHG(fv^#UzYWo7tX|HB_N_JC@l(3a=tU}iNlz|1l^ z05i)6wC>gfzV6oNKsFben;_#J=dLWimkQ~=HaftJ!S5uNhTb2c;L#2E^51u$kvmWr z-3tYU5#H6moD55TxPpo%EYm1xPDB{{y9Vaj1|r8dSQ?<#U~8^H*D3x`oWKA0jrok< z3sx|r^c$D_WD}hLUoHw#c98LV;`h_2s(!cqm@hgJuIzj6_w0=MLWjR|312`lO<3cP z^bZM89765=p8Y*LYrfET`Fj{Df0zD{28AuWi~B=yzR=HbP+su)`45~GHvG16LoDm$ z`~CdKqIoYqGk!PR#Qa_Pr;unV7vJxx-5VIci(a{~g!%iP$D&1S#SI1dMQxim%5^XA z>*$`$VeiHku^|7z(RIs@%kdfgm6t zK@pZv$R)g>h)X!3m`iv;2^U|;k3H|ULRI}f@$UUQ;ea3NKR1Jv8WeH~2NZD$8x(U1 z2b6FL_xSzR5S_&JJ6dcq7gA#%I&^u(>3+g@t}0L?yanD1%W{CV@dX)v*Fc9dL2V9j z2M*qM`+8rv3)XHgh3Y`E0W^9D>De4zGUMLV`@*dZFx3bP1R5HDDS$#9GF18*EF@=Rb0ZE2dcS*g&2N+{BZ=Fz|z5u+-+Mvf6w*-EgQUW@4FmSqY%S)ou4wG0e#H_ z-&_8i0j;fso5A zW9JE-z6Uzj`|w9$jqmC|5%dUn=*5-~9Jmc9$h_H*PQ9eMJs78h-1bQ1tiHzoS4y z<_6J_dg09EKe?iHTredMZwV#es=SePfBUVX#llBnW9CM`m;UG#ox=6KR%|MlqK)XP zZdPHfX>eVSZV6@IntQ+bM(M5m#lk0Hx(t5L{m}=~RUsy9G?nW&kB#W7?|Q7lCez_M zzcYYRVD9&Ozw1G+f-e!%__2?rfJ+!HEMTtu0LjQ8Z71Fp-rxEzVX^Q*2bi@Y3_o2! znt2!sxr9YPX_W_*Rz*PTs+1a#v-a;<-Sd93%-akK(xoeod}RL40$#B5-Rk=>P>BkY zKPWWq`|00lAB9gq6$l%seUAqfroWZC=lw|jo*fEKgzrBJulSx1HbOYycOGcgnlBu* z_UU_e;yj^~_rB|a6zqp7(0c*O#YR77L&oBSZh_a=2zz{A4oOcNpaaGr72p{#p%0*S zFv1=`RDb$`Qtt-nfUMDvjiBTNZV|r#w}*v~d^h~54Ql3Uzy_Pp!xA$6a2(X!fGlco zTJe3-c40$?ACf<Y?AOK^iW%a|vJV;DRc@d<#4yVtdQ>e({Z0x2m=a-*A9Uid=GN=Mui*(7`3F((wD> z_v4_{f|SM-8>XR|Vt3!+*2^2Ew@Q&sxdJyuiQ)Hlh$$6f!b%NDiB6eeIv1Z`#0QZ=o!0&DtnWIv3m+AL1?Umb0+6Eu9iU>???*Ps zJS=6lu)+f5d~l&zwSh)GS04O;pZm0k+MG8n%{ ze&=HT9{JN%nF$_zoQ#$po#L$N(+f5MlUZ^CKJ- zpok7JXfGRNcm>=A?qq-&P}Bf3paisVq^O|>)AH{SofGcmgN6WF;Ff19!1QK;EYDN` zjes=#4u#AcLnar&&8{uaFM|qj=zQc!ENfL%7=Ek$054X?mbelA0B045ab1Y;Xhe(^ zVHx8Q0-5cNBcbzsZ?9ed!|!``%6g&h?_5)%OWoG|2(3k0-WTxK;QMh$1n?`w6WpmKQGV>fzgi{ z-?JINPrJwb{pC*&kZw>_@tye&=fgs?_JEBzE2hP`QCL3bma%8m=Q?m`&5?MTzp8(ANo*XpI$Nd9pB%(!Y3^Zg%dMdxtw{gM1};WOiRu}#e1IeuIaoeL7_gb8#)_&?5m zX8>vW&hX=`=sYgI-+>@AHf;K?zlr(#2CzB5!^Gx*B_M`@Wln+3`yD1W7bF8Q^t03xyB-(EOnxx^VW~B}=O2mC7e| zmt|RXc7JEyu(*2@$KsNO#lkGSoGuieQtV!?YD5Sb3a zb!#*RX6JJVFIeI}SEu_o2gh&zh2QzvyT5bJc)sjn-paZo-ETQAv~JB`)6KzWwEEHa zjgN%B$G-?#AbjEb-k)5ei%S<46_Dp{uqFq|lGWX-mS3INt=gWFmbScb zdSbVN`^3cM+1(r^)m4Rsb1E0fuj<~q=6o-Q@PqE(4Cy)D863q6OBXL$xNxzY@P+R6 z=_|XxGo0((nzg!{W6_+sOO{p7DUr|Vj?edK?fyJ_=aMCP>l!w7e?AX!AUq^;y5qAv zIyt((Gpt|Ly^e$Lci{Kn?~54Ed|$-;J^DwY=zLJXe>eGlnDOHGMa&n!n}~j2^j-V+ zqVGDae5-oiF@B%%j`{nE_o9=zAY7Su-?iQ`|8NC~{O$$y*na4PlyScMUjOcU?K|e5 zm%&mG#3pn7egu;L{rtQ4JH~78n7{LXUk5S=Ee}Arzn}B{(63nbeb@5#W#6lo3QfB7 zJ#g{i%in_*-2f57`wx7N`Dr1lYM}m`)plRNx%X$ki@%odp4m0MWA5sPxgATp4|K1Z zxnUc}AGyD3ySjfk{mh8(W;HafZVK$?(CuzJ=iklISs=PAa^d3cP>$d1zG|+SCCj(U zbwAqvX8(!_ebXjR?&lD`f8hJG-UTbdT3 zc>Z(dK~vh5NTaia~1Sr{#-oSdtzNRl8;H_V14O<@tWj z!tFn(CqMi+{WDB7p3UB|q{OFNwR^%jFVL;WR=+vTe#^YBU-4y(}LJoeEI+g8g7 z-~aFean82BM0B+!tc!Ag}*a@7yZu6xZpeU zcM%9?7LoaJ@kh02+J@hSzl(kselJQ(`(E(7@OQ!Q!VPKP3%?hAFZ^A!VZ-l&?}e-X)O zFecw`)#fihcCHjUzwgX<2Cq4H9}Bx~T_{&L;PW%gA`9Jzt4)4%h~-rBv9V|U}))CcilQsH_+RL0o!eGl_@`R^5?w|}>t z@BYp9{mpl_@9#Xie^>r>JKxR9_x;uP%&Rf;zT1KbhpRF3ggJgng^RA_5@uP&CEW1G zRcs}fu)``YVTJG6-+zL{1y+H}!rJc|jNsz&@ee`K--~{0d|$-I_s42d8RPf+KP^O8 zbMgI7`t3RG=j~~XkFPR6{?04<+l>7;&&l#na?{vdW|waSU8mvA-hF!Zstfbn=2oxj zKF#sno4xzO{Ee4pb4+7b@h$(&E&tn`z1w9*%FRlSPIlX>x|D7g4urYii>@+%-}9YU zwEI%sCfhk2z3eyV&e+s_iQ~ID`*&{N`6_Y^?3b$Nr*^w>{Pt$=_O8xxtGrM#JEz+l z%y%nKb*biPVE=S-{&yaEzVG{+zx~(_j^1zHb8bF=yYCE`7Cv;~r`b;n(KTF?{#+Fm zKJ2iTOZW}LpYR`7VM1RR)^hQE4`}+Ph+@EZlW)R}KY0;}Nx1Bf;*V=ET~#QWKxxkc zW@Gk`;|L3L*K!F@_#=1&o(_#>l>HF7%J{wQH1qeiX3^8#-<5y(b4*eW~boj{cjwy1(mkd>8XQ z^jlr-_v{L`8!gj7SBw04&fe`(UuWOSQTko)=enbR*L~Mp$Zk8KaeDWCj-SRXS6jE2 zbpICX{wko zz3V%((Ctn2-w!@_xM))p_!h|5Tcl<)T6=6@lRW6C_%&<*(; zx>s{do;Y!`+{zz`qQVz)3k!4dmKLpAv1G}LRYgm4<@vtv?0EQ-=ON?I@83H_mvjAn z`TZ0qn<4C92DM+f;YS_|B-`-m*Dk1e_oLvQ(8+h-ZQcoc{NQ~iI*Ciz;P?C=mqCn% zACgl+nZ@Y)PEe0rxZy|S4`tD!)bjew?vn1jiTP7F(x8259hzS1 z#j%Y|*kR79C9~Fb&+VSyzNm#`T?4Cd!-?`m?Hju9b#Gg_>N*GC4+f=&jN5vndQ@3W_Qo;o;|yJUia*lX=SrG)Gx92_V-Na>F4;)TgE!Mb8>fo zH^+jxbLP*hoLeT}-rd~Q+{N*muZ*>8VrO4xKgauvtn+70n%X_PdqMTA^6r}MhSr)g z4i|OSj^6g3)*g-p-+5SjruIzg?&p|aH4p45x$f4UmY!w~zTfUW@4idF`>yzoaq7G8 zA@7)vet#r7nd|%Z?=7N}x&Gv_Oy=VI-V3gX7k`g!`5xN}CWSkGRzvFI3BSWYb@77V z;UMY&mV3_|w?|qMLqn{>cV0I{tKuP2%GFqu2B9$BlQ4 zo8K`%{yATCGS^>gkegTkj(him|K0aHNKW{1=m$5<+V4F->_sPY{gVYLF6v%T_U=3H z+3(!%wB8A=eD|H_?024bBJ9GPKgxgffh=?Uqs=mjOW5U)rr0Dd;YHu=zx#`>;}RD6 zZQm&hb;S3{@4lBo+yQFm{scMW_f3%TASZqoeD_`L-FIe)f*)r=?#N`B%mr5VCmO7b z8KnpK=ON$kg+Bxq{;*rfIPvOtldH_%O@0W7uIK9c5hA*Qi|>yv=%A&p=}33B{**_) zv-S7FA6>f`zlWRv9oe%}bR*YqzFnf5xWJ~a{K0aP@q6<==I_lvW{GZw2rmBKwC=n4 z48|FkzUyCL{%-k0Ky(XN}UM0e1Q48@=p%YeK3*9->)-&zyFg%bU#>R-u}J&86jdn zE{X2v`mV>apNsDg=jx-(-y^>76+Hmrd>1^*Jn!dE(Sux|lX#`3fA62!yh&)^@$YQM zg>OvzsVLgtJE?b4H^;obrY!m27VO=rZOxgWvsM+SvuE}-PXgVh?aMB_WA(}z;QO>^ zmdDHgmSgWuY-!8_DU+Mdp4Hbj33LFU4*RL>ji3VnE9yaacJBw>A#nqGYd7ETg}+Tu zBJZ~eBJzGO{H-;U@w?<%=ITCg{oJw+V7G`Q8RgCf^T0lF4rq^knh@S1S2!hMr2kpGHn4OJRxR zCM1n4g(ZiZ;1trFk4PYk(bLE8P^9$ngCCqew*EHS{C&!Np#`VIkH0!D{9=Kbs1uj) z4QHkxP)K0a`FA%_1kFgca9}*mod&dza<9zTb0x8=>5-{@q9v zaku(((1pn>rZZ0aeuDYC)gjTQ>8$0;>KArT>7FvBZ$9W&Zn=8)h24|8m!xo<)N%aH z$Xb!#(ox#UQU7K-dqqpn6#4G{2~(!b;MjlnxISxNdry0Jdv|?XS1IUjd#UN{W!-g? z%U5!keHS}?n{~$Ac~chja7e&as#Ad({Ev z?^P|L9$Z~TqMlrQ-x<4S)GhrUx$L{s()#J&GnWe;y7k@u!ueTmW-Xc{y!^rU9Y3{2 z?{)79o$JCetGo5PZf!SfYe{K$M|Vwk{35?@j^CWW*(`qR{I>nho;3Z$no8*-TuR`?7Qz~q2=E>egrM3T_pVFN9T{XqI)t|htBflSk%ob{G;i6 zX<0XGb5>S&TX$=Bb8me=$KLNM>D@8ErCF;cluaz@k`yZ@QQnzg&^T=(Pd?_J;Jk3H|@0DFx2)O48FzRTbLUJUZx-XFX_1w_B| zcVD(%q6GHdJ!J2dc6+Tc>gM?E0CN8ClHblRf7^X;k;mdZj!w3^sV#H6S95?avDv{EKD_0H_(m_4s6 zKpu39O?_8WSF_Egz{lO+)j7U*eOEZ~LXI!1d*1ijt)I7k2hS$I`}nTv+eZ&GDP>^LIYB{%$K;!qym4S#AlE`L4w#{C(x5{v~qXW!Szm zcQ37;WyG4nu-&h?JE%(FVJBR3mj_wXQ;kpB5-KAdo-Pzs2lYE!Ydoy84&nk}BJGI!l=CXEA z?wsGbnB&vV-wbTR`yDz~cCPMT!m)1hhey-5cZX+nXKr4yc;4EbvAfKb44rnrLl=J?GV`<;_bc;A7EArqszE4$k}8@oDc`f7V?x;a8p^P>{hRj!ub*}Zh- zveg{IbqBWR?8@EO&GF^TyY2GeE0%vCU2WxaUqnWD&ky4tTB6k}a~DT-hju4r2gE@y zF1{066;l%fy4GoO?^I~i`N)HAvGU06OzwhQw4BkC)eGvdtYa76Ghye!3;XA!c*=EK z{AT|B=zRCa?`+>qySrzxCiUd>bzkS`{_ec#J16VB8H*;b?B=-K6A&Q}y1~l3x-+sB ze%o?lU)QScg&f^|-QSZ~uhxE6mP_l-sw=4G*!^AA1JnzboLV+*-uF&WoaD|E`p&rh zhw*mdy6=qN=ZJ#tcmB;#R%74BQSrTg9=lUt*~0Em9N#&=7qScQ`(F5aIp{#z<#)Qf zcUpCKU%cDhz0u%+!3HTcfl%l5Cf%&=zd1zD zTXeGVegE9JuylXoyzdJ)KV4n7@VgagQta-d?-4K7UwV5WeCBtz>hn}&^fh*oin=@b$?guK73~0 z8IB#l53&gdyfI&ruvczl_saRpws8baW1l#+uXk!Uhwzjy3%j>eZtOnWeeh`a#qPc3 zYa-Tj=$~dSo?XABds+A9rOP*Ua|la(+TFc0bDj~$jP8!_GL7A=ZS5VcZD8#c-L>-~ zyFI$2L3aWNFH620&9OI&bw&9s@YTNs`SINx!clKby6fIIZ|118Woz$att_f-tmx** zsatkR{`;nf?0i2g8W+~j`@VJEqxrBPeE;z~D9$C|HxPFO{8IYiBbve`++l$0daE0d zn}qiw?yTzYNaPZp;E@DcVE5<$_aaf@VQJl52{@~L@iIesU7pUApp@^^&=-QVRnzH9w9SgI*!^!u{t z_bztf)Q08LCU5>Ocr-y*uKPEqY0+_nAgzjL!qDw$L= zshH!pLhN@vwpHq@ROdT!%;2YW=;^W-%b#<6*WdYDiEZ+t z$%`j0;`q+HQ;}`=cLUZLn`X@1GMQuEZ<`AC6&)+vyHmQ$T5?;;IC3<8>vd;+V6EHK zxUhQ;$NEjZOXcSMF0WwU(KVxWxdq2^%i6f#dfjo~pHaybcfOYQ@dwFE-_-hep1xu``1(KX1hectMBig z&5Cl@kkXY?PG8yEdRe2ZG8(}39p9II=jZtTrhER*KdjxX zT~oV3a7Opc89b^#ltH3K-K=ZA$9~tGzoYwk_wB?pz8uq+u?AoN&Ai2+`?p>9Z()VZ z-+CND-Q}?tyIH&HyXv~@y6d|edYU+*y31p)b+fMcZT6j8>AQRP>+bK|+YjI4;QKDw zJ@5P7-QT*h$?Z4Zyo;y2d z()kG-^M21_=Udo4@5kG%jB^%)P7%vo%q(K@ebP?`(eB?%e@i)chkTd1+-(JCrL%UM zH8fa&T(M&wyJdgv43H~6e%H9wedxD_P4^u*>wDh8Zr1N?-3u!h{bu4=+}--!?$3$t z*57uEyIB{1XIwPzJ4-hQ-_Pc}ZAHs^%I2Q>{$la>=+#0mF8sLr^#{XsVgCjhR^IR1 zM7I}y`z<2(TcKN1Gv&7>N8)dH)|Tud-6U&{58bD~*Rn3lpVoAyo8!CQcY*J^-!WyU%n#PJGbKA>7}vx_ea@=q%nz-($b)eV-?`~8z> z?&+H2-S;_`&)Kqa(ZY;;xo)%4up)bo+CSSxg-@9=deE*!3GKUCsGdmDNh zy6d}ZyXrdXIewR4Vf&uBL-cp%1~%c}pp_96!FY3;%lExXEI<9ml-xmhb76-K?z@HMN!9Mcvtp{U&j& zjLT>LZQ1&pkb&~wFO+xxmim3+w_JC3 z(s$nOLVs4Bq?>HDS**Ul)oW{_J2@)8N6ll8E}yYnzI%G#%n36%c7EslKC63+HtY0) zNrm0D-9=T+VW6x&nf>?l?^8v8S9dQt{@t^i^|!?q(JSAbuKadl<;$Ai`@L=Jv8~@L zwg_$g@V)k7$wJ{j-<7{hi*_H|uwm~Sj_)(K{tjkaJ$LoIWeYjpZ}n%Jw`}^V?# znt4B&MQ8L(>zUEbv3hE4j6CRS+tAuNpEiz~@1{S@zWzSQF1*KlWx_Ez^xNDy(rUnM zs1K{vzH_j5e+ON@5?+(2 zS^Giwd&3ViP^9J66_r(R>|)!oXzolDOgmV@4HUbiti`4WX}11d9Kj9bKeb*-o5ZW=&11ipX}f7h|Zhd z+9==MRA19w%TYePVcx>ovlnbgUmpFN=Qqo5&fkLe+vBgw9qZn{WcLYaKyx$SrK;zAzp^D`-uFv$g*Kf3 zZgA}5^qogHoLDeJc>jSPbE`$q?cR1mzWaveanQ{ibLK4X-rBu5Z?X@^jPBXrnd`e* zTN_$C>btAD3ufkZ2Y@cU2&8lwDT8rO7Klsp{^q_LjkE0tVuV-Ak{>}Oy;v1NMF#Qx1UDv&#ZcD*;X2&we zHQgN3CQh6t|2>#}TI+-cxuWiA@j2aL-M-uHyE%TR{FeAF|2ye-;&+MPir-Vd>wg#g z&bW4F%cNTQ!tVIr?7!W*IeyE;ewY4k_FenHccbrI^51ieL>p$+&8nU?p=(0-1kli3 z@1#DC->>F~hI$w1bpICa{;jxL^HkVx){Qowy1#38e^K z%gvc}d`l#^V@2TI#MSqKZU$WqXDT?sWgn^PBs(z;7Y9 z9SK+DzKebNuJ&2}ck%C9(bVD<+dp6buKi%$!rV9=&)-Id^1l;)YKjWqor{ztE2h`a zUoZzPNp6e3hAm01JN)#>cj=Gv-|x%-r$N`Mt=~6KYg++w+e(nzww?SgeNy=TkFf7M zM7vwt%bN>0@_w_i)&#W`bk}z0&8%78eWZKqw&&mVO3dm)x~sa2XVon3-q1a7_Q9K= zTk#UZ>l^C3Te~|zaoOM9J?kjPsqf7J-K?>{1={M;0;BCyk_tgL(M;~1-rd*T+tbT& z<@@%|9@f~#yw^X4m&qYrb#UB(&~3zuI?( z?`!7>zxi(YW0~lY#n)fTJ?lOgJoh&v$DHp86>RP04UJXZx!o}<&AJu3O`-&~(El~RA&MYrcl$4eZQm8>VM-o<`r?dEv* zz4qYujoE7Im}EJG1#a?{|Z4 z4!-ZpyXSpBv1Q@f?=c(8*JjOwJ+c>o_PyxKaWw%Zjl2+&Xm9#mR0kKL}nc2@Ttj_)bo zRoU-! Si#UZTC@aOf9Kv-V1`JFTKd+6-%SLZR7u9|Z5d)XZ3@AJRE7yZrEt@v9k z!H;7`^Wqh(by=->-9_CYYvcEKZ|>f_c*`!1G)q?N?B8Os-R|AlYbrN%e`n}^`<-LQ z%j*j+>_7E@qizaokb`feNAZqV$G^+boX^nJGQI)VE2lW#W~%f z-H!fxzj-*Ox3Iqa&VK&rmhQ>j{T@9utee)i zh2CjFQ5;hmSP$xdoahHHLTA!f7gE8{jPgk{>r3ojt%jm-3@KkP357#+08?Li-kP> z9(nHblkU0QbL+aRIQWj+oLW`(?+v1rO|A8H6I!Rq zf8WVAZQ{fk)0-z&$JX?3m;X`1-o3bQ*2IaECeH3#(#g(AHXC zH@S88wmI!_^1rixSA0){DxQdgK3` zTfc*CI?Vdo{_XPLOTJh9&SQ^lZjO@s?kA?kHep8J-0npjKZ@DA7j(>SozC%*t*x%3 zvb%)iPYHW>d0+K}dX7J$Vyb8g+op4TKvOueo`X-LX;)R*52=0MV`d3`Uv;bRrf|&< zuOD2Zn+uku$a!_UcA8%~JOM?#^D&0?N6y-SypNEhSZc z9KWUiOu5$0y7{}_jJ2Cj?7wyX$=-J>e{P0LVhQ+)a8W>Ta3RJ%yut{hjY6-A9q!7Cjs4wmm;GzKGUMXHAVR z&MEJ1?rxscJ-vIuo$ph+*L9zXomARw*sc0oFDH_tv-iaJithpE>el?W2;BMIwrv7y zczsw?cq>PD_0pccZq`+kYU^vVe)D$A|K?4Yk@aRBt8mS4_CH?NKx0xuGY{=NKX3Vi z@2q>fXLrwN?5^srv;4ieyO2Y8Pr;0e)pBRMx1C(SpM&pve*K*9F*Cl`mw%r)i*e?b z?;b~f@Xu!cA^JU0G(Mog*Kaa!q0Bgp)%qbb& z9LDy(*8W?gPsvZ~p58mHpQC*?Yja0aS3@^PT=JSdM|NyHApflUwBnoYE8Pc1`N~m0nblFHf~baUfw`wA}B4KWd_zzpJn=eX#Gl(TDD}-E(R{Ar}5S?RTyU$DfMdEut&8 z&)X!oxqE-@nLG~R%?+h_Wmy*89GTYBcF6PnR%+PrppR3?KzIhNvuUN(JuboDc$8O zyVrLg+5cT>_QC7>zH5Hy{=|{rD%x%FTUg&eo@3j0b=Dc%mt8&Fy}WyVRd*f--}k`A z)upq32%S2*^Vn>mxA(tW-v3_ySh)V}j}xK}vDf}Mvvr45)&+nT=S#Yc2Df)*xp^1E8q0n@;fJ}^Bz_eRvQMAo!&DAG+MA}Vs)JS_lLjRM30=G zyZYIB6+O@D->kpci(F=`ljHl&(!Ku2?G23I8Mpn=+{XOl;E(O1-M_d0-u^>|wS7W+ zUt2fF?`TK?4XOg>bLz>omFVo{*ZMbz(zc?To|n-9KD@ z82{PH+TS*zb7D8gbWqRg$8PrSlau-n^iS!XFtK|o$Mm*|4RXI7*&13~>p_Mbomm$k z|EGfGkHwDzpS!UW)D|1I)ET-4atOIQDt+rtNEFTZ@^bH+s8=(mvQnbli&AKBumXO|Nj5s-HK zm7K5&L)`Z(qVs+SfF?6$S9U9NbpL+(`^1lm-?E@tzO~)cnpekg{PAV)R%xj41Wo${ z%wzZMuUpjpfus9}46ASp!}qU$cFpYW{;>!&ZMba0o{1cM-{VWRJfHR5YvuRwy5)QR{qHJ|z6)#-E^PSm_J^0~yWh-<9OZ;F8*I6LSF3~W=o2n%u;UVLb6EBL zndoZ`Caw9uUyX-(cdY*ZGM~nPWqkk z-R!sN_muAm->tseevkd0_}l8cBI@i(9M?kPGY9PxXp&40hCx4zIn{37W(BuK3;Q zYi|!n(`;7Xl;07*xx0V6bw|z5nUNJ|6PM9##POX)T{Jss`!V_M^Sdt1TeZ@EqkFeE zhj(gKi5y>d_lECNHhkZ@f${T(2OGZYZFsSs`TN74Dx$x)LlWTR*4gq)x@UDvY@N{7 zR|Bfn3)s7hI;vaSTHC5R3c5M!KuHOdOuui1r$_nx?yBCFzJ`fyv%8mYd{0=;KDm2o z*W_-F?=s)nPJZY4F06Ryw~GAttlwUu-5yEt79|T-Ox@mnqWetaj*2CBDz?OdCO&F^ ztB6K?pUAdw>C7dMXNBvTR0WyJ*?r$C+S1To-(AP?r-{A0w6AJn9S5ix(hF{e6tH(M z>X_3uorCYY!f*Ta-#Iq?_zxO@c)R}l%MEWfF#q)V%`G~;bz;3-eRpbeeYbsgz|Mk0 z-*wi0{LbC|-Mjl^&OM*gzNal$t8}Y$TW0%ubNG1YSahp)gBE6Re^*T1s{O|0J4ZLi z#_vjV*UJA6`EDt?>)4_f-QPvKzl#^Yj5+!t;ha|YZ>8?vDxP|3aU5&DuND2B6r}Q- z^*3A4r|-$%la74)&i0-4h|2F|x!=dXuNRF8DADcyE!zECWRdR9fZxpff?sxjm+Ahl zc;U^v?Hu1v|E(6S?`rO9>o(|i-5Pa&Ud#NBh23+z=k(6%U+cRic~2z=-w*EkIp3Eo zT(|!F*;zufwjG-NeaEct=HIyzZ`^n<{O`bb)*rm0KKs(ne;2y{5|j*=bS`gS#Zkl7 z@|z*v+|n*?jxecmC&v_k{m^SNVQbw7aCbGN+ECubOq|jE#G`Pj;_tTw1n>BVZ3}+5EC+a@UbEocxix#bzuRn> zHGlGiZjQwp3KZnC*t@lt`s~l+`1YV-7Hef?ZBtWsMtAG(?i1Zt+g|x>;}HIJZ{yZo z-G{o@*RL#I!eM)yH83$SBQUT2gxfajZryHYi*6&1?%zz`MH;(3IlgBv6Yc)eez9fE z?iFih&g(wdy)&jewAqK8kT2=3?QNORJgI$F_act(X{*@32mao{+Sk_K zIiZ{5yA|8C2@_|4sulU1ZpUZ8Ilg;+XP$rfaQ8Eg?_9rmPW+aU`=0;X9hA!xEGiZ( zo4T$0Sog8|Z6zxnlx~af_Tu<0Ce|I$kWx}nUX|SJ*UeG-y@ze_-0ACHFOSuh`>y}T zN7SVIxBd6otc&{Q_0O8{@VERq{oU~#zkmI>g;@?DdKm@m-3vSBw9eq*yV*VO`|Md$ zHhfK2&av)` z-G@`R8}8;1&SY@joWHVrL-)oN^S5?y>pqsh$c$r7cg=Uz+HTg4>Xw%J?y~OWMG@V8 z-R=q9&K%vp6~5>7cmL+(;QO(-dsfZ7iX}y>z6Y)TZn?T<)_1$DLbK+5x4-+{Zr+|# z-$kd-nQ{M=un^O)j8&o;Y+0>S_R4?X#NNGk?!=9K9J7AA)UdB@U)0te!2y~EYGUe+ zEN;sJwJ~?GFaFM5*Uj3H(-36T%^}<&^Cxhs<}O?uFmkYr9#6 zb!3VhORJr_IcnKbnx^lP|1NuleQWom)=5De)4vx!xy5Wp zyQg(b@#C1$%_`g_)AjoIw(jm4R;Y7?9T|RK`Q9n|r-QxQtf|2Rl=AB4v3vG6fTrtz zOy0)+yn9aNyx&Y5^Sax<+x-deZu@OFubXw=cgA`1o^*47cJZan`k}U_W|q*@+27^v zf0vtc4;))}9)C|0wdWFE{LA%+yQm2l-*?HWWj~eXF@8VzT~74eZ_QcV--~`6WnFoF z=FHuF9P@rBRRvss_b^E3ybAVRopbBA7=SX>Ywh1#c677y?d#s~X+m1As5^ocRZ$>qqw@cJaSA2X=OMg?$h0{vG=}p7pyx!FO?;Zc`4v z?^4}&ejHxUh_vaQ8M57-@4IQ^(jUhj3$6R6H~)Ly`p@gXtA5LzFMRH2{P#(s-xU?U z3;kA=JM!ItUHI&}?xk5hSse4f>-}E#+m=oE-FK$!OZuSIc)#TpRDR3-mNEOz`dw76 z`#bxE7vEV{aeTLB7ryg**>8jSY-{JO-5~efEQn3`-EUQe-$K6?e}{ZGb^dNE|K0Ap z$C>ZZ@_Yw>o6Y}ju;zRFqVGm!-;?GFEt>sq_V*_*Z9WPAXZT_I{jli0W%o~ZzvTGN z_M7SIZ&A(Pi~+wHRE2dO%%$UU5GOJ^D_Z*H#&rU!4&c908)GXQERxYzUe_qK( zj=DzH+4+mgSJkE06&KfKam;FFowIZGqFvn_!v7lXZn61o_**>2OGiGXJ9l=;8jg@> zti@}xXQysvE(&XVp+-TA*=zK3*wx9a{b^t}7K7DxAYe%l+rSwS&- zxO@H&&Gn4y7QemEyzcu8(agn5*|+xZ+q-N|d7<1N{`=qGvR^9NVP{<&?Ig$dod61odOD+G{M`P6>z`yTW?0A+O#_*jt0 zEk6|JFI@Xwd@bYmsUV8^`;TA3qGz~zGDXjFegF9P9?~&m-!Hbz`M!P8tPS5!%@Ufl z_0+8I2WA|f{XP0?_DkXa4&PaRsEB&+&$#rR@9ER;3Ub}^Iv2OCu7krHNnE$+SC_k`~6Lf@74es|@Vf0=djclia^zB3bI_KTRI=^Vd#Rer067nd(wF4w(( z$Nv4xIGSg&x~67CcL#PStuENseYpG3q651Q?OwL8`%w4kq}`TlK+VtdYrk_I?0(&S zFl&p~idhrqOKi;h@V}y+3DZ zTz5csTvE1Gv0uGsw@df$;P2eOO}@K#f9LPMe`4ErevWFkOee?m->lsnzb)Or8+;Fy z=L>E;_q|~CclPy+>mGgoHlO)N%Xbse@+qu!35jls-5l1xS@(Rmm;b@bR`OZz7Feg4 zlG1On-(p{szKh9!>wb25`CE=Z?B6v-=Re-L>}>a|?nNFmqB;0}N3_iSE5%uzO+muI$BL-2vS}r6KV-MTO}l zF&qtD&8@AC95b6IOk{1Z>8$Cl?RMTDccc4r_sI*FpMx6wZU!FyhTRCecrrFv)0a*ljr+>zj5~WopbKY{_cCZe+A=`9cSl!?_2kM#T@1zyMG3Y{^sdc zPBpf73&`~8_Tcc{o^tNP)0>~=x;HoMDA~tR)6P1jbV6BoMR#&lTxLYPXHr&LH%ENd z`aR!ezq@~z`K~Viy!)g5qwam(J6CNv$x%OrwWPeLv7p4#LC#R#G;f%6PHa~HDTVWsoQ!E ztX#BqLH9b2IZacljW&u35xB{r22hx4S`Wwu{*E+gc0c ze#@}^X6-JWRrQpksJrjC@{i@92D0PCx|!Vf_@|tc#{ko87&DV`cv0 z?6CBhFvnwY_vOCxb-#J{@H-2~`Dd)(dB0nHXZX(d^3HFq-%-EyemrPN!?A|ZJ;@|wMV{7e`o#PaQ(a4r5oMzx+k^uH+9!_7uROwbNuf99r4@a_h%c+ zS$n5%n6mi0<`3J4-@4~O6jgRR`e^-Tu+s8(Oo`x7`Ypz4sp{sb-W}N;IInvX2j6e6 zmbq)@d|xn^ao&sX`D>ZKi?@ib>)teX%le6(6T2pLAL-t9Z1X;li^4ooGK;!9y4!m@ zdd{6+d%F8r_lfLP-rdIChTaChg*j$4FI>UeP|#M|UD55eBjQ{)$M=};>qWccN&@1X zJ9;|1JG#re8ymYTx?6i&``U8ya%%FrE4!N}chBPJ-ZcN{mg_t2Y}&G}dujKK%I*vf zKIgVsLd(wiZ)yK7e(chv2f}kM?Gx=zt?R7nYVMxU-BiQzotAv|} z_-yyZ?#&w(b$9o7e>eEe_uk@saI@otZjMQ83l~m*{$1#|?dvxAJvUgpyI0mujh&p* z-Q3qbxBKvx?&IB$e;ds2KE*MSeP&lrTV8r>R?5wg333rGtioqN`;fl#m)~e!HDPL7 zH%BL1QFcs}e7C}h>?xfb!fP^9@{+m}IJ%QJsCVab@P&7OXZZf;J1^r8#|6ya*?;(n ze$QpwzoXS%{`ZVOZ0s4o8GZzIulvpr+THp69P4);*9B2>zmwRa8dmqoS2i^kbZ2qc ztZUvO$M?Ir`#Xcs_sY#xbE>urFZizb!&P){_ksy?yE%4DNhp(VXYXn61|3q7QRChk z&{dY5-B{P%tzKTW{+)~cj&vWQ6?9XWK*xk(`e4zGB z-IneP9Ks8_zt8Dj$lATMYf=6D;_U{7HH%xOHBRsDtFLQG?M~2 zJVHN&x8$r8zHs2j3(?Ak#@y~~j;OiW7v#RL?QZ&`#VY)u8#IB_(%stK-qYRF-P_gE zy|8=csqPsZ!WX(Hc1`G>#PR*pRQ6SKSFe#5ey}okNxt0g^K6OT^(#C3Irx4%bZ`EC zZ}XY&DU9#Gr!ap%@xxE__m=GLWY)7yGn=}TI6%8wk{X)b8aR@_|K7|VHlcE6_eKuo zANNE9V`GB+x5pnlwsZTjqp`aJ<@vr}{K55;PxN=(Z_cbtqH*0E1cVl;5O?zQGN8NYB8SMG}O%qEubEtmzz4@J= zb?w1vlh*ce{Nep$DO%jyGO=nO$8U)r!VTTOnSS4A%~Ee^EbQW_`!a*QsA0-H`R>WR zQ+lUxEV=bvzI&k;>%_|b%I>D_s;0KQE{?kIGVH%WJ8VyU_dW63mzD2q_lEE5*B@Tb zICaC*4W~CeUVmo&*A2`+I=){K{Vq1++0$vQ6YJ_*TN{#c>iy(@|6}_t-J)*QFtK$8 zXj)|T;ss~rf0whTHC0qJw6@mOgQrt8*``gLI9>kxp~t@uu_Fu3Xr0(l(Kv5|d{MBd z{rB}}ewVX#m-bgos^M7j{j%uqzib)Zd95Y&(+;hj2RhQ?AKSX_6%!XtZwSe#2yPu=#yB=%T^p*+D-5h@!AmewF3C`LtL7laSEE-reaUw_BpPQoR zxW4cHaZ~g>sQV7-P=99xIi_m;nf1)y^EZf|lOZrQtX-Usccgk`YJ#T!D-zaf85x}_&sd<_XYcyzt7qzTG!V& zyL(Re#GZ+L6F9zWeV_eX>w7wD+oaA(-IKa!Oqw=jGRHPjLtTDG_kxC{X`nqu*}t`Z zFJvuEsc9zb?B&G9`|ES_!N)L9c}&gW?Vu|@Pe*YA&iZi+tquD9m!iEn<71D^Oj=6K82($oUl zWYXH((%%ZQGyS*L@7b*FEuEkpueB5FC%16$U7dFAd+)XHMc0JZ9SptZ`S6GB1L0>s z_J98&`rBMz;kTlkTDSB5iU%B~Y>R(OeYE|q)6MaH(&_KH-zC2%{1*JK^xIwDu-iG) z+nGc7Q`zIt?(W~;zOU@=zV(pW1&69KYXY<5TXw_Txc0RQ#v5=${9_Z#(=j_^Hj#xBI6~Ipg<55179%`k^8E zyYG+shwkrz->-iU|9$;S_wOk`)VsgWFz;pst%ce6#O6+xN8Zao;V!_kUMaef8U3{`YRShVGWuj=Ii<{^n`j%erTGPH6A% z=xgn6>~8LA>Zs>X`;*Jsl2Kb(*3I!lY@X=#vp2q5fR^l^sNI{l{(jzG&u*>m-(J6Y zevAGVNaXnKKUehIk6zXZOBT)8)Xnj9qrJxO>fZvtoqkKn3*RZIoHkFsySKZyr;p>= zk1e8&4b62OH5>|mdRe>Ly4t$iyPJEP`&&2^*rv>!IA_8_jt@U_S%u&9&FY!nJ+*rh zs8ZwLJKeqE2k(aOq3eYfZ9KT~`|S;d>xKV(Kl>wG^ta}3#otW79ey+3@%k<#zrTCe z>}{(!ni9WTb+b-4QKhCp)Ry}t2a7=FNYm)n| z$yVRp+}hdDQ8%G=I%viHjEPN^^4-nd4IT9yDZf2fGgijU3hw6kZuiGX)V}*W=Z~GN zebc)^PMp#`xpNZ7_3q!Ce|ED9|GB`WeoQniA+a>No5MY0F*T3_;lHcFGv3zkd z$M2KB_ld?WE!o_Cp!+++vCFqN#78ClX6#n&&h0OpTF${Yr)~ZBu=U^V*E6o$czomc z*&EJmWd8o;M~3KctKZtcS$}){X1nM4T|xf4Z}*Mw%zd+aC%5)Dba!-D*JTxkC-{~* zbvtzzt?NG7y==mo$s0Jn|6`lpJ!w+k%>FsOv%6<;d}lB^`&&}(yVvhzQ5Uuei~AOI zFX&#?x}tvZp~B@!-ND^WT}@q0913E;8`$Q*IB@e0$fW3QukOl@^7e9$--+Kz zwya>o&V!qet}9xeBo`N!93=nS@XvM8<_Yanp}4}4HQo^CpJ~dceivkcQ$ea{$9q~l2KPx(allc*idD(YCj^{r-aJv4#!;DFd74qH9U5%X$9Cp9| zu$Cw$h52>2bhq}l^)H$;YhL#Xj+Mnrv*MGJV&r~A{G2Via7Il3$8uc^Yvu+cj4|=UPlzW zt-Ae-96Wu_L@f>M_U;Z!%yr?I)xf%X^THYHyAOBo&)MncreBkj=v16kULV=YQT9D& z0ej?x+Ns?uySFVnc>2L%x788bO*wwEhbBjNyK#KC`z|lK(r0RFcN|A_PG)Ru=K4Ky z-G|q0ySts^!WB{f?`#=|f-WB0wR!H=?u*^Wyu0nXy>k*wKx>mu)h?La^L@dLi_4}i z|6Vpn=!f9d>-52(DAM4(oy}@pa-`e6O-K)D-FPXExn`3?V-s1Vr z95cJ?zw_63vv$E<9-M6n>gX7QnA5TOpx__(ue#Bap z+}f27I;=%tI(u>HglY2K6MAM&oW-&D=y$E|70#@Et>6w!b$LgACrABv`|0e3-8ECI zws8EedmwuHyY`9S+N^xPUAkx0faY?(C(RO?HD}T6?+*8c8Gfk$a2K8RyS|2ff5)QM z?nsW`>RRl<-Hj8Qj&M}}Hk!>gc}M@;Q{5bI6xkw6n!@F>+3dP&`sz<`RCV`i{}2Oh zqmS#)nb5tDE-x-cFzCQay;Q;f`jvry7zbADktIud< zU9od!?}l!U@3P&$rT!=$=>D$y!;kf^<@XxVLtH<)CV|(=d)6+f0-c?5Vdi)J#X_&o zeqRYXW=A;t$E_d9@Do&or~H}vBLku9hji_NnuWD9zfYO@-C!X|*XOgxK}QVSy~3C#hWdGkX9q&36g$2zdyjD{mz z!Wj-nxrD_T{>=TcMD!Aua0A0n=DT43iq|e!v+{e)%R=76YhIq* zHUoTMPVC9dBj11S6V_$e_5D7`Q+fdNBFTi!XZBj|I7#Lfmvx~4QG6hIw|zMb>DY&utqV4@2x+SKvrZneE;-EA7MxX z!=@khU~ih%E~uIPeb&tH`inp|Zh@ZR6!zoi_lcm>c|uXna0=VaCA{L#lpm@{4)6q< zFk#JiwM8o-Ar%7(sSv11x(qupg6qJKXSN{20~Vl|9&lhcC~BL3=zyJO4>5Ss7>VF>mD7*kFEL`xN@6Rfb%|B-$ z$$u}zqC`03`*nybe_caXn+Ng;risEC->-m`3#Y){oCNnB>;{Kls^1w!uX6Ey7lcUt z^!cd;;{S++@{fOHhKhaXhq`nwRO;l%?>PurL9p!iOJF-e`^+mg{rE6X=p!g_er^)= zU~{UQxK92@E_?T-nKN&JMp@TYu)}tK1h5Nl=yt8GacbkJ__2IzJu0KN7vh zB|PUkmvGJvuHTF;qBpttzVCoI{X5I{A9Fu4|FryZQS?tFd$&bBWUu|2dF)OTYG-y| z;`kB3-hFAtjH?ql=KYRl|6TZ}_jmP=-rrH6;h4K^GwQpYIR0p{cdOJ^Sb!EU)`PaI zR6!Oms(*jyw{U~(vquG7oFDHMSEd|_w` zz8iv6ef)j}q3qLbF5xe~Z~WedtnYgwHjNP79~^#MLumidaGOi`BLlJlpBO+!q6qz9 zxXs1)UA1;W3&_DMXEh?6JmF{Lk1)|YT*A43r4wOp$h*zO_nWtN!4HR7j6agW@_gS# z(X{_q4Dw9wkI4wvGms2axXmS;g1^T9y^OYr@M4k-VTo6BHttK zedocpV5u}OFEx;Z@4F?6vw!RZ#mcQ8>ky%F=Qfw{g5UrD@QdE%;`?EZsT>l7HyVC! zKxn!Nihx@vadHPFw15F-((n7-vwnP;&G_U#^He+=s~cvq`}EgO?LNfO{o`eOFDrE0 zG~aL4#>+ogS1>NQ@jdnx^OEnGqQAv%>3wIG`_9#U^gG9bEgZE|SfgFji-Wp347_iD zXa98Q(jEEr-7^}drg2OxWu2Tqw{k%@$I^xK79DooW&WG_H-p}9J$XLEM$nlh-_2(; zE_wL9{66#dSB+TCD(U`l8p}B)8>TcS$^SNBC+dunpzc|aL!fTo|Ly}qA~M2vX8ceR zozXYBXIeMM(ka!^;A5UbYioj`$2^7g)=cZ(#PJ<;%+so!M`x_)=GeY4!&UxwE_-)y zLqiZ~y-~?5_Mo1cIo&5YzE5CZxoyqdo!uNq<`!6h&v*)IZVqgLpYfF2TQ?VEToU_t zu9V|;a)I3ug`t6!M>3a%cE@+;*IM|%&Y+5@$ZQCy;o$qx*u64$!;c3W3YH1Y`Of+M z^G9%bI`6yG_uHc1qi%j@`tG@9UuBNmp9$>Q;nm4re6TP|+U(?B5mZ*K2fJ zcK;T2iBN3e-~(M$2(^@P)_2D5e3!nv!fa;#b^QAyQK+-!z>c;AIr_KUjo%U9Psk&@ zCJ(Arpbm^~pL#(Khx4GWllyy5?7P(I?-t)DZJE}bBbUtnJF-3EH)pqgx6$6;!r!C1 zINaJBqkjkd7Q6C2SMCSf_b|~v0_=*DT=sQ;7wx{g{@r)Q=^P)$&^>drcKZ)s@b1tp zi)t7A2>Q+;{N^X`_nV^M(|>DxSNQEO_nWahIV&A`Tjv+Zw$A0Rjz0S?3YsE)@tgCv z=kJc+GTy&g-}bU@tfJ)H72-Z zVZ~Cp1>JM!PF~J2aUtu}4Kt>#@8-C3$VKHhySKH0d{B4#;^>PUwN0!Gl8*l7_|DbM z@tyhBi|=CKeXdY%fKCb&_|6&wIuYW>j|JaFM1$EgvQruZyE%Td{FXWWz2m#*i|?G@ zRpo_0eHZOMYOq`feE7o`iv-x-B1AAC=GExL^TyT+o;-x<1Zb>B()@LPQm z$8z@XzKy$n3wEn;bpN*0_4_T?%JHN4dy}ZJ?SpGv!n+?_=Mt`c0BN8=lagFUcip6p zg&f~kZ2j)^;yZ`eclF11DclP|FHdb68-*-{dw+E>u$Gh)z~oSat^*9 zJ>3WgGS2?axbM;Si{H6Eeup}f`ByX|Pst^9w@+!G|9yGSmhVp8&$~f3sr{bN)4!m9 z@>YaRpgi?m#p=7y?^ANWSF!y*W%b+Vw+fcr@XxzN6q>%}ws!Y7_m};i(B<`8t@}4e zxBiyjPT!YzwU@UyC3SN^leqj3w(lOIzrV3N&yPLO{jvMP(%a8ha(w)wk2f3$~80->Dq=?7!7ZlGM7bx@|W9X85jA!om05t7c91g70ZF zRxbQ5u;=^4g+jAIJ?igwL7iK1hVORYXCV63D;&Px`KtixRIUK^c2|IUyABNB`F=8h zdqStbR~}~k`RjY7=shmJ-<;o5Q$+7`@qGsiul&AI^a1G7msLMYL?6OLfByPDP4suf z_k^6x?_9r?|1x)f=l-sev*vfi@1*Y$zY|ui`OW=Zv#Rg?!@2vpi`u}LJYed9M9Xsd=r_rdRF z-^IJHbsuur9?tQAJ$7I4E|>1#=G~#c%YI9At99FbXZg+D%~1jkP*j6|#leCd?6i_@ z*5BOScE4HFx_?V_hkh^nZQkwD9UK!I!C}U}egC0r*Sf!pcOU#+2AV(M?!NJz?L#-m zLXex)Ku(54Ff^oAutQ_e=C|u_9;of8(fOUL`@7w5SFo>CSow^;7yT9&J^W(@+jo`U z+-p|-b_E?P!~^AklfmzZH96l^zH@_;f$MKO2nQCg=g!Lc?)uxN`!^4i1J?IF zB5T!emEYW;6+W)t?I0Xhz8_AYg#N4WfhZ_-b{|Ig_^$$1AODmC`xvHk0#-Nwl>@t(mG6fQ*iNa#%)f&6i~i6H{_XmA0xYaibIXM8?%%Ggd`3HdJB$9% zJN(=A=LA^LqK4+*3BO&tVMZT1%zWralIS13!{1$hPJo3bN&x##PuioufFJPF23(qzgHgqE_|5r=fNY) zKbw9Sh;CR~nv_&jk}Ch_2PpVdb5{S3_?`GY;&F^f0?sO1y*Pn%IZ(KYoHjQF&G7_lWO_zazdU=45~8{;m4wM>lI~(V{I| zmM+|YWWZ1E!{3FDF#r7Z{iEoakb_=|b;jm$4SyOz!HVgK#_sOltcSb1zq7JFdOi6& zi+qq+QfWz&oX=^tEejWJ*|M-SNe)tKfIPSI$akT`Afu7XfIp4h-P2g_*F9GB3JLZ4 z&GNeWk=$?A!{1qdG_pgI0_&f|@3o>nY)Peyw!lo4=lgN!yTJJkhZ%o;M9H@r-K@6V zp?WFZsofsmqy4)5yFGtLr*@}whrY1x=FkL%B}Q5Lwwv{2_erJ0-G{mlf4ADXXjjRR z-&TjZ4|kvZa=e@49VqxzP%<^t{owNX_eyvfhPxbOg%yO~cYyLMxY+Ld&i~U<)b@}5 z_lMsZ?0*0M&GB6=rR2Nm@A~g8-!%(2{I)cG^CRx}!`}=?zh7YGQ&>>;qwFx_4~E0c zKP<$4toz*uiV_UJd=KmH{!<25eiEem$3#$lk_5@*zx%qo|ExRQ-TkBNdl=kn3g2B1 z|43W_HvZ)IBt#Lb^1ZLS`^UOqkg31Jpuxw=2UCCYF!RZu@uGj$eeZ+D7Fzid2C|5i z@4}C*hZuiMIKNoRutKTf$_kUP+cmFub%J-vWLD`RlGmPI8F8)k8!~AReQ_-&PEx*IRhyQNr=J*}; zyYP3^??n0E3g6kYbH2y_PWqnoJt=F|Z>itRtlxFN2Y&bbZUSCNZ1vl!=XU}J+`!dm zex_VxUj0<`3|kL)8>iLxgl>-SCf@^pd;Zpy|1I^MIVQh# z-xa<)%5~pcckA$Rj?A^JUT#SS-P#<#W5ByqW4_0HH~ntB-N$E^uUIvL5kY zXVY!L@!RpY!f%D&jv)HG!gmL`?&mAcoH{!vdrHQn431OZYkt@Ie$2#U0l0|+t^uDS zHEk+IpK9I#?T>#y<3)r27O+9I{pRfcSrFXa{kH(AK8I*C|DFJ=*Fn~P7diZ09Td(!qA$3< ztFu5NP~`A;HBiue7ZH8N^;_cmG*L*5KotM{^<6~t@E=3=P2bgjaCLVd{{6dq$&1MD zBE8>D-Pd2_ul(Kmd*b)j-~G#1*uVI0+s&GG!JcP2bhOH+8fA-XaDv`ZUx(KYt+`Z3Q*@ zk29#{Y0{nYTm27jcX#mjf8F^?tRT02uSYd^0odFM(9)FC--W?J^z+wuVbQ~Xz1jV* zoVfba3zQtBLB3rD_3Z?ZZ&~?LzN>+gwinyw<5&H=|9XL9U+Sg(%H_W&d~f?b;dk5e z{Fm3kK@Tx^?$DRBf|9XLn+obRpQC;o2zJ|REvx*jWE$`1{2TK8dd z_IEtl{nHB+yE3}hbMwDX_}%h-!uOW^<+}E)pyq+dq3;@p7!iS+b6xLGO851b;CPt? zj+d48FMgzS+v`F?a)PM8d%y)DK8tR>i&f)|6RA4@t5`%<_$l3ML`Ds=Be*a z{{CUocDw(MO7BkU4u9d&&7lD;(J-z3wFbSl<`u}zf{_gtw*x~QTzPld&?Rp$cvUPu- zgj9yY%g*lKlfcb*qwi@jV}3+`pC<~__x)J#?_*#SA?C7yG(dVhsBQM|lfZ3uXpqi0{z094MStfjVrzEe^B&7GMefZ4RXkLA4*K0|!=(DBFLi zgY4LTnE6*2ct`@d!2yx`C2*J-T@+ltZa&Ps`G#;9KD*n3lSpQ597X5MI zFxz)fvx)1sc=ypiV9S0u{FD;?J?+m0T{e`0u$wjb#|+Uw7Y?)k7W>WhTkJd6(eCaa z7g+fw1RuV1_}*d0?^cJuYaC|&ZuLD)^!tqO2cEO7UjBO$qoc;_aWA<-}6L&t6lo8#`Zhzx3K8kN ze)I>kfqe_n|EK>`H|rmr-<+baxFA}7lp^ao6D<0Q>jy|4rg_cE@8R8Rb3q{m()&B( z$5YXDtJi0<@=XjreBvyZ$G^I@UFwmXAzpUe`>RR)q!hD_7nZB3USQ)XRLga zLJu>3w*l4f%->6TMZ14kT|Ixu-WRouZaHP`Q-Qe(}@p1xY)0rXXX2T@%49O(eGbQsQu_a%nnTg65U7t z^mliwu>R)!p(FYy<-6mjAF}%F%d@|?eFxQ46Y_Jr_5a9rcYk2z`~GxF*-wcbyLK=h z-@bbX^Up8f2K%(1J4F93-2SuVXU`6HzVA;%cI?{mlX(H-&)glmw}ZPnPq=EM~e7Y;IhZ#wkd^APj*rtg)aGu@9xT<*Tyef+@8vmD=Rp(Q1_ z@dp|!1P|c+uI*;6yA-hBq1&GP-|s`Tzh#4rFCfAYrs;>%VdfuI-}i_@wCq3>@L+#KM(Muq0F5af z1`YpwUx5}KtdPDo$h;pRr$oQI9*0V44(3%$9cYqJ>exC#~8mv3w zN3Q5^SBUc8$AZ5f0~-V}j{W;2Gj>B2sD?sC*zcI%RD?nBRLz=>shZ(=;9cKQX_uWBM_5R0)tUncgvx*-6 z^WwWJ`|pn5N#8quCx8dZpw%ebVdkH9-(QJ-G*>lc{j2buRW$g=i{Gy7-#fl1{qFdl z03ND?sQsRI`1==R<$qGdz*hXw2@?JN<-02zk_F$tfZPo;g_W;!QrY+Ko4>CuXZ!)0 zw^8g?{=N3MXSeeAwTa!|C;hdU-Oal7`*%_M-|-IL<5~HF>aP4Wyv(@%%J=h^LHlBJ zMV;9i)O>@@x;gBuuilgY{*C>~`3q0vUUlyeo~FuinXTr#vhAbavfUiN`4!ZDi^~80 z#{Qf8({JYQ!gAk5yB|KD^<9aBPp5I!_jfzK=j>u!`sn+-d(2C}`-=W%x@Y!P?ppVS zZR;*>ySLd7VW(^ zeKzRu8vW$oE)nwIUwju9ZSpVvUHx0UnAadE`QHVF?4Y`f9#i^kFE7?NZ$#g4 z{l5E0TJ$X!-=gk)j1$FvyzBnG_NNQ$@4ml+qTe%r#C;F^6UWN8=*Rbcj6c5o)Dr#k zuKWAizb>rb`+f?F{?7ap_dD=M94lYgLJ}`$c{aVw>GPt8-R6hsST5-#Xph zk>4e{-F`b+cXvk~2L-`FP+(l{X8kVz?K{VJN%`<4qQ4EhkA2r~mX})b0W8ZTS3M;=7VOpV1Am@9%!P|9;2D z_q|SS(a%|jN)`!y=ljn3{nvL^;T=E2esYSwI6HB<+@ilTOW2p6Yt@tg%f{}S*5EH! z^8NTC_JGuB7v+U-{Pg_(>#rxfYidKFT*>!iP;s!4C5L{_LNb!!JKq9PJg(da_^KIPIw1qTk1$qI};4e)E54 z`I)wb@yDm7%s)Q;xG%c2yYai_pV!^rYyYJGZu_J2lfC=k_q4)p)-221>fh|$9KY{$ ze~Bb7W7uo(;+ALv)@Alt0M5|}i&YClG)*QL+g{2F+Z(fY0|`zP?(nJ8yn< zw~ft?Nt;r-Yr5;}x~se6_s_kZWE&kiw>!1Fa$4iuZjLS8O&f0P;^?+1t?teT-|j5F z`Fqf2#xo$2`Fr&DzoG}XCWb~NB!q`0Zauhv>(>49n_1>_@%^6iUFUloJP5X(MnX8hii#bEJ%IJQz&}xtzLRUS4t!s>J7a^;@;$;DKfeCZ6D^xpFeSg+vOB~k z(Vs&&BIR20S)FbU)yUtxCbnx0p36V#-gj;DIS%0!+wE6*yzgGuy=u;q#T>PBSSu@w zT8q1Fx>LTh=NwymY~P8i-SfNW)J?D9_&x2nsHkv6et4{xXE%p)-gky0hZmhaCx5$p z`)|f&ejI$?=bYS_8Q$&Sh^5QI=L%KPvli!>_xAxE#`E%X(ezUCz=HRpLKF4_S z9P^K0v2(vKeZS=KN0gnfw)@NviE_pt%f!z7{>c9Q&G*+KJN`uciDc!=h017bXZ}9* z`xeo~MZahNp8Z3#o3#hD&90VXJ6re6?ipP(IPRS>V5>V*a47K*$9J3WHX%EH8~-*g zSY`~4gw^yV=0neWk}t5yIo;oP2X}Y>{_O0`%J+NW zPqF!omk*yl%=|qBru{e1-{UZYd_e|%=LRd-{lh@?dtdPHW9;7E-9OlZySx9`{bu~l zxH-wye`qtTzo}8w*KfB{ldjpw4m%q`&q^% zXMYHtW&W{V?E6Xf@0!2azWrAEt@N8i;kTyT?~`Z0pJo5f^zizZk5A0h+?DaQVF z-iMoVOS=~}&#tTJtf=iS=0yt~oBx5z*GvvSc7=L%d$}U8 zw>gA=2VjYv?@TAYvwvrk+x>k(G`m8Bx*b$GW+)wmDLujdb0+=c_sXWU?Eqa(LeTotfjuo8NV!KQh^cKLtF`zG~S0o3;BlqqClFHV5BN zSy153UOr{T54mMCmVTcP3P9V{-wp3wxR-SA!u<>P67OHQ_nrA(-fH1LlNx?Ctq}dr z!~UIj_w~!&x4LiHU;fRzo8!9x`?tyOugNWiM*W}ZSmHk9H$&iWj^C_uTI_ZU4QFWg zf`Vhx`|EO$1Ov@E!hdElYHtfpr{af8G0Z#*Kv> zdSHtjP;-^=pJ@!B%r7VWXI4WQD0>~rJt+VF?8ooI@_*<2s1p7CfL-|SBnGW*{ztpd zbe}nX^vyPo@9!>({>fq2>NA{WzmVgH4EyzkPiMX9k4DvKmneyW_`*)V|bwP_9IsB^L*|74R z1f@*urPVKn@AH4W5dF@;b^lL2sLZ%fe=1?4ABRuXTQgSSPYpdyJx$$>-SE<*Nc1}+ zm+-d+Jk}!@X)`-#cFyA1{GH9IoHhN7_d%Umn1$PSCN7d(c(Lwu!bV??q~DxZ=YyTt z*wfhEK(-U7cg^UW!EtJi<^$HWQ$7cDak#L!=H2&Q?-*#dMC8*-Me@1-wCVyDEW~CYFl^wj{YMDVg&q-`XM7aiHq;| z^OASp_r7Di_Kx{G|8e5d(Nc*0M^ zFJO&5-~WJUhrc4fJ3$JI`rm!$Jo}v$GavpQAo`t|3q|b5O^_f97oX{OuFW}{zANrZ-6S+;;Sbw|!a3i~z6*

{r{) zrgmp^$7W|Gcjt5`E-YKY;s20TIHzo7!t&^D*Y1>@Zg-Au!%G=gJ2?23d=K0Az3?#O z4_Ov{_6Of3)~(sry{&sy#0B+@!6&`;cAw~8JLl9}jz{b+zZp{!W4hzI!!Lw9NID&M zEV?_cJEp?rHxmcn?;US`bcr^Vv+gh1klr28ZTp)suv@EJ`(*s%T8^pnSp#QBtnNPB zee*lh+3u&^kHU7@EaKq%?z-;?ELeWr1TDyC<>LD;yzl7#M`xj-b@~Ul=m)Om-y0!e zCV2MI{-gUC4}nAS$A#~~qTkuLCV$@uIux!XJUuO-+qOIKJEK;&c6YpQtue>^M%LL! zSMNL9eY5-QZ>Fc+kGgjqSagen?>mY?-%H9xPkvVgEgwDmdusQN&!9`!TNc7pmrr4Z zm|fOgF}Y$I$F=Xy?%e^uV1pH=Ig zztX(BJoooykjX2$jebl0_6IRlx>;Qn?{{DP`h7(=>z_G4*g$4Oj6<^Xd#bDGf$zfK zPjz>n{k^{X(08ft6(D9z_nKR*6T2pMPsB*dt=+9XZ9N?4zMHvs2mh8~ja(hM*S{N@ zu;KaOQ1_0|!`U2(YQGymu1V<*`7QNZ7sM3m4*M<4%6E0&(f!*&{(@zK6CelvPGkXP z1vE)ebe#Wg$hs+aYt}&w2gP+KZirvSvHcV4g2sg{i!tO&yUV7OPv^M$-4PT?vfxOH z$8hlG?sa(^^EqM_S!*U%_g7)a&+4AlKC6Yp;&%uu-=eeUQC)v}5hzW5KZN9k3Hy%j zpSthp{z*uF{J{xU@EqjF@Ae=P5+UypYJS}O5ia_jg=^YxZlqN3-R$U(k}u#~Rw`Of#-tNXWi zx9@NM(tM5?O{_N_y`Ocx`+E0>>KmpUd`GXgUH#5{we70VneX1;<-dCi`<(e6E9&@N zfnB&~LDIJTJ>49_KD)cOEnl#O{DnVH1|wv})x>Iboemt8!B1`Mz_V{VoEsQMlpk zcP`NnT*3!_^n4c)z4}{{U3dY^hyySq)E&PEu?a7L7;yk%MA3JCP{z3puN2ORPUiYP z>9;#lcojLldjnGR0H$aus1DlB0#@jQtkB`zJCMSA?-=jD`yTp^`G?J7P#v@5$99ko zwclRIirwD504cuvj&VJx8e_h{5~N7x#|F{KTt|OV5`QFCx7^#6WbyZ%JegP|q`d_lBH%y6*c` z(G4qd)6?_vQsXMNELl8%%MSVP6MjpG{&r=XHD~JL?j_wz8y8p2<@oM?_D_%JcXzhx z%EsdE!tSCeC9^9ze!F@8=wai78hZm|DC2i85XJm`?N43!nW__hw;?$js{FgzQN|y0 ze#DDbED!$9_uHiVw`q6CZ@!9rj%kgo=N~ z_q5l))7E$YX8%4ZtDE)voZlC*DE?vgGeR`ITl;sK&iAzR?(gisC#~sb{a%d{KaiBM z_xnx8?;JlxM8C6hec$rCAIY?75EVyPG5%QfLlPmO^!>Z&?*rZ6wZBgUt&Q9u`aALa z*WXFMzp}!D=GKDp&zT2)9W?=X+|7V!!l*yB) z%&eSN99ESYET2`s=C=@AcV1gtUMENE=ZWmuy>0#7D>=Rk?U^36i+$0g>9ge)c6a8^ z>*n}9=Qp3|cYe0k-DOLXx;diLQ~X0#WbTyT*u8Smob4Q~y{zTARrN*Ph25F6J1%qd zOc(86J#!`~YNs?z%;5O#^;<;r*n;j!tSw!ji$6JP+b7JG@BUui*~41VQrlkC!qGaJ zeO7;awY(2|&5VZr>TZtMw78&%l^NUR7k1B@JaZmL5qtVP(X7(ddv?xWv2wxuiZXe9 z_LjP;5;@@m3_tYM5g`Q&kGqq;`%G?MBJ^Eu^OMbwHw!a-{i!87v8TUhLN~{Z{^k<- z-+b(aEiDCdE#D@w7xgqx=$^;%U7CH?!sQcZc5^J4R-GxY%-&tl+E&oX(ej;P5_^8j za?DyZYtf2Dh1qi5rLBea1=Z;d+gNv`{)Q2qFZ&%Oy8glU z*`V;M?>_Qf>bo0=snxywtXp@!=kJZ6fScMK^jnJccZ}zE2ex!G)*sA2_>k;?7_#o( z_c-cYC%B3)bH7wz3mCuu`Y{9K^uM>hSAl50-|jz+${9~y`L1w<`TK|O^F+J9YX!ab zKe=$lvZbpQb2Q9ht*%ckO-fH%pSrMmI!8kl>#E{qg$wiieS@^Se`|IBmR|9j>6vy> zc3x3VImh%$)(wkRFIl!^S<&jE4J91Y=d$L^&s$Qo`q}Fh-CtOPKZ4p17ez~#`G4p9?KpvB!+-DiEz#}y-P2O*cRTBM-tR|6e{&xAZhM23ZwADG>)%7Z z@Axj!efGD<-M8PnLc0xrm;GM%U8LLj`?TMj&c8i>`fM9Y9IcX@!bHNw!o4-&W7Y~5#n>t49`J;lA-`M1~aecz?Koxd;r zE#URr{CA2@xAS+$n@K9HcCCpKrH&x07wlZQZqFo+3}e>slYeB2K1up58oZ^zgH^Pg#ONX+wJ^4%Fz3F z^=~P^Zs+gJduzj4zw7+iDf-*^^7n)ntkHkiM87-P{4P;th1jzByXyDLzg4;~|33I# zcI|h8@AJR&c3=M8@tbM>#qUKQy3hVLytMPXRcLqeZxz$0ZCd@ze|BtLZnWD zrA~sSe!7G8eD^pB&bnW}&tv_bV0%D$N+kp*VV|3( zbLg^9u0O8(-64>pz@7ECPEd$>YXgTad;ippSHDYL3T zTML^x_?Dmj!E}~!|5@fAcb1EO=jDP5{Frr?dH;_^qTl(r_$I@Ye9u)C-SM53b$aLY zuIb$z-#4B8y^XD<`@7l?rtjLI8bT1X2*3@r96%pwMTEw2!^bHc;lIUMuYV5~^<5r+ zs{01Vt8}!g27stXp^NZ+6z|zN+4;ZjRrZ zJil*a10Co&sf**c>L146R-hV50c0`j_mw~9iXO_{=-2JSq2povn^7)=jnDhkRhTF4 zcZtsCy2=9bs`shuU{R3e`#{2f+J9UEdl{4u|J;DlJAwy>AcS1$TZgK?)JIdo$J8*o%Q=> zQP)Kg=ezGgL*Ws~fncXRD`x~b25jSsYu|5!Y&_Jx^7=8PFnJnv*Kr%ip6`Ng-6`s< zTS4y@l#|M)AqCwp~pwTX&w{?{ZLi9oe1f z#QNLpXB0T>idxTppSBe~=XY52E7$jvzgtDO{?=rF@|~e%$#3=F`rp-m>n>Vi^qql~ z52W7Vd)#NxG~wg#anH{(e_#E>P4pwz?@8YUK@)}`1yu*Xhk+D)H$V8@@+|WYz3*>C zRX>KRs)l}4J^1k>+aJ;I3&Cn9pZ&pdnsN1M<{v92i+&g2;wzeb`UlHd#vd7{nOFbl z6a6m8#a9IO7ev`=P`qDf0ju;rbrqo&B>t!4#|5yC!Zl)Ne|Y?0`Qvf=N5`KwHol@C z%YSr;dj9eF!}7z!`%lM@Ha5Qao!@Ifh;h!&@3q^%*Y0E%`SasHXjzL8SL`Cu-?iUs z*uL{OywjTYTY%&Ds_!+TziWQiuuuEW|K{y?foUAySN&ck+VGoSL-RL(1IPCo_W0j5 zqQ3>EX})RrF2M1{4M)ELiD!<2!6L< z-M-=IqLbYxyN{IYNaJ|^J3_SEwf4FMK~PI+^SH;YiW%LR@iT--WqA zstiCK_V0#Zg)lbr_lw_u!29n9y^uBhn6g7?&W`UzS7YXV@7N(M@O{HW(Un}n9ILp5 zpL~D(-Cp#&)o)4W_?K|Cau4CHLLxyYzSI?^g0~yKS~V-u~SNWc80J zJD7h|et#?a`$7Eo2W-YH5c<2+V`RI(*KPk^2UGrC=X<^A_Xpd5KVbWjUW!oGefYa0 z2r-@jk>4E;gA&|jvF{>WKjOY`6#XvB^}F)7BZ&T9`9}>z!}xqiT7Re=2Bjo~zCUW; z9e=2?{ZV6qviQ0W|4=*3cmhN+|9~ho`w=Jl+Y|)9o3eiY_xqpdZ_uVg)9jrH}WCdBP^X|Jg$gJ<$@4o9ChR4iykoD0gn z&pXU`@-Xvvt{)FXga14@{C(b!Plx|}0!e+i5>p&Ao%yZKc9ks zfF$`Bl>PX0nDP6a!^}VBetZ%={O3XN_j!Ll9sco&o$ot;_u(IR7l36?9u^J$@!;_9 zc|Se{|M|qu_nUt~8EEqaXiKfe_e#<4Vq6e0$i6j*un4Hv_+9<`ro+ECeHRY?Gwtwq z5q7?b3(BBd&_JqIe-{xw{KxdW`VUhO1y|JtGym*i<{yh7HvH!MT`BrqoQv-}FGBY0 zVP+AFpE9T_JjKAuIFYP6dzksB6xcGY@9ICa4*$`DJLxyyZxOJ$Ag5JB15)gJ^>|f^1T<-dCus5^d|AYS~=au^f>SH4$@9{ydx{=MLPxn1|~)Ze+?-}8gJyMM3rbnpKD7G#CG z!f)nl-K*DhXC3Zl?S6LleD`l-kgd7Xr?c{XM+=JYT%Zv8A@bcv^yzo??@Zm@hwZzs ze;2gtw*M{odm^f{Uw7O67Gb^Cee=5rs9HPB`g?`gZ?W&{-}Bk{QV#!!D%*aT@rT^u z^M{#Fes353{x0-8lV*1&M`e9!X?g9^xpKj5#WU@%pI&-;wOse%-wYpq_iy>$zvVl_ z$L_-%s|!zi+t=C`%N=H$J9Fv$h0{wb<+?L}Gim;Q7s|nxa(K~#AFiMnIe(b>$EEKJ zM1Q|K_?zi%_gap*(-$tBKeMn>?l9Zp+Uxe-h2Gh6-ND}()V}v8{q9ft&7j&H%#pp+ z`}Fmh*B8qLvsKm>mY3EqoGaJ8_B+$t@9z$B@cme@m-)w~iK5>nxPB!4HWU5B^xfeH z({~3}zLR^IPyXl@{VvJ%C+WMH=ntmf4u6<_JFxQkoMHs&x+*rC>oN{j#9Dcf&@t4Hz z?+tsIPkx^&s&~!dh1~t_^T%i0;`q_WHpAwE|GjRGmshX7c;R?eSKgxA)xXY~<4+G; z-EFty=G`264vu>AzqrKC9N2&6?EY{sukbKWudoAWYrMo zv$_6E{i_XHfK`GJn(k7Z&9E!n%ioBme+ZudK+hvPeo z@1HjI@Ba^q{yqpgJKd)1yTosup5MwGd@bFF8Godo0_o}g(e|hPPaEqGj~_Ckzn}bm z!p`?yzi0kW_63Y9dqgjHeP8rr<@Y&%R^06V{qUzj6=(;5{(`cf2IY)D<9-^5I`{lu z^k?PoIX_m|c7K2P*I*7vf%8w!vy3Os{+M)*`NR)H(Lc#&f26a2XZgN3!Ri zboSpYzj@SuOa12ft?>Fghun{JPxh`q$*jN4zT5xi`L6q2@4Mx9uJ0Cde0D#wKr5!R zzIXr5`rZ9K>vtFHcj@n4qQ9kooBfvmZuVXNyBRCr@8{nG-!Wc&$NZh|`xek-{co=C zjNflEp8kFcGz`wSIqzL$+4ri$-!0w=eV_ME*y8*7AEBb(rMZN6{3&AjF3sin{ha7; z|KRWbY`;T(hq4M+Y)JcF_`T?R;qRgi8-9m;5B(krD*r;ezlVGeP22Fh@ORPg!tX_C zX{>yE5C5<^%=}~71JU0$pw)%nZCL-<{C*r31WY(ep+Q_ ze!un`4A*9UzXpQ8uR*1MgSp?YWv=~e_16kTn)B={&~)8FaNqgI_3y_)({+EEe|w64 z;Ntu7;l~tFUAEfB-%{przvsq%pZi#&-IL!FQL(v!`?L ztrmO9HgoECsrx&=&)xBR?so~(navz8*=i>Kc6sjk!-yk*&HInRZx`e0dJewHd#~>O zuC@2-UdH`<@9$&&ap${)=-%H;*;CWkY~8YY?N<5kOZWa>!sZeZ;OTKF@RI!ZCBFqk z_x|3-uBG$nJImL5FTOL%f8VzE_a-*zivHh{-+ps^mzPH}`G@kp`+J%9{}2+5{JxZZ z z8+48QcS(is9KYq|`HH?9_K5ENzLag_n)O?^tj$i8`@MAU_a*F?0uFh41ctcC{TBGW zM0D@>ZESBIz534b^@YW6M!DbH_I}^Q{$2hz$G6{-za>FyFy-a=ihgMSm@gXndnsFL zR(fJm=IX6--1>XkKRv8`MU%>)gYw_w zHs9XN{A1O3KhfU~-=)4goc-+e#x+4zbY7krO+$M{3_9rKUS@5e+Zb3OWf0X(2p^gZU?_lO0Ir{6JuXa61}x@6I! zrOV3}7UY+d78EQhSuSt#{Q_tJjPEB~;{rw*mL|whbp~Xp`g_^~!#vhkJSINt8{h0E5r|AAaQ^MJQO!+-s^t&teaPj@PU8&pGEfJV`OO!>YKEcm^(8??25;W5S^ zmlQ!`Qh%oW;1>NMcJ=jl#g&IRUb45==>IPHW8}%f_kH0p#*;rXKtoc;J;wgW z=&b(tlGa%qy6h`MUjJ77A?C=z_kH#$#_w^KqL059vVJepKk{3C0%+{2bW7l)-`kN$r)26J;EwA*{jkJm=cX;s{{B&~UtFe`?`GET=09{q^QL4^%9yxq z(#EN4r*ZIoF9G@M`+8CJ3aetTLXH(zS&uKcJomv2j$`ZrQ^Kal%;324-8;0~15`e? z-17g<*#;WtnpAgD;d??TM@=g0?~oubm8N_0cba-eo4s5RRCqwBY^12{g-erNsO{9C%a{Cl90%I_HmyI*~8V*OqoA^JPz?d9)Y z-(^m-{*KcT4gW3no8U%t-`?biC;{(Jv-#qK}d--Dle z{I*6nciImD(cj{}TRwx_>-*c_w~bBr_t&6?O82|(_3ysdy<@!ij`=&+&q<<_x&G>d zR%Sc`EfM^o|2_R3<9E(?-)r7|Z+HjV$ao4g;_+B)GS^={vB_L~KlFP*+Ce6Ol+}Vb z%pwndUIH8S`{D10pamQcz!cvPeYkbsIY27E*S%vFk@;=`R{T@%uRcie!|#tkZKmn) z&2!(m_JKwYc1{)r?UOU?HojW_p5y!fy}$pnK{vU558C_NkL|Zux2kI0Z$1vE$SZ6! ze)D}){VvwcfwVJi|Nigi_JEA4KsD;zZ>BxpXGf|0K8I!?WV0Q-YW^)aZ|`^cz28Ol zGVb61y=@Qk4}*E4dw*-O2Y2i5X?)7@z0GOQ6}IlZ-It=eyIp>_b+b-?dVk}|ZjSF- zdw;93{T8<{_|5X0`-#qXL8J|H7v6zPGkM4S{mf4@Q0O}R)kE1Qc>6Hp{{76yf7}!O z;duCuBij#~zkDDCzXd04{jRa~yXMyOyBW7{Xa1o-Np$OPQ+B&<$5RatIKH3U_WLB; z)Z6FIKj`N8Zo2ii5!-Jr1I^!TzgeGYe&>?^9s8qR^asQ4KMZVqVZS*Jp9k$!{O$zu z8}rfM95Bo5&V%d%ZDnNNdv5P{4p57S8MKuV6d5kvt~;|YaD0Ee_xE46&WT+!L3h6O z%$&fZ`u*?T-|yH~o;$PWRyPMUe$avw5yL-RKrsy3G8p+ii2Z5zW5?<09KZiZe*e$b zpkrgB-p%nlDDt}>+jp_5NsVbAw-u-%8qMxFc46!m-d zo=@M;p&2L--bM&-B7tLA1Qf}P7xsK_+t0lJhlgn7cP;kA-Or<@8gl$@yAtKZ)*adH zvZuTI()YG*)_OzpRNron-&&F1)!4p^-+T6*G-9!(q`s*55mSSRejl{k<1N75~-xQOw471$mq7 zcjlF%%a-BVC%a-r?&`ceT>E5;idc^vLE0z#TmAde@1Bg`12!?w{}CnH9sND|w@~-j z@8Q2$yLbPt`!3n7z{>Zfdl%!4T|a_%G5`1{*8PLy_u}tIelPlcr27vC>yM^gqJM(F zA7%Sq{Jrmc$?rZ^zTccbeta)yJoDp+=%H=L_Z``JAogU;kr<9Os~~5W{*C~t`JMB< zobiY5G|`X*|A?U2u${iUf_8C$&sd899Yn>-XZ2&^LB=gVj){i-usy*3$K%g&(eH9x zVBup28Mpj2hf3J~W`(j~N`ABdtT@hi@;LJkgP#?m$A5qNyZQLfa(2Go>v zTh7imt-E~hS;p^M&oY1CDu&j_=bQFD=e6kXAkXhXY%6o2BLIBUeu(@yBlbNpuC{GI)KE|_9V z-Yoh%_j~U5TsFS%%=`C#XFc)h_;=QQjMw&lXFdM;_;=R5%s*y-Um+Uw=?L429mkIx zkKPsN6B-#LAEbPQ{Yc1OU%$A>AphM#N9B&F1hM(W`3L##jyrn%$eyF}hd&3i2Of+% zb#(WRlgFcv1j>CkX9+sOwr=i%P3sm^rlyotX2|{K{B16({OL2>ch27{MKen0Y?eQK zgdHX=|3~hJoanl_3pQ?=Tb3ag6vUoUHg}W!;XiVsAO=W6{zx!eMpbFD9AD9Q21pk9 zAtvhid)1GgvwytU_`U~sZ~ndrgcy&5$nOicfSNbf-|vZjm*@Ju6+|m=eb@i}Np#wj zDbuDkO=*z-&A`?+y>V*&1di`^>>!bbrltnD-*)VkO?9>1#oYyqikEjU?4CVi%3Kb< zZ9Thxa_nY&w)^t#?>xIt?q>c`cuw@U#dp)+=D$r@Tc)&6>z>YWioN^Ncb>W3-`To9 z{^nWHeU;+~`_cuASIB+uV_UJHxKKWbz1#6OQ(m`f_iwiEOqJa(96s#b_3aJKO&otX ze|!^du4$|4uH*<}?>73*lh2VPe$@TwJJYJ}M;u4kyXUseX`aP#boXy%w%>i-pc5OSS^0jm|Hvu( zzV$d`&vEAOpCP5??~lj7Zv~Z!Ka75aiyr^}>Gwzf?_1gUezPC{t|jXK`_uQ2$A5$Q zzhjSoU$B7jhv9MN@BF_NK?UUZkN&^6vh%(9S$B}}`-6MmUA_x2|9J7UPIO}T#IF9% zw$|$E?lO*&MLFx%tXZ=CeAt}yfZz2hf40i~{&Md}Jp1<@559-Lmg9T#JNh8w>hA*I zUG6b|*Z!?43R*DV-aBF9Y|sVTi%M2yWMvh_yC0}pf9!kxrypD8zJIa!6VLv8huQCN zZ8^RR-=`gA{PE(um1ya5qy^l3patBGhy~m=H%vJAE_^ott33{06K(=s6AoUq)x?Uj zCY*2K3J$*S=|7@QG2Z{)^hrzp z6#HLJF)+>dJ^k?E?=KHC{;>QpQ8f7bzr*Z*CkBdsH(>i7-o4?E{P*~7j_*!~e><@K zHtdf4A^#h+iot-5?|XXp<{yHa87FLI{;}&vqUfK6Y>kc0_40o#+3Tk^PnG+zjBRT7 z%$XA>a(qwyp7uNAdj@OMl)9;n-5h@!|5%DP{t%qO_C4@-AnR}D-yx#kb-rtV*ZHl@ zdRy#w$oG)%0pA0cb$<{19sE1+cMz+>4?)r28oxDvYkb#ah1refy%;$bsihcqq z4g4MQJ4C&k^}Fv6LDBDfxF=KcQqu8T z;{e2%Cb@jJrgn%i{cTg^ma|QP8so7W$(Y7Ah%pn|ry?1naUe`|5&M+(eu!h*o8(K_ znqpPU=|KUDCM&?w?aZ`A~*=3fz8ALad2xt#M=X$iZ-LD{%bog#=&QM3AqLU9*gB3dA)6aMv_qMEz7q)N?>x)7B616T~Wz zYe0Siy9VqhsB1uef+#`plS0RLU1<0<$^E_uN@d^x>~EbS_x&E*lzwmsHgNn=hlF4| zHaA1knFGfja5`%PC)Y-Jas{We319~)aO?-Cv$lSS&mj&1xf$ehuoAG(!ET2599ap} z=M5acH4lh_(<0c;MeI``1~#_CR3q8hz!3%aAUGGn{RhfL6DP~_{hqPp`&#Dj$9|@Y zMlAVV^(W_h)t{VwOMd752xjH`J!1h#^3eBXqQ9$tyE%R|{;(9C`bV&i?RVh! zK-TZf-$O)y>-^UKt@B-*)mrR($nTKf0lxzax_<|L5B?tbJ&5(&A3@RY8s9a)Yy1Y6 zkU!1J8Mm%KzV64a^~~S@|CA8@zW=x5+?Buge|Kd4zU7aX=x-1|_xqmjj;w#SeLpVx z`|Y~#Z`pMgiGIKQJ@EUj-@e^{IDTLK9r*jkcVE`@AF`s|e~iBW`JMRv=l9g^A4b3b zeNSZN`yTMyXv_Eao4>!`!gy%Qk2{;0MIL;gdr!3VNXFi{%^W{!*=9_dI(2Hx)H?Y; zwQM^!uiLxu2uBWEp?g%2PX@>LKR@pLE&6lk`ycjG8GC}3x^w(U``s$~U5N{;!9RX} zUnKgcj;*exv9WPd;|%#9b!>6T8Ih$y9IM!tp5Jri)H;sefBxM0S@h%1??3E5>!OYn zp6AG6TNt!1az`@9qdyJ^qkk5G9Q=LB=I={3Gyh=uv0U^o7khW(gw{!&9KSb!O!!;$ zdjs2~-U$=Cr*iz{V&4?EH{(bt$0~N`<^BgVFJ!m{ggWPQ1+NN08|E z>Fl#wCpXBocDHu4wQ{Jas(xf`o7mdd+Rf3>+FT?5``>Tn5A3sACqb38ar|!h-N@SB z-r3gO*4@_I*5A(Y9V9ZLZDPm7ZjNaaC(V-oz`m<{<-{4&IliCxew=ms^oeu27jPtt zbvJglw6}9uS}JvSe?NE6g0-)szjH$Or0xm5eLSl7?y>$B{rye!x8HAH*7ml7&d6?# zx|WtY`EKXm0pCF|u-l2XZc@t(IZ)H!dm!Uax9>Yee{cJK?f2H-*I0jC|GiW6d*Gkz zzXRF%zDL)s`4P3{yU`k zolEq)3fK3mzkiE$ZBu@1CAN{j2a`1jSKzbk)hvwv6lVTob$)4ktYcRbnq7_2jP z$9M1J-!0ZK|4{vIBKlpMtLw)$3?0vQd@tSfT@310%ah-o_9Yzt9i3%%#{8JJ_X*h2lJnpHu3`S(^y7}`cYUtkTHpCF)IQtvJ!r>w z`%RGGaXp;6_q*NR*u&r5)(HPy@MGnVY|-xqT*Chnz8iA=mU$p5{CB~3Bd(6$Iiesj zW3Gumm@tfdy5qa#=I<3yBV7)s@A>Y0_`55}7eBUq|0(+2giHAEf$yeVzm2{}iyqn> z>FSdj7XrG}IXHXU)$?n%9gv^-T?^CL&EKzW*#Uh$Y&Bwmm9ecM%_|JnMh2NP( zzngLW2x9qe&L#Z!!S9mQqHVFM`2k%VN$kS^9^Cu={WzQO-v>Xk*}tMEs`T%-H#7ba z*v$OH;-{DBcMH%hH{b7lKm7a9MR!=3D`Hhm zcUpH+SAAvE+{T+J-5%ZLo|R=B!VNVmCw}+%o!1@Qoo5xBTanNZ*&WT%UD;pOU(dn! z-MHrX56kcAjNdJef9E;Q{Cz`>=;hs8p2>e-?EiZ)duV!Pac=I6lXCuS_S*xl%l%&T zBSQ4MCCEWBE4o)c*|>Yt_Ko*fm#*GYHfQ6W#b3*WzMCySx_HfZo8Nk?Yd2M_uHD$M zx@=?f>J3ZItQ5AWVEgF{+74^QC2aA7?Kk^(VbS&!R$k5}Ip;}4-%KFt_xvAT2sOW}g293^zkNiPO`KI!QC2vOb<+2{Ai*Ce zf3tyAR)9?N{)%7=SJbfmp1fG}x0rqLCHCx+^7QV+?zBmzi;LY-qdmF}yPdcAo=A=D zOXyAR=E!9i-ciH$-KG1xTi$oCZjKe~q22y{z7uY(IeNMKe)sdh8*a-MEt|5idwutY zk`0-QIc_XA|1QQRY*oefTkrQS(eE}~!d4Z0-vz`#ESn0oq(-oqO+{NASjeKnZSL$mQn$#I!&ckTMb?%3|^s*Hk)YnjKqx-GgjY%NtAI7}z9nlJXf+WnYA*rJN9 z`_8UIcem%wt6bl`22|&CZ~neww4wln&^;(PUXr{7yZYl+H#=>JR>{cg{t`CVW1;%~q21=YW; zerufko%Y-3yTEU+-+a5jXZ&{guJXNMCM(}>-g#w9PcVLec!K$d`S+Egf%8}^gPMZ6 zLO6a`ey{w#^82cUZNK;bKG2=;z3+D~YiL(+Q(y(hyg=4Na}G?|-@T}NUHh))mL2Wu zx|ehxn0#Q)K@PshAQOMgKEZhI#1Eqr%s)9{#x}x@o%(%B>487t-MKm4f9pq-LHT1%=@ik(B|aWt;eB$+xhv6+m}AbbwBjIWHF=cyY+8|?^2+{ zHm$pVGyIk;|7~MY=j+mK%CSf6;(-GfFB}SScMA)30ga5x{a7t_?!e)T7Y~HFdPD>} zdj#*hEC+Jby6^LLeec@CxO(sRu07v_cYp8R!~9F-M~oBv?*>6?d z-`u}dyML>6Yk%jd_^$G7!j-GtFF4*?U)I;Nc2Se-Ha+QbuWrF_>+cM|rM_Erf4A=b z&hTAw{&$;uGfrOWe#j9e<{B2}>K1b7{DlJtFP%Fa<|_ARwU|d(u&Zm>fs5z%9lm_- zaD=lQ-<$5u-#=|(Tn)+<%vZi=i-Hs3Z~hPZ-`T%&zJ2|jTdsRwHaZ^4)<8LN+FFnErNSO?{pl+B?0TRdjN?||>o@;^TOY!g*5HvY|^Wcctq)-)BjMJ;Hb(zUF6clXlXRg*ZTE}K4O^`zeV zp62ex?z*n7riR?i_Bn39qk5xz>n3&gboF;n>gM<%(>=MHby|1Ngx(_@zfXN%BD$`B zb^ns?W!(!qwzY9oC9x*=R`pbNS9Mo*CG)7>%oE+WJuW;fK0YioVcVet+qWH(|9*X< zXh~~bS4MYUcWH0>gh>T6+V6I6?e6cLx`N|RgIH!;dS_O5PIp^R`NV1Y{T=JO-*xv) zn!K0;90p5v%=@mjxOmmnMVn^tn$b~%_xbH2+8N)~-PFzT`#W1}cUNEMmSf-T`fHc0=37iYpx=StgHlqy2Z2UTe+O+!`5yQ^_{eJBC zdr@TffsTXxnQ=_?_m=OQzi;`zDd70;&A+$&-u!(_K*0B{zc>Hh`g`-S0I=NV-&;V# zuaIV>(9S)J_AELcwrBsIMaL)XIlgE0p7nc#KY#dH@{3*cyEB)W8i=&I4zPxnB znlho)8^1Gcox64J)_GfIZ=SQQ;K9<(-EiLFy>W(;4 z(QVh=&{5l5wSN9w&=&vhJ^pjM_jCN_m?hf1Z1Un+i_#nN(z=tneNS|BYj)SQ*3?&Q zS}=cA_m=LX{&TvIaQx<}742R*b>Y03vGql%-C5m{$E&+Nx+^-X+N##fo4veyefKf{ zp6=ru!tX!-$oqXrw4%GRwX&fH)XtdDJ+pIK`{b&1zdh%vb*pv%_O7f>Z*T5u=&t4H zZs{rQZt4EbyVC4f)T-k7{Y&F#*KBDy+s(0p?Q8d(nG4n*+tSfIciwl#UEhV9yT6Nb zoQm8UAa_Jbv^%b-B%qojq>gpcoC$OL=5kCq%-TC|(3ViJe&{qKu+&p6e6v%9ZrV)vBNw*0otj_%(se&N3jDmcEIRgE7lbH9i2r?c317p@;~_K5!3+P(Svi3(cFgaZ z`kgWL>~EH5-J81CtnJ?1y}Eo|_$*L6_B-!4)l1(a7YHpl@}p+)qvLsdgm*3Yepoc{ zd)RM*@8X~C9lIjey{URZ)Y4h2HZR)V!_mp!9eBw9UM7cdRZCuVZ9#Wccf*A0nVl8g z?cL4YX^ToXcJJ<9yKdqkj)iO;3)`pG%yd%v@!ci-k%pT8tYu90m< z)5ON^`tC5xjy!jcX10v(^qSP187uo|bKR+qSXRcmxT3uOreQ7sG zcJboX^4(K=Cry~uG_z?!T{lO0U0sE|u+>lJBGH_wChNM7bocd4=$Uw|>qtmGFNIeFcsHMNbuRcn81mUX9clpbnZBPSg5Jafp)J+W(I*W_iBr%dUd-#x2sYW<9r(`U`K#p;Fii-hJc|1P$odRhCjHw(Yl zuh{n9=!x)s<%S=U-(^L=OLUiSe>s^$c)xN(;$`-!Wvs&Y(;c2p>;As?x5L!#Jl58( zj_!``pzioH9^F;lHSN_69K!dPGvxJE^?-KcTK#5i49eJA{@q5tp}wuDyRjQInAgZ5 zd|#WPtggAXyR^Hhw|!C*hwy#lhU$vehVJ_AjN_RnyO(y)nLKSa$XeDZT?*s z+ui-Wjx{e=^tT;*SyxkMeMf6YQ+s_2M_C=K@cm?l!s@i9xbEiemhRFXj%n-*`zKBB zUAc0?q{%ZjbL{FqGQVX;Lnmu_dv!-+cYb%ljM7CM8+J5Kk4R)KF0E^;?5^ytp58sP zd&;C~Q>Ss9IKeu7#)2txx~F%~YhBndqqVzhNB+{9sqHJeIet$)ChF(HYSaCjdqU-; zmcG_0-IKbfPoKJQ5{K~o#S9B4J)3y3dw%zv?)9ATG%oL+-aVsl z#)SUf{_fsxj=779E32z(tK{pt6K2QH=MbK+&X6;!etP$;?#cZVrcL6g@)GUq?&&_4 zHM4a}dtcY$mI)o*_1*Oijdk6X-RV>Er*jC;*Jdc5QZ=`GPWQBF)2DWGRJ)4ycK3Dn zbxi2!?^s*Y(>m>77$LW_0v+b+>TT)-;yN3C}lXNDf^xU%q?F)Y%hzIQV`W zAO8MAwEMTj_W6@_ITD@ON*kvee&5qQ^}9oUH*4p_Zjg(Qp55MkwtH^-%=+2=E4ycR zFXZ@c)%cw)IK4daw~bu=)aHqe-L2j2osI3a^J^!Ub~l6At(CLuC)Rgoc4zu``*L&_ zPOhBY2-@L$L-cpu$+KUmy7CogX-Ja_3am_WA9zyQg+9pSgVDLXMrO)2AlxXDwaa3Oa0k z?)2F+yQg+fZJpl8aom@6X8q*y?#k}^*2>1x*4obQ{P^0!_Qvj9j-%hFiMk$V?Y=$X zJNKkHy_2U-oY+0RdusjU(ut)L^}Ag`H^g_Qb12>w?dB!?XT}` z?{4qze+uPe~GrK!GTife9n>*_} z8{6xz>{^t$H4Z@cdqfNmF|JyQg$7Y@S&*gF|@! zI*0zLy%Ruv!L}(4(>cD|Y@NUE$ZyuEzpdm;yQ{kLIyi*q=QvbPtLd-m_UMlH3hD+G z1FbzA!uQuWbg%gB(AfQZ@6!fW;r%iPrY^YL-My9dWpjD_cZqHe;r%)ezqfv0C3^68 zT`b$R?+KzSx~KLpowZ|mch8K89K!c~92QQSH*tFRobClp)9W}+__0oHnAFhS)LmO$ z56Y%>6N(pdL~L!W-w?-IT2fG#-`&_<-B;ev(a_scccgq*H|vb#NzE(UdUGe$FKp*% zU@z@z>2B_h>rR_eIGdwv25bGKrvBEx`o5;#vK|iM`8^Kbr+%*#UEe#Wdw%!D?q{tF zTNbuV2i1lxtxYW*l`X%y+ibcy7G#JD&*yWP*3#eB-P~PMU!PMGU!G81*UeE?J#+Tl zMT_Uk_jdPp_jk@{o6#}7dwKVQsq+^0_H}pl_HxYe5*41$>o9d%eS>^=RaIqiQ+|DV zQ)PEucf-`~>D|*O^evv$*E6Mm=EQSrdb<0%IcB?x3eV>`FtKZDb$1g-d1z5`Y0cD` za@~`9duH@a>6_j=rFUB2q~4zHwX^#BCiM07^?^zha5lasn$BL`*j(9N*zLR4e?2Hc zPnO9-L_>eq?PUL+ z;3(Ri+uu05aCv-p_r%)ir4uW=>$@u(%j@eoe0^CP8|zz}yKB4aXLrx-p4&ft;bM-h z`|GF1r?M6=tY6-}ynAZz?Ed*2y;JKamj`yovL@Fyx3=cycGfqymvS_+buR3l+&#H_ z`_zrImvT&>&pLT}|J2@z{nL9VgNmT4-_t~U)4Qv?%e!4$^_xpuOB?I!yPLb4C$>*+ zozwcAyZbgrW~pd%V|#mZcXfCD^xBo>+pD*CPv@9BbN1}Hi;FAex;wkuJKJ09J8Qe^ zyYr`(FD&QqEf;O9uWyvA?5>>KytsaO-csS9V|Jc;L3z*w{77RIXcZ z(e~Mj9H(-pl(5chpV>LDdtUeK-f8_Dn`Ves_p=J`KTwm_Ru|qK)7>(wb{$7``?R>_ z#jH~*XE!eFUerBn+LSpQ%NMgc|5g-@TAVa5V|w-c+_h!hHQlX^-PPT#J&pbKb(Jj* zpe2G+TW5B2%$(-~B`<85; zy=(%9@cjeRn-|qi?Vi~^c_N5tnO--$cSa9rEOl1~pXY4wFU^)*F>AhFyu(diYlYrC7e8(Z5N zK*PJe?Y-@t?VTOnjoqyiKy|{@{yEDz_%3yy_{nsFasP=|hriD{&is>OrRajjspWEi z^x2|{rZ182o-=jc{P`R|)PHERE?YKZdG`vAW5Jt!<^HI#l{7R}$agpOH&1T)&a6D6 zZu4)|2~CsRW^~W*o;tOALH8x+@0@YFIlfa6Ze z4zHtGH{^ckv(2A6b-p~`cj@ZYlfLK8`OfgYV=d$AwcqQ%Gk)(|&HVk}PYKcA0l$@g z>;HEC?f+fpxAJ$l@6O-#zbk+D|L*@=|GV;UXZhcIe%}}UZuDK`yZm?C?>65>f6Md553psu> z925P`_&tEtIx@(p`!@r}_YAh~B{LV!pU%PeUAbfN_Z3@mPp{u7w0h!qB+)-6~24^@D}YZ>o1v9lF;&-Gqd}*Wp~l^;^`&La~l_SFYdm7;_UtIL*3gl z7N>EnTfo}Y*VW(M$02-IVfOTyGv&6h|K^=m`LTOt_mhKrA5P%-t)KT>HcDRjX2K61 zvF&N!m40i=-Dy1<-mTxATcZA(vxOsxeg1^!UDv(7`%3J?-^>d+rYxMWxO)kQ@LYx85B~Ux9_Ze)WW)Atu{(>7 zcR%jlxN+^l>dK4=^KRqrguC6xx|dB}I&B_D#sb!smX>zV!P~8UEt5Gi%2}tDPb}>& z>9!AYHR*QhPCL*rlViWd?dG-J9N*P{go-9~r`4twPHdW0J+Xb+f)!I&cdzSSUpBj8 z%A_e%Cr{5>nmQ|`n?rc!g6~ehokY8*bWQG>QrlhEQQyYVz}{^hpB!7=Q#+x4as$VS z1&2>}Kj09at?>J2n`pQBfuNJc6Q}i0@0%GpKV@6@h3-k+lX@mqO{ni{?&j!h=xXS0 z;Nbhd*J|pIQ+tZmc24YFlRsg`eaEYwXXADVeg8SFZEE+l{;6#sdS3O(;As=)PMFp> zuXSqs)V66;rgcwknzt+;?bEtBmd%{8T>d0`Zh!5p?#10xdM5Wz=J@?H zgH5<$5(7iU>iRiy!VN(T40F4u_MM!|F|n_+v#YzaOJ2B9h=C!sG_||Dn?txmh=HMW zR>Mj;;e|pB4A;7+u9?xz(bv<}Q5#iOAU~yKUc-v+J>82pP3-3A?dj^M&&aQlpIkqy zb#dqP(skvBE6ZyeYr3nui>H+@s^Sn{s4EyyP+M2gUD91LvwV4VRee)UcU5=!^vXrm z#ntuI-PPSy(`y$sa?~}p)^}HT*G;LPUtiPM)ZE(6(cj2AtAFN%nG-qm=RRkj**ddr zRy#-kRMz^*jgy-v&YwPIdiRX(*{##-rY@Q~eJ1FJs`}Z*vlq>tKDT>5NB5kjSyj`Q z&z?EIdr|kIx_JdNIfNH7Ff3R!XWIPk#oe0Z%2Z`zba`Q35zJ7#tF_4M?Cf`);`VS4wptZUsI!i@|p4l8F(TPR=L z9ocqSoY33f+tWR@ZffJyCJx~P4gv~o&2>%X%^ZHk z-QNva!RcdG-=xXi(>Z2UPpy#?KE%+#U~DeBWJP^-D`;t5LwDu$`tHW=l9KMa?!28< zCrX+-n%i63L*@m}>)y?=YQ>T@a>5M^oC@EKeus*7m(*95)HL?D^tJcRpE?!PPn**? zvuxUg-pQaEaAEzl+U{D8^hQzP1_nWf?xv>3>Sm7P?6VuEG@?5RW25ce+_`cU*VV?CMpeakENichBzL{$xV;>h5(aQ@Rgx z2poM1g&H(o7ph8dqMY{Idj&`;&{HEb?waAAdOQimV%lP zmGuqX98GQg6KAiOzGTgcq8$0|qN=jYY7V^&)~vd!s_ydc+Nt&P8##mz@fZpmEnr>H zFtw(;vb(aerlO7`q=2=&p{b#}t~>p0QFl6ra07!tL)rH@(e8%Ert(IPoI-fZBESA)1h3^{G0-7T%nbxj<**k`m%Z0>I6NGleto6I50K{pJ$wZfR<%ZsFLePvo zyC-%}Z=2FIsjs)ccS6ss=1HJ|(DWwJCG36ey`bTv&d!c@9@R>*j{Z(ilX+tA#C{&t zHb2q!uFkH`ZjOoltimk}j1CWf{1Khd-q#{0JcEJJp|P!_rLC`HlKj+(eUs#cJ2)8_ zezSFo3Qyr=V3^d_*VxkD(bh7tW2)SwzP<_a-x+#Av&k)T!W|3@2Rb`@dgO(tFfbhG z>+7A^*W1-8Cp?1-VgSg_mX4l2IpG#A28Q0=o<6yWeVrZheD&XTuX-K+VZ2Z1>;6X% zosWLlFTCx?*6+Ha-B($K%YNrIvA%k=`&IV`j_>mE+keabmQP6dtsvK}wp-`535W2u z-+5EI9a)9TzL!s76|SoN{o-Lat8iKE_ZO^rPeeTrd4KHw!0~R`l24zO6|2j2t9dGW zhjH+!FDU!Le*5-q#_tJtn7?oQAbL4#?PbM1-QT^wJ9Ky7{tY6YcX!{`@9w^B)!l8a z-_2_M-1@hJ^>>HgD(p^cGM&FgcK?>U)6L3P*gfz2n|V7ne{bASF;D1w^|9}cr-i?L z-}ci;blHSS3qI`3G_{FOHI=LBZs@7%<@nzCok#RLpVkU1IpKG|Eh^ZyxAZo2J9GSg z$zHOgc0Q;z**jrzFUP#!9u?ErS9H&8*pSNcUb(yAx4>_H*38VNw(L%hitozv*b93) z`nsofPnbMo_H2$5XS(ZIyUlBA^E)^yzU$6oPv~o&*?onhd(C|#)(LIBp!xgSj*cRb zUblJd**#U$)7EqR*86Vq{vGSWg^Oow>fxC8+o*zlac6&9cSCngNo7_R$9HY^`JFSm z=5=#?cbxv>qCB6*g0de{w~yRr{GND+`TM3Hi$$N`*1vCMu5bN32pl?pr0%f&koxX$ z`?mu--*?Z_{ofazcvAj7c0c3l{ofaz{Jv;Evk2eMvhV4l-do%ow=0N zXNTuPw{DK#fxp#%tNr%>?eksbx90ai`6u0bmz~_g@teJbwXw6ItFha=J8)Cfp5Vjz z=euuld^i3s^j-A3gM) zho`=4Jhwaf-Tc(UL&BB~KOX}T^oYvA%R-D}(+?~0qaS}&o zcUNc7+KfZ;tGbs?UNC!Z)w-6&-5gss@7*Z>z44ESX!nQIM|PKs7u8H_1`VimcDHwT zOs=0HvRbJRkynzbNp7^BienZ z6h6`Leb0{$(eC_~w3?Wt z*%@294|ShdvGK?pj>(DrDcuhvy$W(S5D^Zs-};S(D~ZnLB+p zhv81cSk~#)Qz|EwyVZnvcUyEjAMid>&QY6swYxi>^|!!s{S)2a1-ox9zj<}9N|BpZ z_iv`|--73Ut1a`Hb$a)jdwn^@@c}JK9KuT)>RMWwy6cf#JEd)M)1Q>S2sU4GACva%IXWhPK;qmU<-QOL4^MB|3ZLo@?y@~Zj z*xTQN-M_iJLuwLp@;QFjey_d%;I}sD)JL_)#uv|jFaKS^no*V8RM73y9l1R7Fh^E* zOdM;&?54?0-Ob&NE!|Ds&668v*LQTP26QKN7tg8yo#DQA{f$li%$v))%KLKMi|&KnyJu`!%JE)}Byd)@D- z-_fkG`wP!^Ujo$!dhfq;Oy>CB^ruwxchvXj?(geZzq2=g*VF9&?bGd2 zRyW6Y=kGGV6@ObAdAbfimo`}|hW+72r!B~*emwkjq5!NjNqHr4U%}zDDvR@rynY!-`%;?#lCxR{f_ygB8o*3So`+_ z?~xRKj{zx!8?^U(%=_;#pr!1{=Di19^9MH@#Bt+-YY}y(etDgO?c8>Y`m(!xPEv*gRbsVMB z8Wt{`Hf^C?_l%Zlb(2o_A6~e7dsk~$TXzRX8AxvWw1o@nr__PO?M#c9`?48iv$FZP(I!Iq>>C}b=a{b*C zdM5V7Mid8k`?bt$m{ixzQQA;n3UasnjP9uuC(rEjUKG4Ny0xdRx1*b*3@W#9`m_b| z-F=-Cx+d<9KU{jcox|(<5z)?eR*1UR?&kL9*5--L{h%o;7`wH*y}PZmt&3yg>gg-y zE}PUawPj{!Z~63wc^$pAlNzVDa&&r%_O`Rux3)BP)|PiR)OVKGb~U!N)K@O6U(vdH zLf3?@{_ct0llrDi;+WhrxnmM&?6hz4q{%IlIzZDnAg^%jh!O4YWCe$Ddv{x3^Tg)X z=8k62+!=`7($vucn#k#C?``GCo|reiVD9uulcx5}oZmZbTJQXsz0)R5oX*kz-AJ^( zleKkv8mGowRC}INLu@vCpB}lz)hdj3}&}=w}aw? zW7Tg#(c-fCOF$WT$-MHS;)=2&`J1;zr%#+TwRh&czUg55rcRnTZC=6jyouRuJ&<-A zD7ZL4VFw!72XR2`)^11&;qYaFB!P+Dlfgcp+yU;A^i7^LrDakFXj)@J&x9V1*6jMc z%7PY<2YNc^H%u?@?F4yXB1b}k=)`(Z!1t9;Ynb2J0}A+-g37%5?AD%%;N_qbJ0`VE z;g~eJ4>T}6sbg}>WH5U|cYoJ}u8ADqO;~y+_DtyR?*=J^$W7{=3|BX?yT7ZyYZAxm zmKF8OD(hQX8oO%CIvX20%4<78aS17{zgr*w&Uf#-2Dr>tzyF=<-gosa7|SBx|7j4N zJBPJmPt2O|?y&Bdtcoa(xmB#om#mw)5p>g6?Xn_{-?85dMZY_6eUBAeG;i*rMV0f4 zODZc$O6OKA0u4ZRZ~k)pyAP&K&m+~Js(Nz`e8#Z*W?_NJ`)jW=6g{(P=(Zvzn;oZ@TbGLFV z{2nG+QZaAwqIvTdEt+3ZTwGC6EdM=QtfYMYqJ{GpELymryriVOyhNVwd)jxNP2X)c zf4AAp_}u|SeYe@f{6po(YSE7mEtFKvO_f#5?tlJ#=l(bO`QMGf%jGYc{WN0y!4Fc* z{F5Ct{+asQ^1IOQ#PZ6HGP$1CzP1S+uiOrUnjKu-zqx{cGwHaswRg02wQ-cq>zMaF z@w?@Bk?+ZJzaReIC3@@n&ad6yS-QWo#D2B8ezEO)ab)-J=I-D1zq3Pa+c@~ZmaX3M z-DWfM52hbmL~ZP26}x}4bpK}Asd&rYb;9rBz1`oNyT8|e&pvo_0>|%1zjulLj{R=> zTk3m~+|2IT-P0CyPwbr3+OKmV7<8`#SNC_W!{3=+U6|0{H=%nn$NZw6s^77{Eq{yu zPLSi%{T){JU3>F)iA~TYq57M@>u+Yh^8K3VcTdm;@hs8b62HaSK^uKQn}FoMi~km5 ztDn*|ZQ7J6)8)R4eHUW~Z3+VI7nlDn_FaPQ>$E42yT7w{KMcL;vg+!(eOJ35ae!n& zCTQ8%YHQivd^4?SN*~->l!IYdKIHwHdTdm0FJC_|B>I z_BYpWP7R&kJaR}Df>y+C{w|JUSvDvHen^RRe-}}{X)uN3yEvL(#o1u~lKU-=7DnQ1 ziVcQF-M`s6AnR&(e79To-2=Av_PaLNrcax{YlFfX-wNE=?=juKRarq{4+>|w?_umy zo2E9%{SE_fz5Tj9uWR8|8%@gImC;v`lYwvHIJ-dJMM7i&Y z-y_&(PMS7lTH}-k`QH)WW7)c=)lY2hY3OQg?P%uso$%X^ef5pw-}$;~7+ypWHN6?#iE4Y>iW!r%szP zdAi*9820WR^S?`dmzu`GH?Lvy_s^S!zN>F(+;VL5sV$w`h3|Y{1v-36gFU(-JfYiz z!|hDik+X+S9h2)m8hIdeD#v#XcHvha*`{0Waz7K^+u7IM2O4VwHM}QxOlY0pc_Q;n z_v!A_>rS0H$T4xk#0lM#yQeix1x-G6ba!?d*%YVOm~*JUW$W*nT~XOt(y8XKSGyUA$IojIVK{L$_Q(LEjmUMLY_Fg=` z_4xF=9N$@f`-uwQ*|aqwIU^+|F)?N1_MKZdY?9}j{hev^_rlGLkG6b=fA@D5j!Ep3T4t9|2hW6oMoKy-wD)s_9A<6r2UV=C-EAEm9UQ^Iu&sBE zQyZs(wlnnh_j4>*(A~n?ZRnP5+6uax37}J>+}X@7})O z{_<7ej3@tB9I`9&hbwqjB&aBmFaHrF`dj6UU0Y74+tnl2 zH20zFIh}3}Z5uni>h8RSGbc`+BHuNwYjXD_j!P$wUHi^(7IZk5+#h?ebHHT;Uu(_g zAJ(8n_*o!2Ym4xW?>m-={$|sB`JMAS+v~UAIli;M*8a^QAD8*t>bI-h_nM!sq798L z4f4WIdMYQ@b=Pp1>e&D0|IM#`=2J8QtZ zLXJP%*wu=DGy8UzcGpjCp4JU&e)si0KJlG(`**4C@1@-@elviUQcVWUE_5&a&3saA z3CE9Z?C+L-XFb-vgyZ*w-`t{qmawNzO6^PSPVG+VNNeHvv4nj?%ck~?-5a|%_HUfT z!S}lx;eg2{J!uz z!}sPJUT5s=ynJjx7RWE^UNm9BG{@gOzrB2a2Y3ID?6&z<3|au&@Y`UiA$+STL7ugZ0Qcl_=6oum7^A*g1Pd;&VJ;=82LZ*jTq z--f>(e{*#IcI1Gm21OI7!VXVN3=dD(x?isQyU};2@9f>*9XY<6{x*I0+v2z7ZxfB* zrgGiCgJG`s3(G$cyw1DZt2;O&AI$dZ<^VY#R9jv;8ssAX zrw@@^e)O?lI(p>Nr6WNua(KkMv!-UxEmU!KZ0auM_@4G%ShQ|l^WyHs96x&4uUJMs3r{%|Cvj1^BOzMWUXF!1&J0 zKD}vjgWPXsa4820F!`fwK?y+>G2I+KaclO-|L9`}A6!nefVMu7bX)FZ_thn5_HppN zTu}Do+7`wi-#0UVPy8M$`djY19BX@jD|no~t+TNLymYjk*T%(le;H#OlzJ}FZb6I5hdRj z{NWYt-ZEwDyv-Y@Zk({bo8!CAcLB5C0*Zz%nlZmUw|q~Q=lgN6W^>Wz?`&HNH_zBy zxaB+B=E5yPt2h6u-z>~^;Fs6;BGJb;F2C5)&|BNs($Fa1-QL^O-PG;kALy_q^`Q#}0xmUH04d zJ15xu?}FV^zU#f{=6Kq@9AYWo_mu99ppyr-Yy#Ijm0Os9O#Oaabn%?;Z&N_irIXtxwe=eW{>ATSN@66vh_D$-aFcHzD>+I_2 z`Yje{`dhI1w-U$iO175oN~Yfhe~TUM=;`bMtukr=)nv^RS|+uB=ZLuRn|ac2A&%d{ zNbwoW*794>?lUrH$4{ME&-~;1k5tjP?xdpH+V1_GOTyN0bT8hrxO;tCciqO^?z+^p?(Xz0 z#ofiMky&k3-ToQNbEai?7j$b>fVPJ)J>5L*cS>*kxsvaa-QT6U&n@cN&N2CW_>0Q! z58dz77Zi7M{C58>B3ihkdUE$(j?FXYtyn&zB3Z6Gs;0RhmgBqIbJ0DUGeYFM6QZK4 zihiqgr_Jo1I`uo}i!(>MIVSw>5^X7N&+p8RofS06quZl9xFx2hb4B~|mSx{rr>yT- zJ8Mnd(%$a^jU2!Ke(w@J+Wp*X>Tk~O3ez^N?$TB9JG(b^AKtchXE(=p_un5x58Z1o ztv2q6h|`@oW5JyX$K*i=GHf}p?!>xn8@}hQUcIJch0wuOdsfdm6uP=~OWC^a)w@?U zo^M*UdsXk6icN(FvsTSpwP&^P&j#loD&I>*eYM4+_h7`i^_c$TT@>xFZ{dd z@Ne@UyG3VDZmzCsZmOujuZdu)4bstSYy#Fjua- zU{=|x+PkfpgV;_`0oMl?#hCk?lg{S_U!Lc&G(LeH<_?bZZ^B{ z--h7sb@?m17j!SXcD{T5tdnz3_8nd|bJl|HWgIIDm*(Vw&25{I)!o&dSDKy>&msJ` z;d#fp&gI<;y1z5*%7odhUBjLuKMn}?t)1HQzOxfa;SSdNB8uusa=!SE6ixy7&B?v;_mt1 z1;H70^QLvD*L-(fdv8&n(0SoghR-GOyw>xBOt_`d0fgy{E*->$za*nV^U zP8a==(#s-4|3pb$^dx75@9+@r~|hdCLvEE33K-^Sis3R?Yd%)Lm88U6=>v zuo`yf<#!uAw(I^K!z%pmf~rcl8|%8`qCZ?x{&}gWW@ZFX(nZ)O~oy%){N?`49!5J$Mh+EL*gB#o84wHhquX zAoRU`Q^mT~n`W$^v2o41^3B43Km1VnzF2fi_xj#d{q4=2pn>A1-lqPRiizocVcn_S z5giq+{gZm9bWh;`4RW?m=2+T(q;qLE$M=6MvuD*+SJl>4Rn^XzGi%n&+46sES%m++ zc(t&ttSP0FHAi9{>p)g z9M!-5MAvU$yD4*9dO~K(TDG~rH;5KS8dlnMw{^!hcXrozw@mDu**$OWjF;Wtc_;N9 zSkb+rdqTu_u_lhEt+SfCtGa6&TS5KuxXG>Ep1ta`3-@ve|NZb?_PeI&<{k65bZ_i_ z{hRM{LEp?dlje2L=h&FLC?z?!G)1nvd~(Iis+^ABT+ZFv-FC;*yX!fG|Gnt0DUa%| z>n>SbyQ?*$KD#BayRAFCud_S5JHMpyt44l)id9dHw({8`0n_2aC3^STeP? zzo{szsIaN8d7Av>?*XEVTKhWYH8!>sG_`bd2+vRGozgR*ySIB{=fuuQ9N(RPFB1h% znoQ_kykuYZ>~7G|252izbW>nhNn&PhEQj#Dgqo7(`p)jA?zTSA^!3Eq-96oXoqZkM z9K!n&D!bdNYe0+V=C{q8eDLviu_gDnuQ^gP@94amwy-(P9LL!PyR@^U zvxMXKJ+{e5UtjP3z_H+a3+T|puC}haT}`vI=GJ%DR9EK2u1e}F2JIF|=+Eq}>~8ON z{mt^*xz=J{^3w8EpcRe16Z<%X?6En2jwvPiDGw7jJ|ue;=8Yqv+YTfnUD-0tY_%9FZ(hj9G<$p*UKM5(oV z7sqec-%CY**t7M_=$_p@r~5l^>=EM~tEVnm-@UDSN6G5gdDG`loYy^VH4?EcNuUC~|BQx8r8k5^t__T78QI-xfkP8~Y` z-Ar-ccizJ%HVOYZ@Z-u4N6~}pa>L4_e@ht17j`F32$&k!nAld>&0!ezU2@)mbt@09 z`p%v9Td96>>%2KrCQg_CF7=&f$4a^1LchI5tL8UveYxQ~!<-Gd3sU6ryY+HAev5Ft z{jDe3)YMiX*U+sxwWYhhTdlFVo1?O6!qizOr#@QqovpK2UijaE-xc2)xEs?eDFTE z?#%A&x|||VrPO`2Wq$N3j_yU9mvpaB@2=aB*|Og`YDX_@Vk;M)c~l_WZ`?)*AWRZp+DW-3i?$&9&VeHO&*I$N$+P9v{|Jh9uex@!dqMZ=rJJUHX9Cx;OF32*Ey)Jg zhdmwrnV>qhEDco0erG<=zNTwM_oD8vJF~kFf~wdYIpO!L2eONbv*m@~b06q#npoJ~ z+?@)kYu^hVXx!0opnE5W@O#Yz-6t2!>|VgJvSevaPEm2TobY$S1Kl+XO1u3zgugF5 z&>d6f*PNbyt>Q@cCJvAXyC+Us)7=lY1Z2LP@b~5e-61u`)b>+8bC2TL-0Bvsc zf|>=nIfcd9kmg3+WX|gegnD3r;?K z_`5(WXp4G!_jmWT-__QB*Z8gRJxJ(#|N8Ic>xJ)pw-x)5)IEP*_a(LW-QS~c-spak zzudXIs)|+kO;PF+cHui!bAB@;^DF1Lbm!-H8{M?){vEBR-tEHrUG{sJ==bu>-{ow= zZ@y=-K$v{r)4x}*T>bic!xo|MbJnlizHi6M4QX$L|9$`by;}4w+uAv;jSVraVPP@- zGvuevnYdPN&q`72^1d0f7WB-O|IWlF{CDk~wuZXe4paHxGS#Bp)n!TDHQ?%^J*zIO zC8xW!JFKs}JHER-w=#24?##3`wH#A+eRoK_a{Zdacl#}qW7bTcvt|DBiuDzsZ8hCt z9j)DI-G!4fXK?K52Ui!u|2pSY?(CkW-+KcZXr51b!Oex zhUp!XI(tA<&i#|7beBzToYTFkyYIz!*6j^C8s+W3MLB+0fr{p3-OD+IH++BfJNSXW zTx)l0S9@3W-j1oM3mUs?sw;9hgfCRgOP?9k-B;aP)m_%@@|)$iYpoT>iPfS#Z9VPX z9olw-QO*LXV3Ea-s1kd z{dan8_iyuVmm|TKx({^kUa@8$N9FIJ-})61O&sMvO6RjjO!=)pukw2k$iPWuKONRG zUfJ+t$0p_k?n!Bm{V)w@6 zZujorHoxfe)r;XW?35fUGTU4 z2~Rr{_1|(CK1o*`-|Kt_U3(^V62 z6u&cdb3D9w<;f$Ti#GD9-BopIRUE<(mT#W6vU@$peU}R+CN6H~a^30Ea+fEUO)gj% z-_2p;<7#4b(dnW5-tMC-*6-jDzA$t4WN5y>h-&vcQzVo+z zXNK5czUI5&GNEsqeiUyKuJ|DZIv&rPtLwX}XnuTvb8NR?chv6g6W!kxzq@pt{+@c^ zchYyip6=gD-F{Kge%-O%5ts9}bIkA|B_4%$;EPGdA7Vd_h-Q5A+Thu3(e3TkZH|ba zqu<-UPy8<5{XGm4uU_45DS1jDe;@@rIDwU~`7W^Kd+m<&_1``A2(8}pU2V&!<)1fy zSKA|;{@{lw#Fsta(?v@Yqkjwk7VhSV4BT@=UReLZwWC`$${p%{5k0r1cx}X^?(G~~ zx1PRsZJS@5Tz7c5eQ_p-u=#`4vpz0g-OaJ(P_~1eS9F5BZny1s(UMIZGaPY69`=~y z5MKWPd+ZU6x?>eDzfV~5{Zu*Q>I2_)Pn_SsYx}N=^C#@wv3t_I2|K^*A7K9R;isZ# z)fCoL&kFzUuUr z_E{&UE#dfW`TMJAcXUxqcRI)X8rI%rd%kt==w8ygA^TL}-nuQ_E4#N%**0f8N7WS8 z!sJ}DZvXD!o468Sb`-H5X+D$O9mMgQ>-W?=@83SZ z3s!vRlIuBsYVEOU9PLY3t*Z>9I&C<9%X|+o{2u@P%j54dPvko9nC!Ng!@>7^O5^u9 z#_v-WGH*O4nzT88+tqvfZ_53YF8%X_y=H33q_S=fCsRLXc|Lony6+ZiQI!60V1L|t zUANn$+oL!o-Iik!+wL2S&UGK^{_gzSYC_icbdIt=PuNh5`Yze{J?=Nt_c)>N{mZ`p zUM9TZr{IqVqJQ++Yo=CCfZ7Oii+uNwLw^jPcmL^R6)wOg|09E)4{YN1Mb}SXXM`Au z;>7Q-zW@Hc5v$=Kv;TA=OUZw4`B5x-`ulHhcD~JiP6@^?l;}nGj=<7b4>j$DcT(qY+`2ux^;T?`b~3Jbg${2 zUD}<%!S_4kr*1jOFwgDG-*5b0FWUVx?02bA_xJk=72SWUzn47i{=Jj+hcL@;Pe;&_ z4bQ8re82r)ZvQU3o%si2pJ?~n*vjtj9Xsbj7?ttW-M_1T%Z7D-uZpkg{#_kk)ve9C zeNOl9P7rJR?C#&u+vjwDkNz%u0K}Z#{XLpB{;ue6+0gFqo!?~-u<{*PQ1(OgKI8W_ zx0t^d?-0#q?`r`KdvmN}n>u6Jx>?<`x~Eq!$>vziHnDHQguV$JtJrhr7M$(g!|^@k zCi{-=bLDdiL6ZIb{kyRW@~M4Ywv91$YEbmxy-*ig2T#* zJ-ypuZRJXiEVkBm&`>!?4*QITb%otE-Oa6ywGABk#oalq{Vo0N{T&>u+2*V~I{QF3 z$MxSSY~7KW9wjv#d}kMw{Sa6?doAPlP3vZ_WB$qf!%nndW!37I^&EQs3fsqf6)-M>3n3zEz7 zYRcEOt*c&NxPIB%S!*Y7_&w?V-tnEG`?Wvo)b+F0FI>y0p`fjepd@YWcpe=~?sLebsmSb=~W~N3Rsx zubC3!nc!<$I>+x9*Lo;LT^7u_7qWr??$X` zlRKw$PwAf0J9WZjjxCp2h2K|B`!3Rb`ZsgsRIT4OtZgkFO#;VGOif#_eayGB;%%{II_mqAQ>t>zOJ-K&EABX2} zXVLHC-^*DiP4Allx)7;jM$2>#yYG^}(|+@v`OV_j-F-&kxBqvB-~Qca?sc;+?_N4< z<`Ry-vfs-@zl;AaXPsHRv}SoX$9KK%uJ7%?MY~t-Kb*xXeE&DYcmLlx-G{T2oLO7y zJ8HY@x@-GtC)IQOVEw*JG*s`m!IL9zS*OgNKXrchyzco;6B{`eFB1Lk#Wt^dZo`yn zj;$_pr+WWp{w>lycbe9B8`k#8trJ@&a(wq+Df)XBTYh(8O>HrU-EYb7>A!i;d}mo1 z9+dq(2Xr)^e{fdfW!Boog|qTOEt3m92fx2ub)f8f@dcsPXTCq)p10!r!_&e)f4YJe zJNa<^u?L;8>dW<=;g7TEcRw!SzbAjli~gRton83%_wRY^)gg^7?oAvy?0nxZ^c=js z{ri$ljWEN?wtrXJ@qO}X;qwhJ9JVfBnTOS;!}pG;cs(e2Wm zn$qdOv7N1Fx=?BjoJBIbU=4R2~Ox@p^zU#Ao&vgNriPgY8o4#9by0LvK$V^bo zrEmYPwe$PT)56~m{8;)UOZ2yQcj52Q&E2f?m&~2Ktea!k%IvU+yqswHl$2yNNQ$YT@?>u-d+T(|5g1uQtts z*be`co4Z;&+FKe+8)~{ZekYw}Q#1Ua z(|!7TVK?ilo-K1{&YLu?e`fcr?u9Lj>lbkR=K7w%ieky{61M8@qG@f@Iwy32w(?Eu zoz_2@CQ>!?>bNr5FZR@CRYU^lhYXcq0(lN1Xg539z?uXy)Stm^Coz&gi-P76I z+1=R<+CNf9{IzI<)$reE00OxefC;RKL6Y2HW;KiS@fq zzo_tx(%Q0?@@|gbH7nVscTeh&>+J66=xF26{%!r`w+ZW{#tCgb-5mWreG}x5d}m@` z*gbc~j3pd@WsZvqN2JaRzb^MZ{(Iiy)~1@)^6qjDVT+nh(e9-a7EYbR@tyyB z>g~ zJFrfxm{8SS%@H1+5hr(IlIU;N-`T8nMV0ks-5kFeg4x=-I@;yBy4xnSOyyAdZus)M zIVdF()YUV?%$ce zqq|v)yDR$Zrf`H`5&g~gJ)O0AMmy+qjwyXp`X_T7eHOivwWGJQ7Zm%TQ0?iN)H}VK zJ#$-CZ1= z6TAB5zI%4x`EJVE4@$H>-9259cl zK*8P9*DrtY6Z?YhIn$>v<`ACW@aIyssPOuR8O?Ls7j<)d5B{F;yOb@_owd2Pt)jbv zqdR|U!ICPD$cv)gizh6cGMnQ&$9LZ)HLMfcCw7364k)0zd%OBN`#8+L8~^s`V6AEE z=#=m71Sj8}dBMi49W5Q8wFeFT^^@v3emPzN1#q$0@9Dqee&_sN_Iv8@{O{4p4ujt+q8nBn zIwp6r`-0c9lK8~fNbhbAhl;Zc<$q)ti+Xp*?v3BNY|;5Mr@A?gtqMsiuW)ye|Ly%_ zjp*+szny-2|DN@`=eybO$nWdEulsKCJ?wk`_c_1KzI*;&C;vnFPoU`U=HF(&&3-ri zZu@QY-THgo_uB8~-)+7(eQ*43_FLy4vo$^3(77tC_#2eXkMy9>Dc|1JOUYe?LakwCU-*r}JUDWXN~LCQb= zW&}~clfLJJXe_$dKm9HN)BK$StT*mEtLXP&uJ1|Ti$JvD?;?=i-(YS47gjTtJ)H$J zWb)IwU{lgScUA{*HUH)W8A6I-3!g50x)~G*+n#>sdAegY^Y<$d-~3J!`yRmc^fwFG z#I)}vAlmSE3D|jHZUEO0-rrA=1L*q{-_74Qe3$sX0kmxIN5yxE`=F~%ejWrJ8=v{x z^1I9L(%+MR%l!8Fo$x#JyXkND?^WMBzN>w=_+BpeN9y}W(eEkWm3}+?cKq%4Tk^N< z@7&*kzfHgEeUJDa_+9P0>GuTr@AH0t68#>+#rJ(Ol3kncf0y{a0A}5R?{`GMhjJy0 z{SNxA_S@ul0@xI_?+Gwdz6X9c{jK&pLC#$4dkEJb6|s#u%QG@^@>4UG=WJZJYWYSv zkn!Jde3$rsgYhTm`m>)zB9FRfJ~V9!-jDySfdL_ zBi|1$$3;Jr%Nf5K+%G4prcA2UR|8~YokG;$aiu(q_cw}93~_qR@N;kawbIt?iYIff}zx%H3XWf#&No%Ut(&N+jckk@}&SCvP@iE7=W1oAqgbtp2#Mwzs!+wslu^moDv|&GExE zQM9eQy{Ds}+~Shr%u+O=y}Waee$V%*`TLLWpI9TiD&I9-a`9=<zwX6-Sg%wTCyGbE zyj-@gilf)SFQ2uovmKOr+9q{?*6GcgxO5UnkI}O3zK^VHrXO6|y@5k`kwf>amX&tP zIeOo+?wfaITldcHT`|j?7I6q~QRsQXDttj<@yGOM55vy>6 z!}8kM?GwtnE3?Z%XN`0*B)E#Yv1iPz>S>?H(LHI>ls)q}HvSG3?K#dW+|V$)8FVsB zcVlxyMFR)l51rrQ-&uZ$uCHC*eoE-}=?e$q*MCnw8@4%T(f7jr@#}A#5kA53^TiJ% z(dLHs5;>5k+k3hjI_ui&IQCrVJ-WPZW>INlQYuaCmO6nN|Qg zPOr16onxjS>!SX?o=M#cyI0q3F7KM$1v&&}QqSbRNgTh;c8LnF=*U{hD!js@`Fm(d zH>>c9j;7zC*}1I3D>>#_ob6`);r9EFXxjwV=GLaRrZ$e>M&CcM3ZKxJFr#-y_cV_A zO;anYnj6dIx*K}yCe(5KHvavAwP`~0gyxAHZEdW(6fcB5>weyS@xZRH9DmmR@C7-q zvA@2zw!5Iaw!tI0ucZgHyS25exvP2S!ZlMtC;3h3oYKkhd)8XfISo@vi|T9h1J8Bt zlWU*RIiq`8dslmRTQ^5Zeg93|Ucph;*j!QGG-dv* z88c?f33o_*Z~fCC8rhpLIc)~Vcd_p)S$}`|y;4-TizBTru{jc~KmGRsu!{5C1^+UDE_ z`409yZ$5t)c(Jj)qNAd_oZ~n16^G9sZ(MpM_gz@w`EOyl???aoiTZzUE1moMdUr{s z|L?Zl-3PvBvI?1QdTv| z@mc+dD*K+hp7GxL?<#XQu03>~`FqY!CsB(?dn@^FyFF>=qgGX}i0F$2otV+p+0<7) zH)nPJijtYtpzXiy?M)mhEAnSmf&0y!U8{bFf0sG+`gb_T$={MQ`dI7FwI1u<*Zp1j zyTHlr@2uTBTKcL%M`qPG*Kia@uvX<27v`0gFKn3CJ*Rv9t;5ChW83O4}3TM?)=?s|D|%qPy4@{eRuwDdVu-o-0yy(j#pTFXY|bMp4mO4Yvv3d z)vH%oTNXAhs$ayR%vQayVqWPSj!*0*GfJkGOyqEKWUcG1?Ew!{cGdByI$mWhZ7pdm zspC*#FRiMms4C@9VXG@?ENLy}xa!E-S=R+t+EWWs>%=;-WNOKb5{^%7b4uq`EUe~G zW?xjlsBvKn$JMK>GdgE>fk#$*X7H$9ab#UIY0=b0(>Xq~FPuGZ-t2`OpV_7_n!0Gx zB91?tzokL@E;+w*e3$$#0oo<>UE;gscaGm2za@W*{AT{m`&&-_yD7`gty^~OPTCX` zo0JkAvn6S#Jl_x7?~^uvSNtCG+i}a2ITvOM{Seu_X8Noh9jn%C7M^$DN8e8e(Sp*F zf`Wx5%a$)%xLkgD@xlT*;qwlqf1E`BFtXQ9YMC`_@}!w^KN#5$P2Rb;`(*c$mW6fm z7qu_xT-yCzqWinxu`N%^J1ROWx>dXF_k?tF{ATSw_?tN*ot5HrdD-eUlR5alFZyl0`Mb!H?_Nij zP5xf79yAO2lh+ePFvF5kD zXz`rlS;aFs95=A;nY(%Vs!1~gCnxonaeQa}%_o|Ynp;p>**CX$TKA#u)t#%l=55Mc zQLvzt=T0{5~k^5gnWrTNS=Ic(woL_RSq@yO(w^?_1r!@z{>Ts}IaO zUUoTZLm@|hPES^MW_Nm5b}o-{gCHvOhRQ=XWpa zT-;U{-xS*(lboNPlam#5uw+;F*6uS$m*4E>*!0^tl| zyJF*t87tPVEM4(kY=!XmdWD}HKb%Elx)UqY(I=2*1}@z;-NpR{ zlkzgw$IXrF=CHE!Gw(3gQKLZ zytrjT+HU#Iyq21V?zZlZ37~^vdM5NvYMIhFp`n{2IVU+SZhiK4`IX&ErY%^|opsE) zub^+)vCG|S7L+Y*SkcX~bM5LKTe8-t$~Sa3wY4<$wDf?^#%kzj108$SI;&#^$DaPR zQJ$B@+oYgQ=>M|pSx_n z9LQ%&I;Pil=Wt|KRcCr;-n}T7$JSl6*rYqZySAaYG^2D~!{Y9l-K!Sg>t4!{$JXVX zY3_VE1e)E&XGg6%uYmG1)I&YlYK3JQ%3lH>a!{99wqk4>3woZ8eFNYrzqUV3N&i-vC7u}uQRoKbVo5{MWcU{kh?(YuW-xJHaGrLO@ zqD&k(78bC+y0iOO_ePH2*T1WahJP1{JDt{T-Tj;W_p|KZVjQcRShs(-|IYG#$)@i4 z-G>yHd2zh_tty&R&$?Or!tdIi->ltbZ4HgxdEJ4_j1O>B zVle+$`lCZMqC2)aIXh)_?CfyRE+h@Z?%zV)zgg}Xd}plZu+WKqrN8_6>)qP-*tbpC zFl)`SH3b`LwzY9sXh*-+-~Ig6Zk>DV-x;UhdkSjrd>7Jq`kS+xe!X;*WDAkCv{Dp(lfPZYB$G=YtGu5b}k0; zziq!;h)!VdzO?Po-X$Du?AkvK9;}LeIjCkmqSjV zQ@0KW-*?U547=~|{L!!*5_jKKcYjyk&HTN3v8XvbYVNZ?oqFlyqPyR@COv!!I)36x z-Z7B>u0D*){~qz%$MHc_f&***^t_3=-74Ky9>qq#Ia-YMx_`5G|K@mL^sSNOd&~ED z(H_uYIKAE98SNga%vp5m*sdGhFS@UR7Wf-^c~~@X#7q|jw~E`WX851)e%RgH-QP3e zPTg^jZc|Wvbw4_N;r_I)mhSfMuI}GVH%&iQmiPq3*mWCp+g;|V*j_*0YZA8CEa`8>C-wjRVPeEoUK-0i?p=a;DbINr? zLS@V4d(n%xSnOVZ3K2T#J{G%+6Ys?&I>FgSKTebCw%Yw{T-s_+A**7cD;DLOZz_icc$t0o^*d_gWC4ty9>e^)!(kH!tX%o zET@}8_+D;zUTZ-;N8;ssyNkD4M5mv6u;;hW)%@QP5BIG1v4~!rcyIR>XV!p(yufZJ zj_%*A_YA)?)pIP`DQaMM@twTzyBC+Qy?%Si&Oq1MK}(L$7UcPz_YmoX`TOc0=A!pr z@7BKZmi;&1cZ1)2zYX4YcRT5HcSpbK?zUk4&0KF~(EXc*1Cm!@;q;x=;^}Xu=^Vv> zgu%i3eaU%HY4KeZoX4K-yuX|I`{W-TqL6$5(JTL*wfpIv>ED?-?(6J+by<_`yWw}f z--h4$x;34v(|rSHn{Vf1zbRYB5dJz_su`7H!~gtk<8zZea}bVr~jKx z^Ea2C-fu4X@BN_TAfveW7JhgC(am`9M>lBx*Gcqy6c=CI_qNTTBV53DT{6G^5evE1 z4}7&`C_*k~%Xh}j-(xp3ANmn13OSlYZhQCEnQIrVE7@ARy_*Aa0pNEot+&6qRRF{!e5Z&$E?Y}r|V`@=NZc}zQN5*f3W#2XA zzu)<;ExKgZswu0xIlil``mK@uw)p9O z-KE`8rLmdWxl2p7bRX~DyI{wf)f{|3JP>aGw0Xkj$y>gE+RXf;{-?RHU^-oxKaNa66^mg~nGkd1sl^nWmYU&**{^H2ND%=>;^ z7VZA6@V(dV$FvKq-G{pFf)48VeRkvTm}MNlQ#C}tXB=hyURdzmt@u(SM~};&Y@gp5 zM_KvoyEp&1zM1jb=E4cQX<>95& z@8aFxi$OIWxP0RH&Ta7Kw~*W)5f)H-KHzuar}7EL?-NdZw>!c7qx?s-=x;_dgWv3O zIlm|WR`vU?`dj|QclqzCCw{Abk6`;QIqNOxgiGn}D@7+F_C@UTTjJF1+wE8E7tOID znswi%J@a>Ub9`rfX8xN)p6|P)-wDR=_f9bX6#C&V`g`T~wZB*XUi-c4cW?J^zu&6e z-G1LyySsnOpXl!XF8^JnyZgj%mG18EKCIu(zW0m%4*4DSJNSFp_u%hgtbE_a{7(Fk zI>Gq;?g{1}zkW!Gp7`y_{$1p|aKiTABELntzeoFZcmIz5E%IF?A^y9_Z{gpbAoD%D zL9*N8zl(eqVg3E?w~F6)6(k?MWB)GlTV&gIkTzDn?*ZK>zGt3b{Qe7k_v$Ip?`Qme zpJD&LH(m6^Z*KPA>c5?TYkYV9&E4JY_pJN7%kR|h&fim6zuW#^CVJv`DEn{i->ScL zzN`KY1zBwiQepeu`MWJEpYHFd6W>ja{diQ)xaGum_7mSZPB4F8byu|e2g7&1-yB~cs@_){`c(E6T%<9@BiT@+WlSe;dlM- zl06*#>?u<-C**XSblc~6`g-_eI&_1N7h#XJ6H{vTarOs_wND zHcjQ|Xa6nPWBOgM`@1p+-}mnC?I#$2uKC_BdgAw0_V1?OSBI_qZTfrlZ`1Fq!*joz z{9gV0DkvPTcKcH(yl`}gD@3xgK@ z&i=FGclM7RK_%al|1A7n(%tRH%6DTy*^eC{g+F$Pp7?Xk?|TXR@8my=j+B1S{;}hG z_MaU`7XD8Dv51xL<8N0`u>RmW!T9#XcY_n(^-eSY`1;#b^u+Jl?;PLrezg2)`P1?} z_qQwi_w4UYqQCXNyZZfh{cZ4r>jyg<-}{U6zKhJ;Ja6Otjq^6nn?6sdd)~^`-Seid z+%)g-yzl(;y63H1v2ermc^#XU&J+G`IpO=GA8n#`zo!1?J-zP8o z&LH>Qiv8p7sY`6-zF+vgQ&jlB<%aI6rkbi+j_*S3QzlKBJb5aI*Ka4$@6lYp`wK*k zf0r-$@bY`jqI-|NSC;6i{Vp#ulK<}an@_Z~t^2zW>(stUpmXjbe#?jo|F@n{)6vk` z)CDqXM(_0AsXZK_zokU8+6y|%x>LI2CWKGr_%6u4t9fVpw(e!!%X*gear|!h%_iE^ zTh~`LfusAo(56OKx8Dq+!v8BL)J19AtK%~&dLRUuj%(?4t4hRKVUY-nz1 zC}_@*=X>8a@4N84so)3z2S4EmIG6JM(T`W6j=yIVs>=Vi1VzBaf;;!VPXI@NCHu$U zGnP8Y{qbV4`8|2rH;@X2?|lXLAAav$_D$})CA-4!$pzMO-!FVu6&3z}E~mSysTveP z!t7HgO_@At3OIsVIvU#>Iyk-yvrp}t(m$o2!|$`A zx$Om=#obBWaT6k^fRYm2oL*3RIeJS}`2W3>#=ge>rU@Lv|KFv27iOQ_KDB*XI|m|# zd}g20IJIe76UTS4-ydQ2K+~53*Z_F)dY97uT?mxCv?0lBE(cUIIC=dADK3E~FUN}8 zcfLEqlb1bJwqPdzDc4o~S$BD;mG+{bt)V?|W|dcfH@zp!~hO?@rtfVLJwepN8KPMTPAc z9KNfwgZSMAH6^*_9KY4rSI(ccsCy}gupL8#xuNKHb@tlUruMcjj^FC+zl(p!i3-~_ zIDpHA-wfa3NfKNh$P3#sGJN;@qaoV;UA?rm`?nhFgq}$~Q~NlC>zEiK8bF3sbu@If zfDN0~JFRz04~OtR7KYGfkW_X@L1$?#! zk3-l_fZ=z;bdVh>Q@VevvkKceFyu}Fnb6eN*x%R>^2Cf*(EU*y!uAXde#=2p!gdV| zSqrKccP|4A{#Ix2E|^`srVNz-mBD^8`whzV-|gnzyZ;?j68yHQFqQxAz#?p?!r=Sc zp~lbmyIalSBj4R-ojmc|b5^MQ9edHjg1XSKf*DJfESquQ;Ig_RIjec1#c9nAbsJh2 zuUj^G+O)LEMWF2eU2V#=Dc{xSZJxhzPWeJm0Zb^0zBh3EH2i*4RQP*?z;`uv;qMI` z-Gz`SQinv*G7jO-4GNGbYHVxjXzBoYd`jPxzN!5nDUSQ5qTkinVY=WE`}^^aOi|&_ z3k1Mr3!>BjmnibWpBo&Iqfnhy_;W);Uc2b`7%q@~-?e|siFSWi%WnWhFGzJ-BZv=) z?%x*QHAIC!AKAb*8Scyk#Y$dBL1$4nhw$enhNSM; z{-`M+FYIgD*|w>hV`2BQ-qn2|pEMl&Z4I%%h2i()@4g_NReg1RO?}`L^0|@W`|{rr zAkSho4=wq8p1|dr+CHU&L-_jvaC!j+_yTx(`F?=mw;C+H z2!C#2_%8Ol5ael4`cq>S{yYJk{?ynJ=?`Y!XP9|e^TBBkW==UM-CY7lgWYeJc{i>j zr8~z8D{#8|-3du|e%~ExPM-YkIO_yF-4#L7-4gV4SF^8u>eT)Hvmoj2hv=o{-`STn zFaLgLxzM^5y(_+RtY}{;eD{0zk08GS4xui-cxvfnQ@H7!oAJ92aUnYy=SC)2mM zWoCpIrh|9Pcc;vc?&gS1NsSFY5PAII#tr-AyJxm6=$^zeW%9)7a>94_bh1wGzA$Oa z1db2C*`80<{?7En>=3KtR_}S<-A&zPU6oxNzvtGl{ucPo9hTd@oI~fkvgp>WsVO<7 z#RYLOn>Makx^$Ji@V?^TKSXzK$WF@4FUW{b-mpb})$%3l<@kPZ+*$oyX7zV55Xrc4 z&3C0W%-=hH%o5!nvn9|kAt^9mf8rIni@mHTy0@3iORPRp7<-6BaPwAf1y>v#-cejfi-%tK#W8G4-vK4f>@zQC_S8)8{`>ynZ_qz&f#;UTZ z>D>k0>CLIN9KX-}=39b~cui;QUA-Z){Zc0i)eriI->P;J0 zEZZVK_4hr|4a*DC;`8!jQ}UOt-?V<+7J0rOq8C6x-?H>OJ1G47R(xj%h5xK_&v9J?uXr=0NK-Q8;!?CxIQy{~XxMro`|a9Dj>({H|-Zf%aZ4QV@19NK?a zu6tF*{O;-9%TAVowk&ddpIgH^r)y5v%+&(v=%_ZQ3E9`2Clt@VyPo3p0|^ zvt!cpm#p5rX~Q;oz8})vi62MkVtM((BKh75(HYa~YO3mMYHJ#2%$_rS<}7*P_Wd7zD?bvg z>u&35=wGmA3aGrl;(6TCI3U1St~+9W!iIu}e&0>@c6)St{MOuM5|&s{n%Et}VRS6u z@%aF#JVl-X!)k@ z!`+7#t=Y)&``Y)!?>E1Pu}1Arofq64(H&gjnac6oOoz2(Ud`m%ZjP$%qPqBQj-21O zL|vn!+?}KMT)DS@=QFt@Y^T>Q*djlP9kiIatC!>Z&hK`tYv!HXe!P2q_p*j{IUIkg zes5xJ>uBn1?dA|}xBuYU$C{m4T_fLJy|SS@oa1}wcRtZiC%+&49mXoW*#1LCL19C7 zcWig+Y=^xZe+0ju5*2RV|DkGW(+;_<-TgCGbkFFX)IOz-LpPYUdO^Xolx_|WHuI3Z zH!knqcmBe@XfOHhu)O;2N)Ep7Q)?G|FIx3Ie)0Fvr9$ghHm-ZK^2hO&!ru>n6YVbQ zZ=BjVxBOV}&hEM0bEa)w|KZX1`0p|2Z+zGI?l5n1?_`db>T{N+6Q|;@PIo*r9*XK{E?&gT>&MVB% zwQc*;7i*W@Jw0*4l>WYMj=uixR(WIg&UQb~-weMwTpfN#{Ek*PD=4XJ0A1)Cy{&dO zN7Izn$=!=N_`b)~t@)8yHuJm8iS5&YSML3~eC4d!6Z;nSZ|0~Lqg zw%uwR!aE;y7f$c%Zsri)^Ps!AtiC&)!~Jf=cZToD-_5@peUJGb_1pZr#qX%!;lC|@ zoBa;?&3N{=z;?O5?g>4U`n)gYUFg2g@!jRS=6Ch)Zr|O%YyMXM?eg3Ex6W_X-@*DG zn-9&LICG+0fA_TJY26&(_x&&x?Vj8*CvjGLaD8TB28ZyC2Q%8Hch2fw-aYR?_a=_t zF~2X0R)>^irIgHRnBP35dr|j}@H^Y;y+H4_?p%DPLtYddS(Im&+fvM!muaN@G=72We&7glq)G>Z!V zYY2*s3klv4d-(8<9fuFa?F^Og`#wYTcl>Yj-^RbAe#d<``EB|==6m#aQ}BMsh~K8) zEq;f}cNL1>=)RYAH0+A!M$>K`j^94NwSH^;_WAAiTjRUtcYnF=+pBILI)3iz{S6;M zt1{vym)%`+aqXuopVnSn!}x>e`#RC?hR&8UYYvm&ksG>M=XOu*pR|r zo;;;>a;tn+_nwQ#!Tc$cTc@>~aP4dAaeTL`n|0E} zDN`ncH2HT&U0egxG`UsYuRG-sYt`b`{>pBSwC+d0qgdz1^n>mdYXKiYUsc~+)Wwn2 z#hTdN&=%6ck>2k3E@IM}-&`3D>uR%pbM2dS^&RW(wp%TeyH9k@?dmS#;QR1hcHXz| z*5BqauABGc);wmB2S1g691#7>@W=co1KXdsKlntyr~EeWZup(@+nn{sty0lHx4OTl zd^cmAlfJwHbjaw6d9&AZd{6lvDcb$}>F<>9=H1g+zo&fn75$y^+l;k(ecrs(?zHaQ zit2QZ-ynT_$&DM%u3WSB^2)|#XEttMTeDGU#ftfpyVqP>wRX{W>CMYm2|wMp;(M`Z z)Vkk3-y^?^e;4@p-Bzx9Q|tP=l}(Epd#WdPEbLs^J*j(b$?BBujPAmk`fLv2Z~Lm| zG)+qH=7=v!u8_}TubEWe*W8`gos`_2*InCJIiZ3hKdmTU{=4RHG12d_T*7bebuZoa z`qdncW$anqd2OxTncdb)y0>*t?wdMc`rOv(9sOM#-}OpaH@8f4?XKkbt)`>;Tl%-G z|98%-a>8HkeUIb%&innoXiZ;zZ*zBRcWqZ)2Z!*pdA+k{&z{pgw|ja`UlqsBHc{cH z^CoYeIeT&UwC)*=-7VcMz3HG_{AAzneQu&tXD*$+{p@dp=EjD`dbvc0@4U<8zgw$_ zevjwkt7u&Pea5m!YdY3ETYqXz{VJiAYuna*mss7ibk(xcYf6?1zbyHY_F6Q`y|neW zeD`nJ?vUf%-(|Y5ojS98=Dx*;yQg>W?cUn3t!ZLOcV%|~hksB0)$MGc>!kIey=B6BR!3p?m3^uibk&W=)+jeZq{^X1VUl&b%h=n)=ex zrV5Vln%_5wc2_khwApclSF;MADVY5|c+$S*piQLp-E|!Ec8fMnZtkt^PVX*G>u%_* zZz*oCZs}|70iF4D=R3@&x{w~+ObHd@5vo_7xG;2@S z_U5(ihx&K-&g@>#(K?}FQgwG~cUekvb?54?jom9d_O|S3U)a62bXk6Pb$3&HeItkP zsSk|{YNl6qmv>h+HdJsRr{wRS)|9S?CgiR)+dxU#Z*|Qo;ipo#lX81!)GQ9+r#1(^ zYfj(Vy%AzpN_RnIo;63pZ&p^}&la)YnexA@`*r7b=jC?ibcdYytuQ?rlC(L5zeeRH zCFjXwP2DeN5s|v}3t9Ix_jqBX?vJy+$8ml6{y?;*ub~GN(+!|SW5SOunrHcc*XrKi z4T`kxh21mDuKq6JI3gzeROaUQl74VRPpj{)>n=_HEtc=bA^b4n_gV+h?x~ZfE}VU+ z?$B@L?q&`wN&Wl0@1k=j>^U}v@%x;W1cXxMp z_JKD!P3Y%|l$MPAhi>CGU&Fkj)F2J^6UjKwibCRk`TgpNEuDV-$ICdRi?VdGZM)#ENzRrnL zc~p;G{LbTgwuyCWcwbR>PIqBvX>(jaWno)EcU5;|kN;HHma>MTZVvVD?xF>rtgW>b zB}HW%RozREHZ8B3y7UQa|MZ@o1@kzron>7(|G=v0-IKegw5}-O_?n&MKy;FL7Ic6?noiYb>arcz&h1uP8-L38I%^l!dCMGubwt>z-t0^c4%>;M1 z^>BP&|J_zJE-#``uC=?Vr>U3YNA!0_(Yc8e)4PMaquaCU;y9F=SZ5uaw6}Y2_tMU} zjh8t1zF*t7nDIN``ybDaGk*{Ju}?HRIU=!QX4RZk8~1IUUq8EA{?a4S-DyE(qEWLrFa@@)C;iCulI(>Q)BfA42)nBO*`dJ4xM&+m<*YkzY) z{TBGmTbT7-?80{r)*t`>%ohFa$@cQp<_nX*GjiPdEyp@Pv$wmuiKDI}CQj~8(vJnA zKjeP5u=Z8wLArlQCC4Au?@l*-Q2ePhx~@`!ViSkANa2N!$!2I zHa{|AX8w{TGnem^-&?!9NUrJkdeI+=Y;$`+iDybrchAhJ9N$a78?vsP-_zSYqkDQ+ zZ|;1K-yjjtIrxp;93?$}61&xYb9euCVErC4MbuKm@>TbDv%9bEYO?;Y_+2f!?D&j5 z-8;Is*YCPp#@1?%-$~zdMTO@dm|ihGe_9sDACYd)+pNO( z4_y4+D*9dghXnhC%HEpptnTd2nzl-gjvUeIgtq#|GLAn^-RG~f@_oPd;|C(vbidCL zt*WiANZb^$X7==13$~{1&6fYp@;h7fhxzwOtW&1UY@N4(yL%s|B7}u zc6F6yb6EYBWo`b>kbGYE6vuZ@w#|!^D&_wy`jaHO{PPc{?|H0!zYNYh1RVRzTWWN`YrXH z<+tH0r*5O}iND3{yK}lrme%dz_^rcc7vgOwZ@tIky4?4wx1x#N=|#249KYq+V&V@U zKfC?FA^Gp(Z1bX46>aW*)m{Hx>|XcQ?(dAAt4cY9Pd7N|unRwKsM}S(D5skvH77MD zZe`kfdExsFtGgF2p0S7Hl`~s-vRZU@w{7>-@4R~59DF}SKYe#_{=WWq$h$?~9fX7p zetiA$K(x5KpuW7qKX9R=)AH48<(GFateBa> z!S~&&_PZbB+V6#;-AQXzHabnNp4hpidvEvj{%P~SGklNzUiJOTcg^oc9JAZmL%Q8+ z(zC3bek=Y?QvU5ySJK)7x}daWa?2c!yi2UDv)U%qcb9hC7IY_ryN%zMF|Pma#Z1+H zB1bR#y1dCXa=zVFh54C9IjxhrwV|CvzVD6;${2rU|Kt@7IQDz z?Fiz1clhq~-2o&9<~g$RRsOi}lS#C*$d0?&|97>gew3>ga0cm{&HVn|0lu1uN$+ z;<)vhby?=s-`w-NCv`7fHhFS?_q<9_{k3RuPY*}OG}i9fTid4{t)1?B-TBqYo3|g| zxY^a)1=@`}VQOFhY>u*3HSMgGGrGIG`?`B4Oz7_C;QQ{>z3zMDy0y>Ot@|FajuA|* zd%hOLVgBLplSNejclGaT{qNPktDb+a`d;hB<2{kxhi z<9meY@9OW>zfFHv|E~FM`n~3N^>@?n)!(bx`M%pPDEnUZy_)g+-0#)QKU#jegG{OV zUG=;Ad)4o%@6{mlK^C#`eYaas_I=xTCdThKzcVra@c5x3`kU$d*6&R0zqj>>e&70= z>GxLlt}fBvTS3BX-H-y6TD zvi}M05&fR}z43P{Ti5R<(cg_AaklSiU828Je>Z+lW#iLXQ1-+88RPf3XUyN@Ko0f% zZEf(~`j7duALie!pZ&J}?#a%V)_v*w>gz8Se-~KHxNh-xzQr%DpSj8W-4mo<gMn1i7Lo$UEAHns_bT?D_8P^ zX%V}2ug5LW4vtyu-)*<*_{g^{`oUbn-mYWouGr1d)Lq-vJ)8AA)A_qEUY(|z~1qUCRu-!|Qr-&I(@TYcXkdh$2ZZ^3TX z??G9j-+8DL67~Ac5b&LW)nkq5Z~pERUs!Lj1bq6=-ObAPJ#uo{ z_jTX3g}z_-Fy;wNU>l4x9y^$jlSI+(WzO9i5oL_ z%OCH4v-jO(j&^q82i*Yyzm>WpIU?7jZQHVT({7NaL(xwfzB7MneH41Sn`7^WRa>{G zu8oin=uS@ity{{`$KHMX_;#CP!@exl*=ap9rc z`FalF6-!sons}jSLhk(Xm2KS~tyNit9Ksti@@fjZGdaR{#2+}Yefxen z;T7HAjC{Ml>#dwHfBuA(7rJJ(F3si;KCwJ~S?!u`jsx4bA2=AlJzPGsyRbGdokMs< zadt&ZNH<4)7i-(9^7*+Fx-U$cwR$Os@P_rvXD;hr!*L*fduUjEe7Ib9=FGzNc^twk zRuxaK=nmoNsb|e;D=%-$;i&In{VmnK|A`W4xCi8&%I`COtF00GKL7iS-x_O#H+-M} zokP^+c)-QWM~_|xIq}19_C?)KpLEav&HAaEte`<-VF zM+X+nJe?w$xrQ`*gInahw!t6NeN2}*LSb&UcRDxEr;;A zgzmX*GwbJbe3$*L%vL$09yGKhd|n}=pdekoJ9Sy%whj*Aa|_z)I$OINInr0=tzW-t zvAW^f3fSJ*aT$&&7k-RlaLWpsm#tLm<4tE}hvuKZnry{5dazPqwJV|o6% z?hW0G7ERv4A$%=iTI=M_>D?Ubmn_||zGzvNd_#A0XH84}?y^Oh-Ra%=1)y=;a|zw$ z6Y6JGar{>JF2}a8ZhHHy?seVER&=ii*_1qC!nEE=-BUO=E^vF3Z99y>3z&<4@@ym7?DhxY90&eoy53 zz4A9Zf@k_;lW5>?ncoKW-?@}Ce`|jid+=StEakiQch1kh^{2Cjd}k0{^IQJA`qbZi z=0|_a{N{M`n^o(;cbVS;_rGg2vhsbe`(gAmS@gR-m*)30(G$OCeP1>Acg=6TufM1M z&iwB4JN38E`|neK*MHahzPgf??|aO;pUKQWRDOnu{*HqV(#ApuX`{e{v{Bz-gS4%M zqSt?ye(&i19rau7^6%Q;Vc%7MC;nEw^}Y6Y@OQQEZJ?5G0w`z=e};)#{NC|<_w?_5 zzYUasZ}?vGJ@|Xu@8I98-#2`p``zI8nR-^638F`KM+F8%M+XE%?>>HP_pTH2eBbM) zlrerk@V$!phw;xaQPtlazgLxgPyg-X{d?i}+V9%m>wasyeP8%J^}EmSalb9TYrp%hp%L=k;yd5--)h~gZr>k@D*ayddu8|c%-_~_zn6dS z`fmQc_qVy>_vPPA{I>Zn z`8^L5hUL@C7=P&fs1=R-&H0<7`>{*fZ?W%8H@@0seiwUj={HX|>zSvbD&Lz}JNvu9 z2h?_UcJipI{%-m`t$?+yqoZBEyQ{aOx0B<0_-_Nh-*da$S$jHqJNiJ!{q*$oaC|TO zuKd0Dw>oPtxbeo(*Vo@C-_;lJec|`e?*^>By}fi4zZ3%^_bUfa#8^Sf8{cgAns?-}1Wu=cW8zPm$?pc%-zDESiT)Oi{vH2ajg{~Fqse7IJmxX}(Eb@FYW#c7?+xAG z%YHkW{+|9_yyUyvcd_qk3g0EW4ZlzSp8Z|VXWsA3Zp-hTtiRbRMeqLZ`rh6BJN~!g zwcpLZW54VCPW!EM=X>+-pzn&`n?OOXzo3lq`@Zj5%s)avjVrC+#caQQzjN9Cmi!+1 zoAGz#Z>F2SCBFN9=j{HS|6QxQ`+NR(pWmD}zf1g%{Lb_}@;jsLcgf#AzqwfXzDt3O zI{uxJ`G*O}D8}FI-x=9{GyVw?{m%Hk{Wl}q55eyhq9Lz-r~T$TsKffl@3*MvA)W7O z-}ypcvHmvrULqQz^F94H-@#X`-yijeZRdjcbm}n4|_5e z32*qJ_|si9s+(1K#mwJVf9HI!F34sTzVN;BcLuBQhTmsrbSHqgXS;t_{!ZJF!zz5@ zd(!txR^bb^zt8XKW)(hB|NZ>;?BA8ER;dJ2WUvFv%90ey}!GsyBC}ov%kA7|Gk1$_&`rv zZ%04qx}CoMzFzr--&cQ6{q4ru+u7IE1M0Sb(rEv0$M4zS*DPi2@9FF7?&s+5?CX&G zy>8)mkMDV`y}do4blcb7*WT6H-6i)s|F<8j@PWMVt5}5>^mp{N%LyOo=;-W}=ldQ% zsqFiQsom3se#lK1zVJiqXQ*g>ci`{B-vQrwn_Ir~{Eq)!8qwYOy)^7M&+ov=&EI3b zbM5?{GrjxJ_tNhHzqwhbO#aRJJscEXxqG^&|1LQAUEq62%jDnjzxi3!Dn$2ukL&(@ zlJ&qB(eJL`S$-G%E@1u6wn6lJc<@ z`R)kvd2e?QM_)&8r`+#etI+QgS-bi>dqKyLb#`{ObAOOcKjXk+ljTa3lwJE9iRc;ZgAXieDAt_@b@H8(a{MSO6%$E;ZZ&KebV4M-tIoIL|1QT4+r0O|CwcsKk9zOh;I8{#o7bv6!vt3BBQ&byR);SgX6cv z@8I9E-@{lt`#_1Ny}J|aj_%H$E{<)#Yk${m@91Xj=m2$3J9@#6i}UY=`#s{j%O5fwZE3x+V_Vj{^yUyNrxehkI@9rRPEdS{* z`dj0-?QhlZwlja{{mz~J-R8IYZ=2t0-*acx{x18j`rY=s#&6r&?`7X}YkpgQSNm@L zUHx}1D5u2FFZ*d)&iLc@PZQDK3ctl8zq`l%cK@#2|L>s%1>eJe2mbE+R-5sClE&|)-Q8K= zmv;Z2q%||0^?ToUrr+VegO?S2_x~QL_`837?VInDx_>WQ)7|}j+3Vkv*3DG>-v2$| zcjU5VtiMCQ2Y>JR%`|iU@4h$R7j}2A`Mt3Fd*8d-b-#NQzK4DfELiqC;CJZP?>!ka zS+#%nb$?&T%J-dXYT5Ut-)%RS34LGq-R`&YcXr_i-{*YaEt=Z#JGi>rt~+~q?#gbC zwcST|-ulkM@!kEC=*GV9!Lz$>bg#}|nb)1#?dI*J%kkS?%<+rQfz0mQ?)=>D>~6>J z9+i9AIljBAh@R28;IpB7W%u%x-K)E={&t@m+0SA0T}D*+Lu^WFT=d4&ox3+~+_f`x zV~qSf7U370AI&-1y`N)k$)b$Rl9DXB?y%WGTa7t>C$l7(R0Vd2b7U?mS+{o4qP23} z`zlYQJmBE_&OND2=zHh->EC(HcJEy${Nnr0BciFxvsSHNx@wD@@Qv;hX@|SFcF*pg zFspxB|DySmyEk^9{4ILDdsX+cCEY8#zy40k>)y}NJ)y4$w6>z7cLI-U_rC9Gx!vCx zx>pn|&FQx9_WdoI3Oco>uX$qrlA4ulvw?T-GPw!B-R zJ8xOx%I=%pN4^Vg=?C8o-pV0-V|mf+S>1UY-5Ke2-BBDTzW)>5vN|gZ2H4vX%B-=YeB9Kr{>Kb^bXeY$%^`MRL_6|I5A-Ju-93%Uc( z8Fp*@W<6c?UC5-npu4g;x2U70tFF7L`#1l0+XLS@+<&ur|905Z{hN=YyQ!zHuV!V@ zoaW`-kGki57x~V70%Wk7hgG+Cci#MrBjrcu&sz^V+kZj#QIDtHZ#llRcrExXa39pd zXPI2~U1{O>obS=U!{-UDTlhU_P1g66%r)PG77E{L_X(#-MQtm3y>1*=nYdTO^@cTG=KPsP@fl?`*dS9kATyl^?k zyYB*Czm-@UirUJ$E4pJ>!Q4!CF{Cpb+4Mf zbz#rEp4r{Ax-X`0behcZ+ic!B^-}`n~6@J+E`!L*jQKJICaMK zsZ(djPo5`Q)gAg>m$hwH2k3s7-3ylQnY*`gL4J31cU4DCTQ!H~?^034-vX>91-aEl z-9_CMlk(?xP3xN0J*j)|x=jbBaC|pi#(KL}bjr-{rUk4An%1RsM|QV#HFP!PmshkD zbr*MM&naEX!S}uKyT#5U-`5Cz=lj0qxAKOP?`wWbe&6uhX@l^Q9~R%mMKhQFX8(Hg z>V+$E-7mDa<@a$2A8KyvpEz;C#EJ4Vx_c{6c5~c1a^U(s|MO1r+TE!Jpoz}b3EdOB z`+IsP^m7QGSTuM3zV0m?*8&b&Sp|eT$aS0VDekWCZfk9A>uv>AR+DNsdu)m5=5X?N zx3E7Fc3b{@cl+Gw-5kOfCQfK=ZEb6BmKS~y+h!ZmZNnja!uhJ}jjzkTv#*sCe$qXy zeNy)XjtLVcOq@8Oy;ZKet-YnYfursFSyACjCl7>qdxwR2dxal3bLPOlv+{i3%c^&L zZ(QZF9S3cXPZvap2CyfMc%m(cNj~p1#XZPFdN#mg7p$F=yw%04KTb%qh9c zeLYU4&x`Kna0v`_u{sgR>m={;f$7{z0)0@|E3nz1dSade=SN#yY95ufMyWV?ujht6V2rcV}0poNhI% z@c$jUze7aVPC4hs+TY&a+1K6Q-4E({_jL7jOyG!rFmEO6y1Iu+-Im=ENl6ji9Nyib zr#jzp{O0*yAX<{IJDatwr@OlgbkIvjcUyORU;CsEj)w`o8LZijb)A*n9QHvwkMG;I zWxxEv?(KOSqd3Zc>xnKXiPdNAXam*!-R+#pjqXvwOq*^{^1v)iLPKejr8 zBWrHOq^fSu?qsL#-0sRLg>&*tTJ5U44Z96*R4;0+%5N&{uHs1D>2+LgcK7^AYv#;~ zSsr<=`&{?R-81*BshZa^r~7pG)=S;1yXQ15ty<2pXyT38-A_4$Z@)e8``1&^-CL5P zqLPxLB9pf4k>9&z^FFyHzmr9$^=?sQ6~6iHKwncY=)Uo|)U?>R)D1i2Ku31>P2t$` zby{c9Z${Rxw(gGZR*sy-g)5dXUb;%IdqQ`A*94B=9Nz^+r?$kZu-4_}Rc3d`bf<3Y z-r2o+-trYQI6m*3*remnTGh}{-d({V{PN3zyoII9<>q(Kn>KAOhw$4s2PS{F6YZ^6 zOlEEBXzFb2=7>w%xMRnLjXUJ0c2DV=($4WUxwHN^(-PLUp0=L$ZjPM7;=H`2#jE7| zyC;A)0(}?!&Mw;8q`Z?gZ${}cXhQf%m5 z@|}sbr@y zDW79mSJchT@6PUyO-+sM&hE~eSGSxa_S4i!$8}hzRn2Rd-_0R>^4S6KK{?&?8|GC` z1Et>Y>Px@BS<3i5a_RTiOPPNt{|piR9sk?(xAE`T-;v+Vew%)e`i?v%WBxl#zO700 z!fy-KqA8Vqh22Hnl^sP*9OwV67Ipt_!MeC{UdPhzCEfG-7Ek5)?*79?)cv;wYt`oD z&B=2(e&73JFM9sFCF`7|&B>dqIDWW%_YnPU;l?^;ao@b|rQORq<~A+n__Nwg^t;6c z)~4c)%I<>h!oG?r#T>rhFNl7(^k7Y{N=~jy=J;X%`=01;%X6%o=WO0QXA8$~x9^ig zpWn36)wQ$J)wR9(^7ZxWZ{+#Db1f+QF1GN8;X{!sgfFo^f(X7($=koNIfQ>T?ElWY;6nGI z?rp`3z301E- z?z!Ew8W-kq{GRjMTl9NY;iZS?~;jaz9cYK!>J@Q+4PIz}{cT8nfa5G22 z_p746g??Md7pHVrcGpZTUC!}+&UYWt-&sq1Hg<3Ap58xa%3KbQQtLEf7KHMyL%r=za} z(yavb^@Q&C{MPv{ajogMHfwut=OnrA1^wOK3%YrfCiV9A%Qt=3{x0Fr^Ihk=pgFj| z3Oa1Mudk<%WAgl&b69(NK!XL{yJsiJf&VQG<{#)Y4cbW67J-xj>^4+~%{armA zwR4*1vv&1$^nnhk?&|LB;`l9S-t}GQw}iu#@7k>WoxN>xo!w>aW&NGqtZkj0?ebH8 zYqS1l*&-@@<2##I_ixGHZ0A_N&-*Se+WlMnH=AeocL~<--M_6wf3tf1W(Zw+0Jx-m;BD=!TNjNZ*kG?@8aLt&UXKnVEx_w-BR>B>)G$@p1;|>Soz+AQi9>+fe4Dfq$m{iW!a6W@(87w*-}_zf%dy_yysW<&fAg$NxN)&vd2M(5=NqmoxBX`P&eQvy_e;Ru z-+KG|OFupiW}UNAf6u#BrMrI{WrC){CY62P|DF3c(+8pN559B%X8a)B@crlyBhg80 zQ~G)*%Xjy5_jmPi{7(Fy$68<3(OKTW@k8z(VQIL6MyHiPG8X1 zJAWF-AI0zSqQCQgC$TQ5@9mgY4mu0GsCyFY^D2f`aUs9}HXw5&N%Gx8>Ob4=ZQ^SiaY+FxPO?vkm^o0}(hZ=2G+y!&bU&W1Hrd+JuS zb~kn>+jal8;SgT%gX0IAs6lsJdsahqRc>8(Yj@-IWl-4na}TKY?;hAsA;qSjr~-Q3um%+bl-I4xzV9O$gS>EG=czn}PS z&-~yguju#a-_i3I{Eqq_&HCfTpI4&4qrXR&mwk`=9nJdVKg;i^-_i5SzDIwLX8i+_ z{T}r_x_rU!=-<(--%tKv6OBtti;LTkwqwVJ4cm97ZHSX^{=P~yBOxR{?bdI}DgC#; zvz)qUzqm?%*572&b=wYYKW6uxxvA4a+s5U3$sGCbpMS83t_$88lkr>JwyFK2^_7c@ zug{hHn^-NHaVTNOhVK%$CQbg$c*E1B#IZt-@4LX{vhRxDot}Mn7Fze+^~iT8l{Mc* zzdH+m`@#8xNA$ZP`>wf*S9WjV*p;+7CN?=KTCO{Jao)~Kj_%)vza?+5_D=7e-aV0H zXY!_)sFbvr$jwQ+<(78Oow8^#$9Hkt-{Q8crHjg^6m@e%CnZJ2Zcg2^Ys=;x^4*iW zCbiGv_%3<9`?n#h@UNJv;@s{ej+iaUyLN8gvP(|*TlePTm9cX;y1yHKm$YZ?tm|y( zZsCaAoU&`rrVYFHfOfkSb(c4lmT>$QzxiGKChNkIc}>f@Id*T}vU^wZ#;BO2lsNhB z=AM@RY7V~dag)lv&;1_!Tj5FA9HH+Ape*609~wW3L|1<|U=?oZoz^?Gdm_i;vV|qZ zWu?V(-K{;1eGMGKO{;$!u>PLSlKmT`s?I%mp^?)lw|E9MnkVhP3-1ayl}yy#ia|13(FSEPwbxBGp&z9c*1vs)vVw13`HOR zmSUY#IHPHP_u}q(^ST#z&u^MhIEUl6)MM7)zTbUCjlWB=RxPcYQr=zMT~X0p++9AU zZfOJ)>R9q&F)^(y{dR|PIqy4bz@03$8W~p zdaSK|ZM{vs9IL+jvi8pGoz^{(V|Bsu+}wivY`N~{o`&9f4!-XWbIKThbb^M1tH0O$ z=KRhHUK~>cV*lm@t)Kz%zjOZP{9XfEN%Ok~boL`F-}eo5>wn6uWn8!Br_6Ug<{!>= zqTido+kV&lp7uTAxAk}Z-!;D*e%t)k{GIkY?z_=%!|zS<-#32$Bl_F@x9V@z-|oLX zeye;}{q7~#ePiX#1INx>y1V{eH^;T_9bUhu%kzB?Uhp$s^l#bEvY%z_zx)5NiGrQN z`J3}M$eA_2!A|`SVuKd=aDM0fUISh1!}*(&mG8S__jj?ZH9wZD5n8wA$J{kp-{piW zelq=Z7yWbp&$I6gY{FZ#e%$$SpIx{@i7o4rsPL9QOW1`g{>=Tcgbk#5&5!wOvc5}$ zG|yX;^<7qY$4_Rl&+NimKL5D?=MJ0j4b|Tae;%^`*uO^f&%7V=*@U(=~O_Fb6y$19M9$NyYm`_BEF{m1$4?jPq_e+xen{j>VdvLCD2`M%qAulce1 zJ3m5$*mvRYJl#Le|2g0NoBKC=_n#}QKaQ^v1u6frjE!%4&3DHiI^P+Dz6XAH{H^|7 z;Ouu(;TPYReK!=1PD_o6+L*d~*QQN7<@cm*jFkJ`0lIR)g#GBAuR9-h@9e%+*&W0o ze52dtcTi>b?_khE=k7hz=N{ks;_S(_-B-K6i~LsF|69G=sav(yIgf*Hdd-sWd`k{4 z`F?N7(It#4mwcC6(zE=dWqql$Drf>TP$I}$jY*!sV#e)nF`{iOSz;+*(y%Wlg|zeEnc-xtB| zJ+kPB(IUpZAg42*`6(~@U9mg$kC9h5tIrQl(anDnzO!_1{*l0{^=BqZknKs`7$slC z@;!-*?|V3CCR*lasOWFa-=^O+e}gt&X|jG#`yCBS`)KKf4GXC$=MJVnv>=u*KNP8WZCa&94pwCe_yzDV)tzhzCYm$${5#OVE+8m zUDWNb`vo?>-)BG~-;MKR`}enOe7_^V&;Gvh zyYfQDbx)Wb`%J-Fw-{(JN{{HjFLecNZTzt2_&;EYoJL?k0@0UO!W&B&< zH^*;Hwe$ARQAddNMfUNen0a)#K<=@I{9(-5-v20VxBB6EPAN`*E=KI1;!q2|n{9Yrv zv-?p0!6}`;Sy}>WV)IP{!d<(gyOURRukD`PH+j;;88a?dc3 zxgFhGXKq~ao#jE#cZ2Tlp&Z{YvCr$8*$vt#dTV?4(*8X&IOZ&36@K;G?K>M=N@KUO zRyV6rcYH@|qk5T}aknCe_R-(c-)rQ)>;JG2{Ugbq- zUtI0?4MK;1xcvV0eWUP!?@PY#6HH=pSG6=oBmz) z`(B~7Kl1+Yf8QYd;Jg0M5K*V-5OewN#F?o}3ib!Sm~-Ga@1lcCmz-XGV)?0M-JiPe zM;~(TcIeIztu5xLU6g;wv76&N*B>`g;S1K@KDIWeyl;VQxZ!=qUY_s!)$dI|$@7ev%Ax#XD|3IdUxaZ%-;dsn%&km-P+x5n{B?c z6mal;x363Lqh#K9)iqVCh1Tso{(Xx5mgnEx6JD1xcZ&@=(7!l%QLf!^Hb!zbJx!5j_pp* z%t`Ie>Q0-HwQ%B+zB!#K+LAhS!vxIHhMbNC9^8$ z&s#WewtV;W!YSSBIDRlSiKfkrUf;c%L-_WAd9!BEU9~(dNv^xDv#G6$BlLHSXyqi< zYgrq;9^u{iwD=X?patlyGXvSyS1aavB|eEH?BLgyAag4c=o-e zqx-vU_l1Zfe%&_RsqQs}pqtyj^L|sk^gVKc(6&8!#~&^JQFBCi-Ou^o4~x#Ln^7)T z*WJ|CR6C)jx23zWJG;0zySucze0tr2;tg|OB%R9N60tn8n+dYEdP%xy@o{LL8b{9B;=w?y}E$;-b5zcYrcu3p`6zMNz7G}hSJ zx%2W?d0a|+-~FAr`#Z~q_vbFHShaHA&e;g#zPEp8VP5ysOZ2-){O#Y;-M{U+f7@F9 zmiaB5z%jXj_1wHw)7H-U&T#1Rcfs!OlHK1WoWBeFW;mQVCwrRbJPy9!OTcBd1@m{O zpYEc1zgc2`vvvRG>Hf{L`#1M9MUL-`YysbeiavIK=k5N^zv;W-j+0PL-?_hAFt7XR zE_(8}$U^<@--6x0xe9+X1pej$X?pr;&v(A=?>yb#d1Ah^>wjnF;QM_RY*jMzciErr zqN=~&ef<6QOSkg(x1YbiV};6oV*W1o(_K`h`_u1tD!<=;>i+!wEi0cjXux2V*!Rfp z-|gSueQ*E%_BV+68zxmF_Pzc4yWbG;@9n?e{%-#bGSVHSPx`03sQT}>?|;Ah-qNl9 z{oQ+ziNC>8%R$vsOZWTVZ`HxF@4*iFKB?Zg_?yjkVZ(Q&C*40b{#JUz%J+M6_w?_#r+>dQo$=v! zHRkX8f4YmBbk}s3cgA)U)z=rbRCl#>hj9FsVDFCaZf|X9uBd4$?c%6sU(_|db#m>b zx}N6lIIwhLcXv-yfBw|!`3+0z=5>9S>gJf<#$MK4KB;MH!;G%U-A6gTGqQId>X=tI zw|0Kb4XHx(4$#bVqUfRPkoxQxLep2JC<{4f6-Mcxy zgQUB=yQa1;Z=6*=t#D>-&u_VI(0bpxuRrFzV8phLFB)aDwfXN*`Gb0*e+=0}XH{;K z+nn)x$GVklJLb*aB;Pfs`}-W$m2DFW$?~8 z4^_}|LCBu&$ltI%-H@T_=4Ple!mk$3yMo}vrCq&J#}X1v+pcdLd@j4e>?te zTN3hHf#Z8_cYinQ`k6fk<#%oP9s7rqy=!{+lc zRw>urGOZSTra^SwlfB<-l)q9@sq3*nvyY~c_C z9XpSfhq^FSPIzv^zTfGhdveys$rW}N)^t^Ow@>Tb4&ikUYdgP`bSrS^7`puyP((qP&6evfYw75& zc&5_+lFP%!>>?K#zUEf*I#<7`wTGy1WN!=W0c5XQ%&-XpL?(4cYKO|l- z{$TqtN%XhY@2c-w-K;Br*r6}TL_{PcghS=OGyJ(Gx-R4Qf_3}Z)=!$TRetO5thMal z8^3?M*4_R4;dh?y?vUR+-Q7{FnG1yK{T@k?yq< zcFZdN%^BPMo3A^gyP_wpkE8Cl6ziI0TW9R==J<*Rd{Qg`N($W@Z!MAC1Q@48>U+X^51;qb#jblt)^%e&WcY|C05pOBTGCfA)mr({C~N4|!r@Tu5> z!o==q4*wl-$Bu5_ykD++Q`xfUM37B0Wp<%A#o zW^_LMP`>-r@{Mh-J?SH#NBW2A4#_uQhef%!);T-di1wS5$CYM_M*88oy&~o#Z1vkGNd^ak) znI!*P_&b;A@0j0azfFHf|Bn1_`r8aN)(Ks+9QoVyyZP@3`Pe^~MHi<2F4(x7ZFT>I zP4eG&v3ITQo0i(m5wwEs=7cTtm+#=%_{WJYbwce1x$m~IqK2*qzGr=RI{fPMca}p2 zzx{tFIfd(j9k%92)Ryl?prMHG2al(1_`YL}@V6f_KOTsFSLPD__2&1_<7~p;eq^)f zedhuN$@i%MOk{jSCJTm1WK(chK7wb{R`{IJB(_H^&})*VmwJ_hTI z-SOS~_;-sn%s*7Wn}~kb=IZ*f4MWGX9p6hgeHVke)$-(br+o>BzXz-VIk5Ko2hs03 zU{~sLeK%Yq3KG=gn)>}FhA}^;?R^5awB-EvziXJkH~qLH`dy#vx7K$)47JZTeGl64 z-F_1!cw7&s?)`4JH}>#%w>83l7yMZHBU|*l0hjQcaA7X z%$RH94<-yFpYHfBx%qnq)JT`Z>3hCAAO7wN^2LuW-+zjJH{lZgd*Hh%*Kecm(V~Yo zN4ol?#)ZfW|2q(zz3uAxHQNrz&-|{1Y3%0j*S2hfnrd}CY0Ktg-|ddQTO<7E!H>f4 zOrqb-xPAn&d^hJ3{`=r}$!gKI*wp-hE{-I2;eQYA{r-NOP5AGFAKC0*F%#8y@gL=1 z7=HwQKZR5w8{s%)1yt{ER}%eGZt(qw_QA>Qx4Re4ntl|VySSr-jy zg?<lRBDRzKc2yAl{vwi1>9=$IZ^n|}8XP)o^S(=8`fl*{@8b&qi=-PeUJPd`91s0cjMnm-=%JUPx&47Jxcf^!w;n&yG4@=^I~F_#MM(z-|h4Rx$Yy&Hy>QcA^fEwd4BQ6?wuT0&mMVj|3t98Tz7P3eslpO zrEnkr&T;yC;*qzE>%NEI|E~M=dnEG@ksrH76ADuOJ(nl#*tvG=;S(7XjO*Tfw_Wo1yW->T%*;P3e(V&DE%yA)_nU9I?6tR^I%( zAuOdv{f_-^{M+<NvRG#nq(eLZNOECUW`pGByJ^OdTA5ha$;>We_-`T%QzPopS zU;lmc@Acoevhsbmo>caI)prTT?`OVCFn>SueU9kwRo^APuL24FUiE$TZ;9Wlf3Ny2 z!Or*H`um*k5{zqqD2V=+`04T;EL8hl;=39IGk!n%U5#1f0g{5>i~s0}ewX;I_FIDe zyV{QfFgEDwP5vJ*MU8(8{$Tl?ZSkAs2VcZ*hwra#!7~Vt*ZuGj?fxC`W7Apd-)=u- zzbE~X{hoOHw;L;1<4e)+H@~mE`J3ar!}nKz_#*H9X8ptRJDZj7``z!?cjV3%5^h-c zgGIDEj#b#<`~O+2Y0-)9g(2TBewX8J)~QO_4j|V->%ocyR!c_7W?hG1H_OP z`|Y~;yDK~2_YL1;zi(z-H}Be-qkA`B`W`ol`G@w88qwdIzq=?;V&Cxo(}(X2+kdm~ z()f`1nWIIK?e|i#-p ze$HE%=@+`cvq4 z@*m5oU!&zRB}_fAziM`(BXVA6vhxh<^Y4y#}i4d^c-AcXU@`d|GN#2I!cj z^3BuOyN}P@xO~pC*)w}zcXNE-&c^p!=!fI?ccQ=N{Rrs(t@2x>yZg`E?;_pZzg1cJ zzVG^O{)6$i-(+yYTO#_0@rRt=GU=wc8 zV+S2^9=-T`J9v-yr^U=abbrc-{xbJI`bMY4GErsJq!8(eB?KzkRxY3x9v#-L3y4 z?0aDMcTU#d7T>i*zbpMV{GM%~|SzB~UAg&5&G@4MS~PR5^xA4I>KfA{YG z&h;ncdtkTz?~mOegFJrw{}AtHJs&0-aQTmT_ivl;Za<8>1F!#3{B6z3_wBpDo9{bU zgSMNOEN1?`{-?aC((j(%FWK)}eYfiFuDb8@eb@KZtiQkgjuw6MyXX7P?>)EJzfb>N z4$`W*=KJP1aIM=#fAD?3`Fq#*mF7O(RllveEv>qLKl$F%&ARye718cD->3g>{yqJ7 zY4`2lJ>A{kH?#76-}v4B%=g)g8GoFg$Nb~`kDsE3f0nYJ_-)|+Tl~D&kEHHy?eELK z&u9JJ_dQGWyWj7?-yy#}yZ8Q?+kO7Kc*u8&AJae~(Ei=(_4g;&8NYYm|8Dz$`RA1% zzeM9VxG&P~*6wyMiBCgjbNuFF-?;tx(VN}7yH9m)D~Jg2R_XTQFgauN`SY14kL0?~ zcz=pGwr$y|-re0dyPpScjOX}cvQl(O*)gr|XpZ>o(D3lAgWKi0cOQSVWB~`r1D1=w z&pre7z%|kD3%@V^z5M(9ZtXuw-R@rJe~Y{SHu$lon{{56=#f9u*bjY|aQ`m;V{Ui% zzV9Kwy;%9a+xL9e_#yjUgYjqP_jjV-gMJ%)*ZW;3r`@eo5Sf}=T36DV%JKc%cOJIy zXMSJ!edhPY?(aOmzjd?D`OWs-IP<$eH^=v;?>gTNzX!NyEq0sZ zaVh>oaeiZdcSd*S)S|`t9DilL>xu5}{`^}nx|_A+yPoJ?5IeG)wG1X6+09z|T~Blm zNIa^W^>=5n=$>x1-#XFVtlz!9>xk|JaU!}|zx#aG5#7_R`dcTeoAtZ*cOB8)AWk$W z{=9y~moxsT`#xW^IPbfl5^H)}a$I-D(kXNL7Ip9GKG?LqhGX({*38pY$GZ1-PwAf0 zGc%((JE=XMpR-b@q7CBJXV{I(Dw8e(3z2GqTRVuRVHO{e4qY1m$hkY z>)Ebj9DLtbd{6z(*}b;=JHL>y!cUDK<)XEf?e*P-9KsIW#r;(?3psw9W_~witE*_O z>MjOtz$ltfGqY;(jw^?+tm2r?o-xaDf!|Ax-y44~6BTyYe|YiYT^l%r8-BlHi%N`% zEzaW*R_OY^OmtuHzVLZ99N%BDrLBzIo3@RE@4MHJx*xWp-vz(0_Q$VIT-V$+6S8|? z-l;VwIhv=l##To(C4uhP>noVrov}G?eH+JbK_%8dF29pSH?$mVzuC$0efsxYR*#k_+TyW=O8 zEJ)0pTH9CM?cN^P2ae5I|ttn zK2SCL;|=JdbSbvju*!%g)u-QCU)^}PT4lS*Wvg$!<}0kWCUN|hVAJjX%`~IvyCcW% zXKb@o_qyH5w$2N-YtZ8O{)}zOcg2f4zk?>jzVH7Tb&dJQ-tUp3d3zhybZ_rIwsiZY zV;mnIuy!?fH+Oe*hpu$m71+77dusRe?(YmszB6vS8D1UP;?*74oif2>4##DuU3upl zIaG~VqmsglQoAF⋘GE{~OfJh>&WJEc2nYW5mG4!-Yg z-&?<@{$`#b^rPszu5iN7?C)Phg%|u0n`wFfH)l78S7MU4&(@^V^1=z<1-tJ({J}Tf zyX?1bLbq;r+UlIm-E+F>TTiON!>X)-6DzHz5Ta_dG)H+sZBEz=Y_8DyV}k1 zU9o%8chm3XphnQM@0vek{|NqI{$2l7NZ8>g_xEVg%C+Sy<>qzInX-5xhp@u853KX& zO_|+2w|j2mysFt8zj?niu?joXPOq9&)1BI#U)^2WU9hmRJC{RPp{lW^P|lJqy1QXw z##WBdv#i1mrOR4-)4Dl+x3T3{H&)7bH%+XWT*qy3Jh7> zYC3DWYdJi(wjPiJHG-$@xVwY#$H^a0L@y|vb^GY}o7W&C&FVKN$4RymuWw%c&UNiK z_YLFwk&ifjCy32WJ?yY6isR3uxS#xNZhLJWCSKzB6Zrd)=#NQk*#&-9g%KQw*>+yP za_hn#j=%hDe7~E&2Y+A0c;@>e=I_xz5=G~8@pXT{xrgz`xgT7jztg(CzDIlgj{cq2 z{oU@jfYz|8M4tP8G*+61jMv^#7Uyz;5dkvH8do~Gn!{+-}(eG(o ze81JcAKAkQnB1orlNh^y8^T(usTtJZ^E#rIvZ zd&zf)O^iQXmx#8rrF2&>i2TmQ@w@oPqWtdPMSpg%rs~z#L^p91eUD$l9?)Mht$S(r zjLAzD9ORg_>3eB6>ss6C>D{H>Wp#}ioo(H9UA3JYMc;3*|LOSNAo}uq`-k7{tb8}S zmlUo3etPBir7ML#e>eGV@ZIva+;<1zJwNPzFp7TXSi5aY_jQg>dPja+%l$rC#CD;5 zUUhc_$Da!J?ueRtr)G{KcHye;*Vw+>erLJ%omIa3yX0@hHR01Zew&qwu4MbPH}p4? zR#K!@eAS%o@;`c5*3X);Nlw`2;_3ul`EI4G{NMZ?9DEDAm;CsRmBooBa1S_V0|{TT-U~=HOWR{aGPf&2N^}G^K8i_U|=p8)r<}FZbQN zdr#()-;x|l*o5zWkNtDCh#e_d!fP8Gn>mVp+-2YVJte=JHCiRm@wX9ZB}ezVAD`AS z{`j(#`TPELqK@4^_Yl@0p--+qZkk_k+8>uUf+R;k()Q*6(J_KYG6368&Ap-W^p{3yPEP zFP5-7PpO{Ky@%sR1$+12`LoYY;8^neMG^bOw)xfFksQC1f46>52FqLo$viD$Ki4|9 zraKZG&`~vYuApS~bP2oDc2I?C$!b#lH&{v#ndWbiLd~ zw)G1OGvxVx$9FIJ$+m>?JL7jV=I@NJMY|&_YaLoRioSnZ!frpIVrKV#u#fjIn0>X6 zW6AGNMeLW`7Swb{aPWP1hG_i+3a?o|{)u*1w>9VFaQx2wo!9+6i?#dGtl3vVVet$U zopWot!@*$@QB&{S%u)3HDLh2Gzeg-v^4;rqIqSUQ2~FJ%-F3BXS)dHoUE9S`^!>>a z_M)DK33anLejok8$ND|sd)WFVZzgjr`MtG>{Y?A(j&9JJ6Upu+j6Yq)mas=nDBG6u z3^`YqM^y*-f`UJO344BM|0enF<=@4>-(Gllna_9W->140IWn`_V&sb0`M!UHc+Cv^XY<*+{p;#&L7uH!!frXSVqW)Ej_w~>-zWTewhYuNmxS2%dHWCN z?abe+ep-nBPA_7c-#w>hcM!*)JofIOnp!82!^@Yj+fAvQ*L{hj`-j?(;6GcIcX$7I z$l85m#*8ylIhOp+E@HphGOw)Lh2!^8cD@tc-_=2z^S=vxS7$uB=*FV&n-|?&%>3gi zXl|$G&+6Ybi`aK9+_+Ef`&+iX^ONJ`ZGYE@exF>zo>iNdCimNyEvRwRy0S#SGoAj;`#a@#qVI2}-|WBHeZMn(kNci;>O04GPI2!I?^fR(gx`Fh_5HBucj=zBecR{WS+)H{_YIEk96D!zTgwUG`F*X3 z?PBZ9YLIF4e}7^7u6O*q)OW`3Vh4Up{5Fv5i4pxB z>GPZAH`{MEpYIIcW92|msq~|Dtl{B8vw!Dr-yb{YH#f(M?#}PA zf8KO={El7G%{u2h_x_#V1^PMoKKJeX(e|B{amC*6QQsB5N9|=6vG}q2r-tZn*6t96 za-HI@L9c%^baOa{c-u8hY@H^*fNfHDe@{=}gt-$J9q;Ct%yw(D!*4D5-%EcpN3pN} z&e*qp!*|9FJW6|hvwmO7{#|R+wOevi*iLr$6;7<_>6idopfHtf?v#nsu7#X+kmvip zx_i;PgJYCGV|GUd~p6?FddA_?ai}3wC0@{-3d{^*%^qu9q*>{#lp5GOOZ!!GX{Nu1__ko$~kN0pa{(Y*L z{dm{R^zJZ@-{;xwdV{xjU*-^g#?XC!!QSrU9QT4wX$Ob7hm}mNnf$K*eqj;2Q&0Pb?h_m@*uN`(7u)@v zSzh=tL-$+9dEMu`XRVkuyZcgit^fXRj%ib8EZP@#F<3ju$3kBCRztV{g2-<7Zs*{5 zyG{u;OI-^myKClR;dehfe%Oe95Bu$N<+s&u>)+l^ zzr*CZe=B{Lu=p-q$r1cpg+WA{CdcSpW+e&_hk zapX6rhFrHr!EZ*xOpf2#?80ZiZ}{#}%odT@o+9^K^)TCa;kn->?{$Ay;^6x(wxI0C zuJ6K(KV5!WivCsa{(kna<<4%_?}k5iiT>96{ph#;_eZRJv+LGny!rlllhB6mIzQM} z2v_`g@Lf_gkFBk^yr`f%y}NvK!wQZM-%tEj=+67i-Pz4rcd_84WjDuf0h8bSzd7WE zcl?&T`kVc`vfQoi1E$~eG z)pv8$flh{)SUF2x`0lLknUiOAbDZf@NR;PG`Tp#?4CC^(-(|k*F@N9pNVJo!@@e+> zy}t#zIgHwV%lOEzWm`F8%{sZS-5Y~p>UXI2?=TL&l)7a<*q43pSSz&gyNU7B@9N*Jgx~y_`n^H4 z`+NKEvuC=`bgy4NZEg2~?tSTVV>#B&WKBIDw$--Vv)d`H+pSyUyHMPX6pqeL*5XNp z6N|bzgunb2bW;1x_?z#m-ggoC?>bMuvwxS8|83(1A@+0C(J&XPqJ ze~ZtEa8SxIkni?5nQ^?6LwJXKqc`XnY1Kz5PrErjcAr{u;28&>?r)Fl@4u`55ctk| zd4{G6Q};KnkpJ@V@^=P# zzS#@Pe(0YPnzG{Pca`5N-}Sx=eAgHL@?+`mYSAAL*)N6daFes@_6aGon$I|WHHG^hzIokq zeu&=pTqbnvyPoid@0s71ihfUF_bK&k@aWv*d%i zy&`{G{Wj;|)2(~uSN7xeE1?4`esHf4-tnXAySnJiJodc0$W*zs?#4|mD>$@%u(8(d zt@EZ}#u7vhQ)Hd@c#?`Oc!dtl+!ucY*JEzg54h9$z86p<&N&ZPBbn zIqTNXUAab1cx6NPF^@ytPdTRdvd@cIoqM4BPWQgk^R9qOx`Pc9JQlm&>b}#x@yeu2 z9C>VArOg4k-5h1zi)P6SFKhV0`CIRI276@7Z$+1cj?HW2zTf+;&)#j+Yt(JX(fzx% zGPB!_gHLyI*$?g8j8j&8fAEp{N6rr`(e+HmN$e{RON6%H@IG3<@%!41!oM1RBy@^CjyY>Am(`usmf4-!?cCpe zxO@Myb(@!MuG-$Xy_;kG!%6%0dQHrb5ATkwjW6I3{@GA8yL?6W>h3K|*DqVPrEY8U zwr-9!=cn!16Er0qq^d9}n?v|dL-F+FMcu2qcdnbase5Agq|S+5981_Md#ie?x$+XKBX4GIss|aEo?jXb+K)0jb9D*mV1dBYbhmEjZnY!9uex`3@9f*v&9Q!CM^sW= zWtMzKcgEDrZjP*+)};8oZL8!zy}$aMLmrfcemE^>-0Viran z{Law*eez|wP3*#p8@{u%b=$W&blZ13tn|L${hhgc_O*#GI5OEfbKA`lKpUMGchBp7 z+V`~kDaUu_zK-1!UxKQR6(A39`!4l8?7J-Uj|m_T%j$jot?)ZA;kW8{*I@4MRlSJQv1xq)hf-yv_F ze^>n;`<+W@^V;v*c9*RX{_?}(N1bRMTV8%giCl7bN?&p}$8WKS-?G1%ev7U*{;nWD zw|hnZ@@@{{9~;*-rzK}srpu>wr%p=i<|s&LO;6ZSzwX<2=9}Ny9>p9rlrQYg>B#No z@DH7F`{lKnH{=hnz1%hRyDT^gzyEp~b3|zEcir!5-}QuV{8;;gOH_Erdv@U)sV%7; zsofmHJ0iOyr$%;j{FYM##j4^h70}qjt&iW8| z-PohLev3MLHGgNkE!VxQXL0vTj=jBaE?(|Yi2(Vx?&$Xm-&utwulVp?<;jXxkZ-Mj zREti}>trv;?ktz{>h_%I)%}~ZyV9>ig<}O|fU{_|6RS)X^UtM;IrsIPzWhyU#J^A9dePiT1M}?)q$> zS)QIN*X?!Ly<3;VBh$^^sq6khx$YHx8@o4gJga`GY>^USEw_Sg{-lG;7xd)R%Kc7a zS3KtQo$0&BuJ6v@HRSj%)vf!Xy@PSncirzy-=)55uVenn^nHQo)6DN2znOjuNBw61 z%`Er3j9sJQJLh-N&E4O5kIT>Mo>eueo@3!s)|^#&bK<&xGj#|3=Iz!7HAZ=U3w>Aa z>dEzk+SU%r#r(&UCL(oFC2c zJLZqxcm3`!->ZAOS!V<+|LuRP`*C;oci#S!9C>V=X?4l*-QnFC-=%9dbMSo^s$20x z9#ruj`>y@=gZ};C<==U~3k!e#ss8<)=)U?Jp4nB|`EuPZyL`J{y3GUi%_=#HejLm1 zF8XtfReN^rt?v8X>-x8JZ|1mPdFwZ$WoD4Q+zPgZy}MT}>yD|E>yGM*?2h6P{@VRp z9>x0IbltJ$^G*LU9Uf#2o6dw!oO z|65N%w50O)=HD6#)O9daC~>V{@aOd@^|(f8#jRNyX#z0$1!gQYv`;_@Enn@ zs`78`-%20VzpKgf&8}PV{lkII72hYV5SqN=@ON#4r{6ih>+fD6yu0DYlkay$57!@a zlWYFY9sb?ow`wWZT+u2$ z(oSv#+ky!PRxIc#sge7w&;FbDq0e{r?=CyO2YpwP6W-m>y}EOC*J_TvJ+aC1zvI8N zi<)KMy0UTd>AeRh{5J3b<=JoFmwppkxxD4OM#;MGQlPb$pa~A)!$0PJw-?Q0ORG*! zNG;p3M(z;1@S8(hPoL|)(!IHGNpg2!ceQJC9LH~YJ=Wg2eRI0!avZOGWoOaxn>#>m zIoq0f8#ioTn2;ftz%G2*FTvZt+qyere$wXd6Wz1VPTS4#UHv0#K=*IMhjRBPd>1$_ z&v&T~RK^Q!|E~A&x!KJZ-}Qyx{P_F5M6~<6#&6*_zZE8Mw0)1u>u&uW$NF2L?YEFt zH+W9)JDbsW$z~4WFD<{Lo_Dhfzi9!bW68kkLFmx*7E((?-#!xH2*N==8#w z8C4!LqXu_CGU~Pow~onuPhmSg!8Tr=?{fF-?`i9Qh|OpG@oTnd(1g~>byqlaf1E1p z{=Mx_2dnLGsfNbnW{&Fb{ zeI46(!|x1tzjMiVKlfSfvy9`nbGhi5?yc?fo3~UgiSG{O_|4$x`I|4Xbk;Jt?j18H z9r&KiaqasY(TNMEPu$eaA#8MeL#CsAcTjPBLUCq!bcps> zuc`Y_&R;#&AAM^@|M+x&fAJ%mm2YA9?CLe&cdxlI|GU{5kfWsLe>YnoT>ZUkjc9Q3 z)W!1MJ7-Kh^gVT5=c=yhpi#clGwQPByQ@0u+DbX*x1Il9Q`yg&YSz?h&|cr2-IdYB zarS#H>%@iA`VV)1H|$i3(B06Q0rzfD zQwu2IfAq7T{BBk~nbloCCGK}%HwWLG?)5)zu4nvy=*M5t@Aln)ivPUtuKm&5eekrmv)R2T)=utT-aWNv zV((;*?-D;sS!W-eGV#b%4!(PpYre0#^nKwP#?4>8UzoFM&h9VFKdpaQi+(TWQraW> zTmLu9??b=+HM8&D|E~9)<@>(xes9+}n#e`RidM0;W##5&c60nzV=Hc0F<-vBzh^>E zKSxz}^zYEw-K-OrtXj9Mo8!A0+q}h9?G{8?uon=;|ZRF2udyQ|qzLI>IFd^aJ6{EPvGV^KBR^sZ^0 z{oRW@r?z%CbQgz&8u@bk)=@n0J&bkc-sSU;;S919Ou9He&_w2!n!zVW=3~KcTH_`c1Ig%Xta}~`up

^>7)`|@{j{C4_&`1^tH zWvt5=ESk2spM!6I_w1hv<%~Z*h|OmAn$WeN@iQe(LSqgizy1LhPFP<`Y z+I)`h6+d(?{IO#N9e>!}*d3eLmEOSty6L9_>d(I*e_D6{e)6X=Yp!y2c{r#%iki(H(O*6t6zTm-C(Pma&h^9f#Gh?*x_|KgIQm=k zPZn!USQ$KhxK9N8drt3+DXSoHFz5Kh3CE^z@STU){^S0SRidhY1b(OdX#Z^rvU*qh zg1WgW9KX5$n6jq*7HO&v0+p9wYb&O8FYlhwHy>0&u>G+5UiD|!-0trxKe|~rd>8GX zcxooc?BCJV>_^&WG=lEnXsGUP=HPqMegAvROvulX`)$rv zR61jseD|cjN!=4UdTZH3f9K5XW}UQQ<%&(+9N*2^md&fpha|{mj+tza6GUs-nqo8J zBDy(#UuR3roxV-JdwTEmzUdq_AX|S->SpD8QMu;Fzcq||zx;6g!o2r;q3CagXByvy zzjuFk`abJ>z;`L__rFDdJO6I}9sRpY?tA_3BvJF&6Wx=B_ z-rT+AJJ%1zx#u`$ADKGo@KkUu@O|rs?`uJh_|f%A_{EQpKYB!8I~@2eBPV?0cTY9j zk(QZF-G$w??PYaw9KX5#*s@mnH@AYLyK^?X&y=>l?zP?1d#6ra&vD>;DC?5erJY;4 zIlku}{LXn-UPbJ;gjw!yHo2FB?mg4?&e#b`v=4u*UCH?4_`IWYn18x}lF!cW@6&(Z{A2U`&W{t_+rPV3 zbh9=`B_x7+p=Z0lzx+OjwQ$vM`|pX}b2(-%n=n=GyA9j5^YK>l-L2g%UEoq{Qupj1 z@76K?xHX6Qr#Q&S72S!y_2#y-?)|iS{&(qajs@MnRsJkttqm)z_H5&*{=RKCdsRB$+o5jO%d3lmt1fr z9o!w*5Lgq;5%N2hwI;X$y|zBH{X5s}`_;-H602^^ zW}NrwN9#A{@87?(icasG(LJM^gH8t27w zeEmI}^>_U5s={zbP^p_Wn?1T}$`bkRS(E3?n8op3`@6%r-Fvb>I21bG^{a#Yew0{qXwE>bO|=$B+7-dZNEofA`t{UN7HmaNYK1Jx5*KZ~1Q4 zxXRh9<-skDDICS!>A!WqFX`U$U6VCoLE`4_M;za~zneV#&T!^S@^4MK?%#}-zYTu# z=5d7nR{y@Fn^pMB?(e$A-K-7iMd^Os9KWTy=WJsY{_#Ej@PqFf-QT%5zT5qF*sCkY zcd2{+j~{Cp+kWbaepmdfH>LYW&QCqoKYf{^KYY4>zxb2Q%2(8|sN~A`AB%*h-2Xmf z#g~P`dwwkb;Ua3)?fYA=v%7mX>zUHKroUBw^ZqveEn~dL?WNr9?%PMUoaN|c>u&C9 z>Tcu^u1TC6x<~G4_p19-c5swVW~~UQXe;P0?5F#b1&87vbJm80 zGT-j>?wpyGE4#OKPdzYc3&+bJ`&bu!mpgysW;e&_x!zHBVctRV<=r*&O1e4t^nS2j z7FxS-$#>zh#ouk83%~fz^*clKQ+Mw7_&KNMUHsnp88q#DK z=qu?4of*gXo9(w-_PpPW-^Jy>`+dJGIX z&LU0u?&uwvTiZFhI$4ExIW>4DcUyKl-po4D{i^%I`aKUgN++|X1}CMtbaO<-EIe^~ z>-ID9)4FGNOl{{7zBaRcdh^U~jydzEFPvR7qg1}OySAyZokMtAV|#r^RX0a^{)UaW zPA@tpzqfmO%7jXeNd+_Oz(;Qc|8`sYUE}_bsBcT=GEU#P@!)rb9n9a?d5U^Fmqy5y zb>~j3US6}LVM=K?M_o;QW$fw7jgPx4xUmvyh`-dVKGx7)otJSNqJql0bYg5DY33%XZTtd8rp@6K_n z_TgC2%sP3+@{60gXLk2CbvJV~v3J*Z)OR#+=>C3wtDAMzcd4`AmwXrK-r2pNxI3e} zswSW!6cpv>S({7hv$~tQv!_?B>E_@IZdmmF+T!oq7cs88|NY2)=I`zeqTe&WvwuJJ z-Q`{8+ut0&vlV_{`R$^5!t9>>_u?O{qQ86C7Ck@sJ^DLO_u1}=p50;H{w*Q3K^(2c zqQ#nlze|3Lbq9Aho#{TxTYdLJXjelQbZLjXA?yl{Q+*q`u`)T+3n{!Ta@M$*gFDv_A zw)nfqshHV9!W+JSYZTqwy>!Wr`y4gXY6@8^a~c||y92u?UM@adwJB?PWZ#4;f&CR ziguG~j%xOOT{Bv{%ez~H>i z*^iG`guY8J_|CVoWX-pQ!rOiXf9Db{oy^)=+ZdVHozUHWxbp~ya8={?*Q}lAo1Z&$ z`*c^@w1jgkXkneUcgd_}-MhM{hjjaaI{n>E9KW~xiG17rJ>vUmR^e^EzvJ2B`y5Wn z9qe9nYwBJOKI4YL&sx@Yyy>YvH+;QMKt?%xW3rm_m(Xf5lg?Jnm?n3cU| z(X4q(<+|rLENNcSvY-QWoYd+`+b6H$5Z=+t)*CT5?pXJO?o-cJUEt_rYsziN?#}H_ zTwb`oo8wydnlrP{aqy`uDElF~pKkwD z=gdy;j_Eejwv5Z*$ohVQwS7zN;`nac?mUkwFHpjsxM=a2mEH5Yduw~EIX3_P&C2)N z7?i8k?>pT8p0@pa$otiEg}Qfc*_e9pd*BY?{SM#nC5U=^#RcNb-Uq%*o%Na#DldHB zp{U!dDYZMMJ7Yojp6)A;&o7=nzjJZt%x;cn`{!<0R5L46KCRm=xw5IeJ84$;=I*Pv zwya;WuyX;p1lYV{{etS58S;tUVR4b!9K!bw6!f^v=-$`8YSGjgNTop7os!+H9K!b< zq6@9uyIs1o<`k}O&L~&Y?l$kX*qeK;n?rcN!@BNsQ$Xdyyt+(zKAVO`KeQGxPQ3qp z2Q&{Fd>8+&_+9sf;~BNz8oy2{&o%ufVcgF@%@InI3?^~!b{oUku>u-+k@4+13ncuzrI(u1jrd)S(S95m@N6wPM zRqLj&T(%=*o851B{ohKzP35|OM}F^l(rwV~>zxZTFyYEupCBDmjm%QY@GD*Ift-3q2BPWGp zW(Dhlg$t(6>gHH6v$R;A@3%$W`tNT~%`X>v4vzmFKdgU*i&nBVSJyOEbUSxPPH^rv z>`qR}ci>paCVXS&%H<1|bT91Q+0ory8|3>twmYUfZbSaI?q}Vr&Mv+NN+AtN+0J3z z<=u_byXSY$>|Zr;KF7K5?X2Hfx_3^SbAyA=tYQ5R?kz$~7A(BkbnUz1SK%+;y&6Qn zn{;3J?zHB+*rZjHS9P!ESX#C`JH09=UamWSX=%3;hl%}fcAezXd0XU!-*mq^`>6Y6 z_x9TLQQZ#RneH{-pi*G+szv8FchBwa1r2Zrf9dvi`z_IJ&f&5%{k$CC3P^sLasPYL zobNR&n7^-U5dCiUUG}@dcb8j-7r)2y;lIPKb+dlI{$qjYZ|mQZzZHIq z$zA?Fk9GFx`PXlBa~xb88z#^9TkLn@wIB7j8RsqdeqaIf_mX_ky6^noyS_is@BVG| zyQ1#9C~Mocj+Xr^&*qr%o9p+i-@l)CfA{)6Vdigc z)(J@yR8Mpt?p}Af|2Rk4B-WPl_V|eIrS1T)}7k@o6F$0;BVe)j_<5LwiI>$HvY4PHLx#!Y4<4( z;dkB7jz8{x*1fZ0Q$)8`cd==?9mfi`X`*KOS{ z*S%xf_G#M|K3@Le+cb`ei&=l`xBq7TZP5K&t=npy`HSwm-Fr_gy~NSS*45Yry4@sj zN!%qlKApx@-{0-{zP@brF2<#gzR$bIy!5-bsKR~IPjV-^4=&xb`^bfj-(|bM%XNPb z|IP7T>$i4yN_R$cNIA!r2-aPTR!-U2{kHp9av2Sb9mn9fRe+ zzxXaH+8kK)yY{zuH%Cb3{`=oK&c3`WzqNaH@v0mSzTYPsS9}j%`8{L>(|P!PQ3ix=<0^0!n+%Ood14Jw10NrobK7(I|^nccK5I7UD3Ug<9_)^ zC2b2!?cYq_c^dD@#r^hSt*}k_Ew0&}++8`pdlQH7^@i@32cLGo>fTqeDY)CNyWG3R zhhs6@q!qJ|Z0KItJ*ftCVA<}5Zu1c1ZvAen-8nbAzXx|e`95>OcY`(Evn#u^L4y(< z4IGN!H;Z-$)p#`rbND#Vym@N*)9YKO=cdW?g)~0+e*6~Ws(Ih}H=f_j{QblCS)#jE zKfKy~vU^_L^os8A?gX9cq>zlzjPR7%`{y3*KGeOXZc}tOhfQ~pPK_VOJhmwtmTunG zy{3D5S$9r%LPcalVq~hZPq%A##gv)_-G{q3JfFXnWB<0J>yNCj4lWPu4(Lvvp1!O5 zVfXwOv(IrtZ+S z_fdQTd0BK)el3~J1sjxx~({V+kaOU{mt^nkkxup>Y46q-QPvteHZ>N zK8=Gfpn#SgRlLg0*8^!~f)E#WsmLx0Q_{nO6o+wEYP z9nCSDZO5_&(+~D*MEHPw%Y>GlguftVC{Pym{VpiieX#OC%u3MdlE3*aL=6Iecl>4r z=U?GB-EXgb=-%HA%E6$M+=^Oq+Ddy%x=PwDvwoW>c87P@ZSCI90m;qtZ?F8$^QoKT zdjZ?Q`B{uYM#K{K*otY3<-2E0m_2n4$FYy!v%6U*1x-%sF68)~{M|zIyTr+=4U+5KIJtMDr_`${nW{;yN`CS$n5s${w;4^ z{F|qZgKu~Dk{>_74t%;yc*9TapN67;HWaa~Zr{@1o!DL5R@7Dn8m$gxtqrSbi~wyg zIkAL2qbGW5_v-Gc6P8R`%JE(3M+&R(g(+)iOkLm2@m=Ut>2GoQWo@i{-?h6pGyXIZ z`#tTaeQEa}_rLb6KlVa4gB|$+-V6rX{&sBj_xWJ^b(VkETqa!hecEc#gi6rV;G9{T zyC-rS{+`PEovm|a1$gSQp`(IhdE3?ROY{3#;|vOGe)D#7)ODZuUd%dm-SjD-5o+r> z-bNtnh1YHS9`t7eXp_zEmL;9tdEGVbC9S0p&(ws~HH3pUitS@R_C2g<0&9?-z2k2O z@cikzAFtOjexI^V)V2GE+mDakEB<732mN+i-p+dKJJYER-9}@w7b47r>&AB=C|YT=I(>vqgiJi zS}^NOFUOMK7mL{Uw=C%B&h2h!FKa1;CWf%;`bg0Biakr%eI_(c>|WiybjIrbg&cdo zdwy^3_WoVNS`%7P>(S0p^!*}eD_-T)?iJlLCoGz|m*YG84|CSp2WC#+2U^O%uZaCX z^Zc&vd=9?vl9g+}uK{fy%UR2~?7PbD#VZ$omsq@V@#gO;%s<_K1c-hw;rgv|SM;~E z>L2dkcGktGE`68!^n>}k&Gi+59&*VpqQz{@nRx~2-5joL1(max%Xd%cnbk{_x z{@N+sYq}@)OzfG!aqfHXch=u+tW(PS>$@AeOA9(vJ2;BIuUW#L+Fv`VVKK*V!|%_& z=X{T1-L`3R-!_nS=|${Qx~6pXbua0d+R|Oqo#*cUJL$Iz$I0&%tTXp7n00Id$CBTt zVDZKQDm@r~JQZ8Q?mDq^YW+o|@}fAcrZm2Rqv(6Y687Sx-Zk>wvnS1-Jd@*l)eq6l zzZbGjiklwU9orp|(wW@B0oqM=pngFUD5!*l6Mkv_G!*@v(f#|>pOVlYCBF+nY4}vf zl=klY?z;B!mLiVdjDI*;Q-6y$HHOx66n#%#!tOn}dV2Sk?%5NU&)CiJ;Rh?L@Pc!H z4lnQi!TsanZ^1t)tilO3q2;w+pmcL)3435)?PO2}?wv7lA;)*YAMva+_s^cWZz3qI zK*RYs$PwX1Y=_#Ww9bp?_|5Z2A7o{7LkP%QDNEP`CX~(U-qbyJ(zVK?jKm5bJ{>*o0G&9-!UMX@|+qNlTkV-efe zMWQ8a4e@!gpw`q&wzSFx>*c$r^-SxV#!=es|GWCfQt&pr%C$fF!I|U-&vzB(WuPfR z9T3@BYo>`#Z)%Gu9(6 zH?-TQJ7rh*)$Wh`zWW|}z3=IPEvKPn!;d%Hi~TNlQ6|4}2H@QTV;N`?ukx|Vixe7E{`<2%m>$AiD+ z<%Mtj-dx1Cw`EZm=Iv9$1wXDBqpY6kD597?4)t+Fje-&{NgJar%2R>%{rJ zi?@Qj4=RSvZvVat5x&TUPj^H^SbY@7?-Oh*-(30b`JKP}diRQ;Zg-B1-;ttus&>EA ze~WipcNd@PzSMne_UT!NIrzRSc7Ip=(X^KF$Jga^Rx&HDIgmX17VV7aS-Vh2{! zzOG}uwB$$UGUlHeAmes-r~bBI(!#p`{i!Vzz4aHpjW|O{^=wGu-;l^PRbSZ+Elb`fiR5eMhG3N#2?z|68g1H}h{T zFSV8r1>d!PD|K^3>)U!ZOzfN?zrK6viM`!7yH^HuJ9X!@W;JDV@O?M#Uh)RC+wH*; z#_sPnKa9THG5?VG&L{fa=zG%b?*ZQhf2(X)m7jfR`l6$gAjRyO_RZi5zrDJn?zdt0 z?|7r`-(KCnMZe3Wf9a|YXv^~i)%|;xuxIxqcXw~?o-<)SXaPdl_cNlZ!N0wJ3(2*y z_4dB{{&~_ejuQ)$gXMo$z7|#fBf#q4TehS7AjkVt-`&6S$$^SYP=)a0-V#yg$u%?c zUUE2oZ~PwpJ&|?Wj``Epg9_OABKE0Wle_x67j=S5;mV4_h)@n)jfM@ZUA-yaxw@Bi zFPb)GP5*@MX+1MRHR;hJ_T#N{8fV6E-2c7A-I4XT(Ql`m{6tU=%38u6*Iz!Pdr|j{ z$uk!&<^Zi3WStSa{Wp7ea(7W}b0}y}`w4cw?+d$^d=Faj{q38Cr9wBq%Qipy&i#W$ z_|}g-KMsq|JTP+ZW8 zCzH0?cv~As*G_DiEVsYA_xPpmS=|fkK<6pH>JI-c>ev07fy4M)>UR;jB0bhh_r_9x zXmuafQvqsJ&FG&yaS_Lo7p&hyzKh-Y&LiJ_C~I-|mhSzBd%C+XcISb%yUm`nbnUL} zy)p9I-Htw`7S;ZfA}if1oZaJXLA%{_y1(=NShhxJ&Z33ioxeZ)?xMO#_{R^&@2;ZX z^%j0N|IXC?-JavS_HUVkzr}ug{BHWq{yPOUvG>~VZ4F0l$ZxrB*6j4|HJ~Qtq~1v! zCEanqUA`~wKKMPIHD-R@uI`;2-vu6jPx!9>aJs#>T(^qz}>JqCYCSe;@f%#LCxG zw<7P=_s=VYI=^#&PhTy(mZ5#Rc6N-Hn^umvMxB zj}#T&VHfy&qH=oO_TArgE`E1>vU6fsrW{{b-Hz|~uLN)U-oISP{=3+rZQo@^UFtjgcg63*a@}wIH=0ccPX)eR z@L^~7clPc(Myp)9e>--2{T7b+?ac98?N1GBQ+9h@cUgDb#-_R5`@3guoV=ccFX{We z?-D{&*R1}oo3-}4`*#N67vC%PiC(?(J?nP&cXy6Msf)8Ra?AYXx~nIZO{|$%Go`w_ zsyn;Bthzis);lXI4m9ic-HYu=(|36bIsNYP-y$`h9BbGnt(?DlZ8yi$p5MlP@?F8K z!Y?8t+}yieIRZCVEIqs6_*pr=l)B|V*p`29U&Gk-U3Jyl6QDK6UUj11?Y=92H~MZU z-~HYDchSl2L){y<&R)~Kw|i^m#AuFonGPxME zG48kAZ=T<>zlGH9_UyWS# z-Fbyj$#?FskKgTI3UB+t_WhCQ^63i~Ui{59Gs*I|P`0{!ci5raEo~gayUgot)4FxK zZEj|t>HgMzX4Qqe9L2L)a}z?6Lb^GU{3l*Gas1d$dEslzyB8EM?&esqVA}GzHFHYk zg|`)U*H)x;a};N8UVnM#vIFu*yC+9YtmT+eKC^mNHwT}>Z~rad6~A-;(ETpBbTQ+C zbF+_rm)yhrz5mBD(Y38Jy4G}aoVv4m-`c#%$@0P7?x9I(6+QlQy7zFbo;`PsT=ins zuvE}x6v5{*_cm)8sDRFwQ|s2=R&cp{RributGYRs9&HN>i_VFZuk0?JP`sdGPUYh4 zZjRFOy1bC7nyIUfEw}z+v$F(s%Celi!nB z7hXB=UG;hQrtbcN?#k|}rsSpqj^BL0Kd@GXcUE^7cc-q%UE9sU=Uunr``LA%U34?Q zbAPY@&dvPYxlZ(Z@OR$tr@q_1%lXdwoBMa{Z@%9rf7@tm{muTJTmF0Kj|ZaNmi5++ zQ5?U0y64|zUGQ-Kch1Y*%erS4cSmyM{q7R2vSO_?N&Bs8(VgF2vZVWB_mzo9XFuZL z`@OL42xA}U?#Ly~-=}?-6fK&@+MHkLoY39SUDn^duzT{vc@vj*ci;cMdCDe^-#p!# zzaO)9G`6L7H*|-uuHM)EwtL^XMYlNkiW*O4mwm5V_nrMg;4;wEp6mB2(Wz{6L0yc% z>AzVgyM5^9xVQT|OSgRabk>&QoV3F3%WnqzT2>-04X*X`(D(>*n{*Ox=M=J$g? zE#K9b@BqkG>!M z&C_l0`z&iig8%QN?lO*mB^ArI&0Drbu6ujpy6%hJ^ZMua&*kW4n-sGwe^2+F?z3mt zp6BRg>!|Lm>8|dMn2~h?Bp%q-@}{l0^>b$8|FkZz0aXy*zy zjwKDO6X(r4v#xt~_mtAU!tU<1zc2OYL$kTkckb_=2fq8g-LhDy?b^)sc}Ktd?GgUh z@cnU^XjF83q#Uj+dgD9O>G(sI@|oT7?U~&<-Em90cXS^+uzBmk1#L^))^>Ajeb#eo zRpIOm`GjugguI&Kp4>^@`@0X{J-L4N+SVDM#de!-PTse)az?UzYEQXgrWuVhYp0iXa}<|Ur{%B7+9Lm)Cd`<$xV!uK_k_u7E_Ux*HSZNi z0b6rXNfT%?Do(FkZ>k<>dtZKL%5Nc#Hjd?N z(`HRy);+KLyUp+Ezs*6T((5L#owSz2=%VLiIlkZAzx}uTxV}QD=EV1kqvhX)gkOB0 z_`_9n)$+PR`R>}5y6!TL{Dql2*RNQ-b9eIb-%P)Kesllk`|Thn{Gt1I$M>z@xw?M~ zb?21iM0VpyMW;`FmGA!U^m~&<_je7B?+V}9F24XZS5|(1y1n8`#R?%%EGB>F7QXW% z{QGRtn;u6z_w?@!^1^FQ zbZ-v5(0#dk?z!okISSYs@=DqYx*NJ1dK$VpgzrRkCryd!=J@UKTl}}qZwwI&!`jqo_+QE(U+Tx)<>lkXUE8Q=g-QYTrmx_{XU07%jq|(N^g#v&?(cft=Rh}+{TAqU`^}X7n~}r&cOh$3&dPh= z8TWp7x-P%4dscaWJ;(B;tPPt}rY3cB{1(*wt?^snH~UrN?`-lRzeB(CbhCax@ME=T z_irJU-RJ32TJHLBj_vNPNuHE+C>AzX(V>uSru})pTaP^k%L*4W3xC-_^#0X-S~T9FK7=^M^0B^cTTtF(#W0NH@jDVXPR<|qj)B3OHO5cY&S=6>v#DL z@}Lbr;LU#fzpLtg*ZLl?{OWgp;Wytu)`@<%TKna@{l@Ql6Xy5L@1D=Gp?+0jShZK8 zTzC4U%1Z#V0PStS|r-L6??)gBEA1v{a)*?zKfzbGv^Fc5nUdU13unQ4r+UozvY4x?1(-+NX1`aLhQc z?82FDj>&6Te+T|%`mOuh&3L{0Yx#%WJGZXC#v%Nsw~@_hP3BEGKArNt-!Gp2KJP5! z>~G(@J~97b1s&gA&{f=?8MHNhW+dpO?5gzisOWspZcmQijE{}JGk#Cr|9$#*lkZ`_ zdB1D@Hk3=~&TDZh&5S~js{LUH$Ro9^ER&V|1N zI_tXXx*NKGv)+&R&egrEd+X#~(>SJXWle2OZ%yrX?@rm%eY$&De}C^x4&fc&^QO%{ z%qo22cluP;?XRw^ebqg!ds^qTPEfO8#rKPcJ63@92p<3J^DXhscd73@i@{q2_kTYq zI(7Dh`Q3B7HY3+rFWDcK6g;&slK*en)=)ExLJ8rh$C7PeWpBF^56n&VzfV zd^ri*LdZC0`FHL;*EW6UUBUeQ&G&NAV;jFSJnX*Fy{dRtUUy)3mRm(E$8U2z*4dk< zZ|L6My}fo@c(-MD_HXuTACBd0lh>@+xS@Mh_soLs{O;1GlD0CBBt_e9t6qzd-fSnzcj5dIiWkZyM9IY((a|xmQ7s3 z0o_r^IN`hM_k-V+nZK{D6a8*5<-0NH8a3wsKelz!TZ1`@F*BuSIblJ4^xASkY z?%xjGzgeIAgXaoCTMEBt|2QPttsC;Y^fyaA$MYXftVi1C#&<_>{N{cq`kU{M6Km9j zhMnD8yFVZNUi@8TDr^rSWA>)m-vvIcIQw0hdC_+b(N1>#OCHa@TYQ)QuJzsQJG0~I z-weN7G=Cre9VypszR2NKcn{LJ{VUG&d1HkWR9^^7o% z*=)<^teCsHU&BB4H+T1M=5A1?>weh1YwNme9KB6!j%zb-eAoT>Jq$EGxw~dp$_kDr zY`^V-MY~Gh-C9N$ye)=n+VmFK(Lz2H0h zcMirMDc@y8cl?(5uH5~*?x)3Xz3$&Fe=WW%b?^8t!}>k@JBR3Rm+r^k=dki!=>E>} z-T6DmcirzCLf5~`fA9S+^_%Uxr0|y?f4_%|cArf@V84Sy_{X`eOE+~NbExsRN`yRCGJKG)kKWd-@Ahp>}uPum-$}I4f|Efs*W$bMk3H;s_q)GyFm|*2H17Vr;-|%L z>2B8V`G1ax{@&jGUGax3D_<9qOBt_xmuLQ-@qN4K_Z#0ie&6`6`~A~*jz6`3Eq?TV z=lH|K#y6?^JI9XzP*AM-X(9S&!f%f6@7R97`xDUpo8!lX?(Xj#zu$I$fBPeVmG8Su z_k!=6zH>01`Y!){!FPG)A6egzi~c_TUH+%VZ-w6zf6xCu@wfb6Q0h9)&i7rW?>ozP zm+!3KjlMH8c7K=rp$KZ!Klo`X`a9yc%y04E8NX+KPw@OM^4<5l`gf7SFMtG7zL$w!xct57_rC8|-M{z$5dFUB`}W@!-M{Dj-rN0s z?{E3;Zjf`kS@|w3C}aG7?nj5{_nUoxfwTnm;b@}U7mTv_lKgtlfDc8*7zRvJ^nlY zcm3~S-?M*b({yXxw$Zw6`NpjzBey)b#^cKvHm*;;Astk97C%jo+oZyT5PzzUhbbZ(~sM5uQ?Z_B+G(>)%_o# zyiV-F?Y}dUx_?)4{8-#C+HDjRXkE$i{W|;gsuLmIrl2#oEk)gCdF<wg9-wfp& zugZ6SzxG?Kzx(%7)^4j6LEAHSrS52Y(#`Q({JZ3L@pyM1@ zY>X4Wi~LXprL9%ppNe)HWNZCa4CeUF2)g+BHw*iP13PYZe`n_S9`IY_yUy>--_gJM zep~)_lj~O4W_LD(<2T!H*6+K%GqIVhcf1T*@%vr$#)t1*-$ib!{T2qDz4+tEcMis% z#y>4Y{~C9HUjZsTyIFtdgCcT!_ix2NvaEdVb>Er4@BYrn2s*I8@Vg@O_gCM0MAg$G z9nA|*-Mhc+%!9`ZEPdpDzY_axeophdmE3{u9Wyp8nO?D`e0BG-?zL04&F9GYnp4*O zL+&?QcXujl#;UZL-aQ=Zza=An$IJ7%R(<;(@$I|)XQA%zg5O=fv)FzY75?(Wi{;O+ z?}Fch*jJTrX*k&ZU9|iDqweqA-47EEyD#Gq{;^>8`gMIbIKHzuT>LFA_uD#I<+rf> z_x$fdqPy4T#-`N-n#ubqTwoLa@?lA0m{m}Dg1lFE_QLwr99`dcu|Ar!>4qF%;tz%I zER5e%zY8+|nEsXWaDp>#MI{7`wmoeb4^R#rz}syQ%1JF1GgU@=V=s4xh|9Yj&=idP4r} zD^E86^l3ZRuAO~K{$=<4to|&H?_6wk53(Qr_5fW8{9B^aFr)i7OLypkoJ~b(B_;XY zLERcxBSHI={JU4*o%xu9@3-ua^WT{nd%jD2xBAY@{9W>Uk!bgKyYI5!#lQ2N=Xmyq z=Qs0r6Lv*ox8LI3zg4^KmIWWmJdtv&{Al-@?g<^;dEM1@nT5d|-`SH`S6-WSuzO?o z_q5-eLD@Z#gRk(1#CIOX{oh5tdwmyXUhqRibVARRzDeC26DD-F%Kwr7F7ka9`=fQ+ zE}os1UD}=BnpMZa=T`Hb@q6Ic z?;hX4g+=anQReT>KP^O6f6Ipd-uZjME8FiX--C94SNfjtedll4{c7^x8GmmU{hiC! zdvD&GN8MYxmsQQmo|Qf&zBhv7cdnx7_guDi-3A@qZm;f??d{)HIrx6>t^N*bVTdw+ z7yYgw`u*!~(LXHgzqzmHo|IeBy>aT!c^vWIxmfdR%9@)%8y1$#lIPQzS;pA?U6}bt zVvFeStnX*Or+kkBT~BQE+o1cq!EfR2?mv^h3wL*ak6N+(yXNmQR=%A2@0_M(KTdz= z5fWbTz5Dwq(Y}Uv$l2QSJ!v9cX#wPE#der^ZgHN z=Cj-nzgfGhx=UJf8#rozv$AgM@0qv)wEw(yp({sUJ*#lSZ=XNK;QBeT`IBRrP}g^n z@1fs8W4h(9MEz2BACv!n@VCfcx9=j~li637Ep6GgbQXCtg7D7&C$y? zvwKF*p4A*#r&v2Cl(%*t;rRafx6to{>;}QrX>xqAKl*-ji~g4E{>}6I?r*N|cYjNF ze~cAk zIKoz%S6|Sb-0iqMbx|FM@E84v`lwbn4x9DA*}f;q&1E~$*HJCsT~_E?pUd%`C5d(A zmDz{8H+QdUSz0|me@jrWMK_0Q;KqZ;kM6uH|6S;lI@@pN^6hKo`AW(@f4};LarJkx z?+u`2viiqF(cikdYQHUhTfS8PuJ>K%)rapU-|YGM#eMWCBEB%Vz=e7XlGydlmUMs$H%JC)qPy)4ize{{i|1QD&UE=#h(eE5*R_wXEAjU!NyD;1L zpx;8fwSMcUsr)vO>o(kCb0Xo6-<+x6g=(jDPweXMs7tY>D>&_ror#U zzhD0j?*87!!8gC*JLC5q-#L}RaVY-1>ASe_jqiO8qTfxQd}sNtDc`-Xes{qZj<4T2 ze=lSc-cj3K-Cx`<5?^-wnTuGk@RLAiAb|%hY-O9C_b4Sc_ZB+grOi$~t(_Fen^cjoU(-!*?d{ziU2R<* z-3{Fx-E|8%ezSg0`yTN9>2KNY-vYnOG`@SVHq6i8?%v(i-PBdn#UXs4qr0Xvx1HlR zGrRDD1>LiIruNP0?(UdT!SP-Ackb`f-!DLO8Cu`-F8!8gT^6w|^*}erM7H_eizY0c z&cS!C0o=V1d;4AJ?RPO|k%I5v-i!Va{VnuIl-+Go-tz8O9N!oJ7W=)2z5BQ96UXoJ zZ5+SdzYBeLXa6oY;pw&R?=l?U_xu+7y_mgQIk`Nrm4olQMD2G5#_sO|-_^ehd{+a7 zUdl_+z}|+n-3vIr=l>S?ozLD~(vTg{>Dyg%rTcLAcaB4+PM_uY&9k0W*CX$@es@^+ zZ?)@M-}$?bckfudV*QHr?ZwBtzYBCh%E}{FkN^{|F8;uHcjJtg@=MtGn$o@te9!yN z_dV%5pU|4`;wj%{zVm#S6n^vl`cGcbt=+SVdwzRy{N`cn`R+7(^^R_i?|dx30~&uv z{+=lReF|IqZ_miI0MLo5_W8%Z^UD7gXPf+6VbyQT?-AYK1H1Qr*O~M^h~xZsJ`c98 z?`93(tw5DOUsmFG;qTwS3ow2U{BF-}cv2GyMQ&lSGw zH*@@U`>p^=kvdaeee3>i+5O$w;LC4=DI9zyztg`<{$~CzvGBXV_jljhWty9*{d$w=?sqXLQ9N+C#zjOSymiv>z5@EmWyV`f9>#O$dzmff0 z{IK|*ScHh<+^V! zxwPpJ$M>sjn?jfRbenLvmD&0PRc%@-_kH^hEz!S;zj=1DWjWNDb*Fa!Ryh4z`nz`b z)$Z+Umabcqw6ow$_tWn0f@izGr*a4rkv7UA-~C`?u3yf6(fdhu!Dq>{-nr{NeJ>gD*iR^k0o!Qa6J` z_(j%tHBn3dT2r~-Jlz|=>wxO~*NYqCBZLshP_@;xd3&d>Pcd@;x`0U*Bw{tfuC zr;pY6$2`%V>b1Z7=Khx9_`Q$qd({u_*2V=JIQWi&{NoDl#`7`$Xekk0`JL~NYWI%s zuHXN5ul&yc`)~Kg@A^N)yFs?i0{aOP2tT4h?x=uhknaA``Xh7scZNSxyK{c?eSgx; z+TPL9@~8Mu3`hQNW>&t(-#3C>)>k6>-RYOS{<0jNm~3MldJ8{bcS z-`Q>d`vfcBcg@W2pwsO5zuSK2W$gYg|HBqkRQdgM75#ngx5)1^zioeqem98v_I=`a zzwfQzYrjYS*4z*3EFAt5EPD0e_X*$gzw@m$ag*!xN%}qOcU3osyZNf`0`fn&*NTFC z^`$%aH$N-i?<+Cik1;=hy9yMTtb9*?oGKCh9rHu?`~UCTemi&n{{MU1mG9erOMt!q zUA+7F_vW{^zN`Pf#LD;N`==jWqBE3Q4xIjz%kf>c`!_Qp6)A$cyWv0nm56?K{^<|e z26X68Z_bnfsn?zRn~(K}+V6c}?n*G1 z@4HFNcitbJ-+38(zRUb@`7X`;Bk-q>= zrQb>Nzfb(h6}`Cq`?~LSr=I2h=JH7Rz3F#@uffXi+;V*1`60=24#eeu4nUkM4qDlJ z_4}^w-^zckvhscJ_|Eq|0OZ={@AAw)`F_k7{jTv_ZGwf|?}ac8=f2PXuG-D|efQe$ zReR)r@BM8k`dhtw=l1}R=#wS&k)avRzxmMzcb0!w`=Rk&mH9^`XmDr!Z>8VMfBXIB zI{4e*d(QW{-%Y-Ef6xBT8~R=6caHq;)sW$x#NSLNE}GpOIw7Y&%l}aSE(sdk+47zJ z(UlL~ue%TWbSrhYcC~c3aDemtj~Gz;t$`)?@AJM-{LTJ7{`(V9Ciye9yYM%|cmCgb zznA}>UGSTQm2V?BKz{N=~|tb9*?R)K~U4Z43%{k`!w%lD1H zO}f9^|Iu>#z4N_9|9*suc1-g8 zu99(`A?j>Cku3wC0=NA{~~$@3EE`rxxgTb9`t15%gQ2`#1BS zAXfA8MO)?hzVnrQXaAA+ot<&&ci|s;-z7ljmbr?4XAb`^`#b4(_iu~elYhI*bqCCj z+!D+2o4GsxH~aTZ-OIl-vj#1X-qrnvSnpEfzXhvWCGnBSY7 zH4g=U=aJvvy?f@C#dEUOwSq1g@-F}OJ@V^!hc80k-+q@g`pzx<;YZQ;cG2!F)!PG> z_Vjdj_jGdzzj$QXny z_u|9flMl=Pp7+~8G_pIjzj-gmjqf{Hr>vg!=4&^{k}VC1MHRZgndE)C^A#_E>ZoV4 z(~9%Ue>41MmFJ82A^)9;@yF92P2i!Spx?W5zGt#F-6)>z-<{u`UejL5QEkAwVA_Pf z+1-8Jlk@t@IrjfP9P~T?JAe1#?**)U#ckhNzc2W{LFJQB?sw_$)4_eF;O`8gf%!}K zZ(sXe?YrpNl{sPZ$pybveyhp-F5D*Sw?gN;>Gw^0zPI0#>we?0Gk-CM@DB3}-=n)% zeEMV5U99(=TZi?xbG9{TT9cQ9&$a3Emsg)Z3l)49b@?v*J>xr%@R#p?uSA3MXDnSh zXY)(>v)#*Hd4T4+y{c!eSUhL_iyKQyvgNxwy4$-tIKFeU^~~s*);*2mJA>!a--5q$ z!hbW`$#o|#uAI@xA^fGF#yzgvsN4Be#&^N)?`+*)7M=L6&heXzt+}i`?l)sM2j6ej zAIH8kFxGun`ubgrx$e7~=x^cgj@et6PU@(a>n<+Kjq>LBn#6kbJHz~a-J845Moh2l zi<(fXT#b;gA0eSO_M-9261?cJT-on4(A8h=#33w2-ozK(U_;~m>CbZ_gP zRob23UE5LBR?6|4TT>9yZN-MQW7bs33WGwQcRbaU{9 zG<^N;|K+>OXU5v^g5T$V=VSi<ew4Z_#e;Ka8yHH6110)!mLKDt2|R z=-x7Q-#m_*?;NbzHRa8X-Ob(2z1>qe_`D~VG5&C@6a6jm{q*-q-y=b3BJ8)prtb!z znpWh`gztjgd%s7mU-tBODL6H<{qP5k)-U?*!~8?}JELfOcl*@N*&MOo-?P?zYP$EE zxx2Evs5P^JBkngN>*l^0la_UF?Vgz2AII_g;-4hYL{8NAgDZbmvrb6sPPov$xO?N& z-3vH6KCl)w)->05bMVb;_|C}qqwqU}X!jKG2Ae0}U;h^9w*S48waPszJ*+#RyJlzi z^6n|!)B2`zT>rlFw-BhxH2E&ny%%(_VdH|z1Km5jch6h5YC_4v%5B}NyVow5KaHd2 z0xO@xj55X_dZ5f0@%wn%_v61MyU+dU`r7^d=8rB`KE>*9-$6@bzY67i7j68`x&Aw! z@R#p#&qM=bmu*|I?iJ`f9scemAGJVvL9c4<((Q-O?U4WO)V=KMAJOgt#Y5e!)#b4n z-B#UMv$7Xga|nNlD*nyr-ThmNBWRWFcZ;*rI-BLXD?-hyi#gsUvtItX;7Iotj&o@< zehbL)Wi@^Ke&7pZ-FK<)Q@_iA4tjqo`d#Wb(`wV-D?@)TaQZFtoiXFV_c<57&v+>J z`^fKRQHR?7>vpgEZuwnG{!;hs=$Pab@4H>M%kK@W zEyW$>-BsNQ3kpwkbMRGUe`o!E;X5t7hk`>!agSJx6^M)dA^t* z^4~cbr+t_C-Uc%E)sHEnzYBje{f_=EDc}7&<$KPg!);T#K!Z!giG|69X&m1<;x2w? zVx2O3;pXnG-AB`x{1%yJ(-*b5`w++0wcj5*g+SZlP+}K^;-O}IP-`(Fgt$#kp z$?v`2y}Orw`xDn)sPJ90+voRG*0!qV{O-K&^w}}bK<7@Re`ja>&huSZv}fLgQTNw@|n3?{ln8scm`PW!);*LD%Vgx9q-hdHr`)j+@`l{TA$Y`MsXC!l}t$y*sBn zwy$FcXs=K9cb@O-zl$)g`Y!xK^59!CE7r(D(-EzF#=C|r^iIf!i-+wNN{?`32 zl6pff;kV;=q3&(p9YKRi9N$B~%ic@)&h^_-UZY#}k3Oqw!j3cY-Pcc^y|IfU>J{s6 zPtEk-I^9kje09HbKqcgNF2*16zoSG~e&=N^Z7y!E@2=}^?`@mM;r~7F`>O9BK_@AB z|E>-B?#J3Tr+T+bcXM}1S6mB6{%FIsn*W>Mxiz+-*<+B%N-ni-xYGcGc&e+7x>}$U4Z$=_aA+tzlBp+H~TG0>bCEUEB$Su z-Cf$9HMx63cRzURF7Mvb{axhYr;m?0elu)fb&mTj z0UGbH>i#WyJ^Lc)9-R4Gwyq31)wa0%yGi$V#_z7+(FxX!!$nIu_zKHjfA4+sUEww3 zjqeQK&wpY5q5D%+^fz0#wnx%$7LL~H@2uT@lUSD>+qLFM_nq$f0p0%HrJY4>`8n%j z=K6Mn+Bb~G1-}K_D>^H>t2k6HIe+Js`|jWU?7Qsd?_M(wPuShPx%+$YZ@$Hj-5kEZ zABvvaoE0kH-PqOG*;rxc90EGmC~ZUc!S1O&Q+ub)zkPhyEl|C^Ed9H{m+vg!8NTx} zo(7ESkT{;>fHm-5iU%mwx9h?q>ZR+HL&XwN$A& zwLPmlkK?!bcg~_4a*NscveUowd{6z(^*!o4C*zIp0^e7B7iIq8^W&@N;u*~;^4)pO zF*Obxzd6`8e79b>3Oqi>Df-*9JMwp6#P1G{?_F$3zny|Jg1f`JOP6;a?Oxo!V$wQ} z-$HDy8SSav8Qtn@O+mGfSNE;&YCFGsbDZg3@}0}On|1n;sYko_bwA8G1#XRmB!B1o z{_;BylXW~aa?m`UAk)b?uFfRx~Eij=XM8{y8m|gZN>4MSphVutMFaA zyM|TcP}(`UE#0qXU4F;meuuTZG(I8&bSUHE?seT)wj6u+isQT0caz^n-M`g-%d&on z-(@cco|$Lmx894~%&iEjMv^jo?+{kJ3Q@9A|hzh`$_cmL)%=klGmg(Lm9{dcME4c{GD zzt5PpbqTKlsiiwCKCc_cPz+g!lYV`LRay zH%s?#gMirIxg37KH?elLbhUIhce|c6d(+J!Tyv-U;erdG$XxbaxVW2Dc#lE1U%7R6 zYIoJL?giZwd%7nqT2`{LZf`fo_o{c_3%_f~f9G?&^;=3#`%jo?QrMhFC)a$p`Yy5c zV40b3=5Gs71@N6O>pR!?r{6gk_k9=r5ek~di1@B9y7znYZ_)0^-_5LTC6y_@-ErM@ zYr3~|pI`Cy?h}shC)ws?PEYBM;V?`6Enyi~yJV4E_XO}s%isV1kQV*x)?M_QV@Ee@ zT2K&Z0Fbd;eTUU|w(iT_d)BO8vnY8};i+!WB2Mw|nxONB*0*hJUdOSW4SaRs5u^)N zb(ef+Uo3o&*gIXn*Rp?S?p_f(Lycp}_rTw7-)q=}-)#fm@v7K;s$wPRj#vBN8DG9@ zeFvS2t|XNIT{(aG_nxH>mkWPeXe3%xRGOZ!u5|tO<(qfO3%~o$&%SEy%(dT@PxXCQ zlPm0Gt1jzkkSp#k?kVo}?hb4$wdR=kU5vH&-s)v{yE(oausvS*^`d+}TV7#X?(bfU z_TN+Ge)q74cQ@9`Rd%<}&FkjiGpbwuQ*t@u;_pV^Wxg9RFaG{Q^mo$B-|F8bzl*;9 zuK7LodyLk1?cbumMYVqG{Em?OUjF;0Xk^7PP()U;f7hOOboZY5zQ5Jwepj;pwkk+U zmg6((Ud;I2@4E)`cdZ}SM3+n}jgkK&%HC~RUZv5+QT&5@F}p!;)vWFt96!!gvHza% zo3Xo)b@qg9^Ou6016r=+`<;#P`+@H=%&UG>h%RSab#lYBZfwdCv8%s@)vFwY$HYeb-~Xb#2|b?(5y(?5_W2=r-zh2n+tL&B13> zyZ6WWJ3`;re^+z(&L(`}`?v3UqRy$Y{&HU3*>7#TIZ_LXGeb|LKmTt1UE(|2cQyI$ zvjrD)ws8nQIK64!itc0G5B>J3c58GyM24tvEU0JQcw)_>{oSX!HzjxbbbDrG=;U%# zv42f_72o}vmxIq}LD_ee@63$f7k*b}{$cQavgi+2YxV#;*54w(tv&VqbwG17J-=1H ztA1BG^_`jhcaqreR8WPsmzB?``NWTw@4}2fIBzoF|E?gKURab7b~NM3cgydb@4qX` zcYpT2{hM)B>a?7G%LyC{?|fg?z3sd3?`z#!zu&UHYT44dz4l4|fuL^B?%??N;BN2k z&_|$C)s5R{S_-_`w08l=crM{ZacRq#op*Zifq1i?h2OIXtqGLjBSYJdx`@G;P)9W1xJn^n2@$@BfxD zF8!|aUH-cP^Qt=0?+WwJFS&W_i#_-!K3 z_pEZmkALeKZ>|5j{=3!sZyT7uzXYw$i}v{~^*j4__UYfk-=n@qoc%8PJ@b2p*LT_9 zk@64!ii<8-H*H7vA&&3o*t<_P?ygw#HGSo8=I-A*-M{s+l@u~KOdCXl^Y?$2|9*)5 z+wud44=oE*lKXv*-MPduK+fRD2hobG#^`P@j^BsayZxp`FUa`KurBvo_jm2??^-Lr zGk@90!S}3t!;eoJ7{AY3e}6smcbOkAMeC-t&XL>Dy>`NysT_X}n6Z~NwAO>f(Z^??VM@nX36o}`gb&IM@MHzcPB^s4{i39->cHQSsR_23T(SMe!H{Xz5Bfu zG(o|`@m*Z|{BK3M?=iolMY~f{!|Xjcq7zwnU08Ik`%L$x`u)YLA5?4$?>6W7eO|1n zYTiQm?pb}aCe7qn*U7r1YesE%0Y_<7MX}uXJ%1QPo1A{%?cR0xdwMtPy6&YD=S|}P zMc{Xv4c}QdT;1?pcfHVe<_%37POLB4aDPL=hDRHkHVFSZ@cj)aN?G*6G~(CiZrZ+L z-6r|&%{i+R`c^DkHFHfj$F_wTF2Oaberc7hb@dZlr_1kT6aK5QZ)v33Z|0(qNcqI> zw3*q#IIUHHzh?%>hx zI~*?!_xxs%`)>MMS+qMKG1fe5-iE0=yAOA7Yg$*e~3$4-*=ltyJz;zm@tE5 zSr_Y~uGv-HWgJEI^~GgXvlhyIPyW*(D*QuZ(VFRpy03R1YS~t^>RH~-`0l{&-(t#z zv8^1xgT7md3jbwTJinq)zPqHNyr_#KyOXtPX8Ww}IUGyo&R-@6it6uD8-6&hXZ-$V z{rwHhKkm*G1*H_Z@b6Ol*%nNjIDG~v-Gu*eU|Ui>Jxi{%ySk;ZmgDz{KR(?_-+Q{d ze@i)cvqIC_T>0k55 z`^wfO9rL<(ckh|8XUXc>4^DJ1<6oNHgD?Q)V+1W_Ng4Uf1Zgh zSv&nu_qFa9O{Xf>-mX05-fi5i>)~Zq#_`+eyR2w;aanz0SI6|O>D@Cp7EYVCXyLSm zQaO;fe{gQxw&A<*dZA|j@ZX_(f9rjZkrV#w@Ll)y-R~;$ z-<8a7|JIcIZu(tCRQPYh#D+f5F7fil#)|UBsq^K!mo4gB*3Y5y+h0`ppTnbyZT{Wr z-8u>WCdCWZO*`5Bm}3rm_lC|ZxjQ*5|FDS0lpOlbEPsjp*^(W5cP|e3&8^*{;VKvQ zejf(`8(gs(6_ih1$h3@*Rf<-7eihF?qI5PsmG850!@Dwlb_i~4?7`L6k$ z|Mqurx$a95Cshw{2w!Qqy?OJ_?gJd3f3xrVEg<(j?zg^ZcV1~;Tpow;p@z9jr+_Xg zT-Ug&V(HGZMLFHc9KYkgbBkU+6zwA4omifpl*{p3{T8e6F^0U^HS@a{a~wLh=B%9X zRfq2le*#3iLy8ijvSuutw77d&_nyLy5z9H053v4bI;y|F`)c>5S*uqr>uBf%b=OlC zSb86QB;S2>$W(5UpryR((d&f zo3fW@$O(TrkX)FZVfZHg^LLlm-?hGn$$t-x{I2&qM()60G0}B<=H3RcZH-~?zR`yMOC;|5gwD&8nKg0ZM_p7KQv~mUm${D2k1c6aIK0rug7@#;+5;T$Z=^ z!z?QN^ML8Rgk#ch~Q(+a5lW=d)Z;_9O9#(D#Ph;roRj zd~f)XDEcSyHoNeJpVHy%dFqvQzXhr}g4p>iyKntyy~Mcl_V;ObnRouM61~mVe|*-< z@66p-x(|hSyLCt8#s_9`n8&dGma^#?f ztNS~P+0Ea4-NxO&d2-u+hl9Lw>3jG2@9CF>R^0Zy{k`$7&t2gs-@ATX5xul6(OtgV zJiJD+lf#XDX7{X~ncW=1pPuYUwzG_kHkUW)PT1c1lEa#9>Tksh?%$=mze{#MxYhlg zzx!^^4%=lM!f%c*TDi0PO!s$Y$8*2=yMK#yyE%9KmgTt3)_ZZ@`H$TkApd{Yzs&f3 z-)-ja`@ZXo{?@dUv`)IhucqS(ch1{qkn(7+ReIQ`>NS{yEz^m^4HM|4KkK@?M_%-uddU1E@29lr z9~bNIAK8BE{VwqNEgk(kgX1CFy6?$%zPo)_`yK{~p~>gI$6pp&dpF?j_maB-cZJ`4 z|M~re=-JH+*L0uY_+WGSH~VikYiBjNZvO>YXOlRDKRITkhjzPg{N{dQ`Ca6@@YB2B zx#YSpryX=$$D#PWUR3za`R&O*{&C43?(v&XoZPxlM4e zt-SCT+vo$2?jAgL6=d0W#vhrV7?*w*`5pvXFWp-v`rTvOcm3~P-~Cg*8~*nB?H2c2 z`*+aqfE~YezPri4`u<$>w?}GZP(b?T?;+p4R~$ThZh6}8I5|+#`VsU%=zH#W5wi!v z55DLA2on7h^j!p;xy;zh?L1O`Gk0^Cfil;3wjX|<8P|X3|L*-=koiZ}k7&{Ff~L2B z%lwwIwfQY8SMr^I0sG}OTaO)Hm*OSY?NXZeTUq@#qkU#j%WrOuA3opNelKG?^qu2- z(dzE+p`aN1&iFm{;dif3LQB63{ALGlNfA-_E+YJ9-H#~IhVNYLy%%R~zue8SZ$(b1 zzGGXYyea#R?#W#X+c<RF&TnUM8vCyNmGS#p(9*|(@4ccPKa_rJ zv;A)PqtN}E;rsON?%&GaMY_AcE1&CT{T=>Q^tWyIciZnPK>6T1kj(;_d9zx>x|Va=WgoeII}6n);>ALTRyluXGO<;j^Bc8J-=BW+I<)5 z=J+oE>eYAo?~)eJesjwwbZ0lD<#R0fF8zHzIHP@M{ND8byZ9%@72k!vM}FsJUIE$$ z<+k&;)_1S(qL1Eyx~=vPf3wJicc)h;CrcVt%uIzsB}k zE9F1emDHC_uUs7bD596+kO(7|6N4!JInXb?@WrIi2D)yqf+#P z@zvklzj=%uRpq*Um!ura;Sm1hken0MZO8GO@1@yyneWmsAAIMN>%Nh*$6+(aj>VhS zc3-O&5wDzcc=kF<^!k z`7BGnGkxdz`n~Nl1M-p6@{Q_8*V8Gk)jY{{8F@<{$Ha@QQx7v-r;Oo9j1^)o+g9c5=TN*nivI`OW^F z=R4QE@9f|0s4 z{C;3N^Y=HP!R#ns)~eRl*6#Z5hG`8`yL-DQ_fF{L_^!CU`_JCrn%%5rP0e-H(^}@q ze}Ba`f9j;!Ga4tC%m3D70~zz<@^+!8Tg$e6Kfhi0#&_l)J)*)d8mBf*lM}u%y?f%M z{+V;moZEe+`%d>|uWrZgsPbGjO%CA~zbF1~>h}6!-Q9hUbzbw7y6Tpu3b~ZuPy5(U z>^pz@#q;ly-^CWJ>ORHsR{xQb+;1Lh2j;O`>%W#@4nl8I;`8ZJ1#ddwxYD5sw=dcqq3o;u6kC1k_`~U?TaI*};CNwqSwZeM zue)8jJjlb}Ww-y}-_H0$eLM3H_3xgdzh$l1D;k?2mV7rw1W@{S+4XGmr%s+RyKz#v zeEJV-BevgNe@wa~Sif)IF8aH-`+La`{w3Y4pjiI?dB=B;?Tp_|wte5ajrsfYAN->;K-JRSRQo`{)^ZRsm zMC2U$y_KzQO5=37W!>wyz3S!wTXK2Z_sZ>z-_^E$KffK6nm&k5pE7m&{FFJdza79w zXXO44c=B8PyU^#m&%SefXS95(C2!vykdR=~HNR_i_tft5bI-2az5n)ueRsR}b@$ZF z?&kP?`;VIFf(-}0GrZ~MxOT$XT+h$dTHd5P@?_q377o4x^UHpG+|Kx&Wyg29t=}cK zGyll^aaZ)W!=GKOU2R>h-R<4=Q=7oID@^R4!|~na$ARvPe^k4>Em_MaH_n-Jn5 zdp(sy_>)aqVo0|$hw?M??@ZsBp56Z}*L^whu+4f7>+kDDh2Pvc<8SBY8Deeeedyx# z)5q`1|31PZ{OR#|KMNaIZ&MT3)3MFK>zYGev?cdLDW&UorO%xOrWt*yY ze0Kz&Ka%@B!05a9Z((ICgWnv#8SfapmA~G7bldhPJ!L)B-HqMul^(e*BR``gqavdvqYGmk;|#_XjOQ5dGYK(uGreFIU^Za(VoqRQ$$XIc2@5-m zC5sj+>^NHaBt*(#KX=b!4u81o98AkGp{VKF>f4iCGQ;GoxE3hKk~8h zMeA?zkq);|55%M{I3L<1f&Es1;PX}1@Z-E2^3J4AF+6`La}DCsbYJ?j*DFtdn0Zt-YtGz zB1s}!qFJI}VztCwiT9HBlKUiYNPd#yky4WilFE^4lbR;AOlq6dF{w*ZAEcS3rKK&T z)1;?J&z4>*eMI_(^cxu=nRJ<2nI4(FGH+xhWK(3bWm{xt$gYt+B>P5AOwLL!POeI> zLvEVf4!K)$kK|s-eV6+u&nYh=uO@FP?<=1uUn)Ocey993`7iSS6%-X56&e(#D(qFb zq3}sjUol8AOR+|Amf~u~Q;K&K-zfJ{6P7W@-G!8 z6)6=h6(5ygl_-@&l~R?NDyvoQs0ypPsHUk_s5Yo}sLoPdqk2yDf$B3g2{kP>ceO~h zbhUQ1fosPt~!f7zi8rg`R<85b0*E@SbqMyOZPf6*1neRw(i#M%98f1 zPL7)Iv9s87x~ry@?cv}qN~~@!>f)&Rt~HB2zXYUn%7ocdW^-)5@LjLF+l{q5GP^Cl zouh`GJJT_*TCyQdjyc_|+?}ug$aZ(vurBLd*fFDygZsMx`=U9QW_k8nzwntJ=CF zIDTh1L|sc}M>z3&7u$D0fu7vsc4K^QZL4CX$&BtOOA446=y?4Tib=^le z9_y@e4FAng=UO(SdZFCW?&{Y!yE%?6iU~_Cjft1%PV25*T-41`QCnM4KC^DVJa<8L zeQDl?lFe7o?tdWvy`Q~%@1lwO`#5I(uCHN-8vXkmdroC-rCddK|Iw6g4(`cale+u5 z=XXtR>#pstDX&V+;P}m^(*0e66`N*6kbJM7#U9>QGO>FvNB4KhX;Ut{u54P^#d*JB|$sOMh z!92|Ud+G1Hz27IZcJEm@=>W(=Z*Ty#bVqai5n00i{qzs+?%z9Exw~U>n`7HKYQB5V zVh`&poz%UDR;+4-yME9V|PhXy>DE%6NkgauvhPn9=|Ksecf|w z@ZyszPA>00*}b4~LBnJYlUBB+d+SoI%{0l;sMJ(^$&#`Mm-!eJwBYBq#x}7;9 zi{kx~J(fL^>pru7#qyaP+&%Y}eHZx7-hHh5USiL0OOESoUEk&OUx4m^{4HXj{aad| zyUPN!Yly8opxa_=*LO<}d$yk6a?kX>3wLvT2e((`xql~NOXbKxDZoCnYg+d#&~=Y% zZyu26-rO@~*7NoGuAZ4WwsH;KP2Kf99NgdQXR$%k!1vcH*>85wY@Y4FF{iuzJI5c< z?)Kjtv%6VmU!6VWdN;?)->=!A`HuU0E-ao<5@tI4Z?3cc-&ww^t$gxU?zvwVu`=+j$owI{DW=7il_BHz6!P-8(eOmYA z?io`jtn1~N^*f}7eO&d)L%%*0bNW4}MqK@^$9ANfXvj z;^0nPle0x`Mfc)aTh{eu%&uA1y{LQUltoK8=Ce06HMKW(bJTQBULpTojQw2c7CSj^ zpRmltnz^;JX3w56OTK$z+w|@!9J3e9T()Lrd6rxXyG53HVYf2}cXwG=Yi)CNQ)Odq zHwSlB$;{32-J2FJyt9IX`+FK&^X<5cZrvPK{+=58djf9ApX%PXW%*@}x(Te=5rtI= z-3i^nvpe5&a8KI5c;}Yx&E3lrCkAtH|4w6@Y`w$vLN~{q<7eMJk2+>2@6#QTl<&+j zqm6a-zNK@vb#LoFT-~eA!QB#G9Gldg+?~I*^)Lr_Oh#o%cM(U%!JJdKUhh0F*S#fc zRa7sBX+JA>Mo&$D_q6V*OBT+aGP!GB=j84K-3K>!pYER5GP`L3$D!pBY}`rRwF@G< zeY?Y>y8XHn=NE4;rwNuq!_#i}@1MQ={E^QYGkNY)D>m&qyxz~ip&%hLDD&D2 zIqt01{x$O7D;4V5w{%Wy>CWQ#t}y}xxD)z$C==j{tEf(CUSpN4*m#X-!tjbxt$9Wlk@9?QswHq>!w$Cb2K(KH`GpS zo+&@Qd-~Mb-5iJKJ0!%{I0VTTcW2M*T*6VwHX&tG`G#(e+q+I&ljp9SQm|;*H%C;LYli%E zws}()FWhtTw`IIscS&YywJOvwyD&?6&xAk@J$ZVNK2U#P0O&=A`bt?z-;UzIu)sY>Ou>UnRGy zds6n)5{{L&zXg6zJl_5EyVnY1)~UHOUAJ^|?CjpPMxJ|ie^Z|PZ$0+z==Rpw4vzZo zM$_5j``Rah%ld}SUe?Uo)Vj<@j(YZ~z0-Rpc5`r7R#jD0%&wX@Z|e?i1aM^JkcFaBteRe*ewAw!is)Ge>({hQ}>hBFAkJa{D{~ch)^uuOHYG6DI$= zioGg3AxMsU<>B=^ukNw`&GDN#%E37%X#Gk#?(YJwPqgHMyW@-ELpi!{wDz)chn8j3 zg;a5H=g+QL(!H#E^Qz@LyN`6Q%v@x|F{_)EyZyUNLpN({dwXkZHwX9pzFBj-=W{Ho zoLf>-Sy_^}F>8z5_OSIrCTw&@Ov=yvW7UY-0Pn&W5|>$>te#oamG@!9zi z-5lIS(^w1r)3Q9eIouL=oZr1_%U*fzCEW`umvnQ?pE+axyxN%+^4vw;mBne@9Py>Q zHXc~C>zMq_?rQC$-5d*N%wBNuH^Yo@N1ZHV`R8dL z<#N-zr%j&Oy|}x#s=Ky3DLp%}g=1m~>(Ob)ws)W7;O_P~7JsyXW8xy##7XI^ySH@D z?47f?duI2v=IPxW%jeBrwkCH*p}c2ST(mrQckYAVLQ`ru7I$ZScVul{)G#loqV%u|jwuza=jU(U)qR{}U&h9`jN+2aoJHm9<>qux?CgBsQ4Z=k3G74#&Y@Y>3uUN%;4Dho%8#w?k(D^(+egQcGq?n zRW*loaa4St%+CFt{rmFo9KV;datAa8#C3aj>pxAp-Ocf&``pUak2$!%cYoJozuP^l zZiWTNqVF{&Yz>~Rd3xO(zxBQsu`QoCahcqAX}0gI-3x2x7;-G>X65equKZ_tcXtKr z)z%rc-L4#e0@%A%WPe(I zQ$fzCJIScVfun=1<4pfMgKiGnI%_}q#ca#^u54PHFc|;n-w;1Yk4ecNjbO+Tud?#P*2Df~xsT<$my+|9;EvvLxJDQY|FWVj3+E zF`?#HsGc2|4cWlao$% zpX4}Rc*(`4#@ADBG25z%C$_Gbm{BUn-R<1$)a}fnbI|jx+;^L$-+jM}$#efsXAekh ziQRb-nGA<=1rAZ~xfMaqMc*FY9|JKm0*loel%(1 zm18mcZ?O)y-%Q=VdAe1P{g(O8-F>@z!?7t>IQrP8IBf{H-Ocfx``CAxkMi6Te}gWX zZv3v!>aaZWO80kpj_(?GzkKJv`CTC5cdA_XZ_!jc^8$`K@IBKL!Iw;TrPn|&nVztX z^}FEu>u2!B($?-&4(=kh;)a6!qPEQ&<+_*mb#zbWuy6?Tg9+yS=+J zSJs~6$YbmKEpjdIMECvfo%^PL<=_tee)=FgcTq`6Vg7>BWy=>WTqeJ)WKq5xiV#Rd zp1ZuVvaDoI)q+KH=PZ<8SUI;u4n+tgBF`Q8-GjAxan01y?$YkY>h8+!w!YelEgakv zC-u$jp3ptFaW+V%rm?x0Bk;EeEBALXtGBZt=Vw`SHZh<{nX9dvSt;P1lC!|ce~v6T-nGmWge^N*6JPI^SY-@nlPz* zLidFBncW=R4gvm-_Q(CNT|IH)@{NGwcJkb7m*@KU#s5~&&!||x@!+oSR(IsN^|e)$ zf3w}s-+1HU>+h<^vvzySbJuq_bhI~ffQ~F=?dk;Wjp}Zg-aHLta&Lb>$9L2339P++ zJ^kJN9MkKzh0Ae=*Co`;cenSn_jYi6H~pQ!+TT8*b3*sD?&*`er*-#s_jdJi{AT#x z!rDH$p|8H1gL}ccIU80LF3rs?D#}h>TfIP@d&&1r3r}}nJ z-=g+_Eb8ydYV*Rf5-SE5mjE~)4y1zSBe^>sk$1!_4tL1mY-%gKy zdxNeG_^qk0|68AfyL@)lyajXTEs*2xUY5VC`^{tjp4i>p3%X~{>R#Br$E8^B=#%|d zYr1!JFRGbc*3FU7-5P8f*8OqW^zJPj3#wth_* zx@z$p(B(X{t7u7{JooQ>&+qxa3(kHo_@00EcmDSRPd4tC$KQUJ`7ZDK#z-Sj z@wd=#+2d+2<+*pQcKWUKTPfcyJUsuxcg622D=+Vp=l1)4FqQ3h+V6wiG2i{WyMMEs z={$x%@bG~Yn$6^yE%Ru(`B z?)%5iynGb7&sILSyRa>%aPFrYSH8{V=$yg&yR9tsw?nrz$8Vk!*3aa)k1mb0_pc0( z_RqWiXjA|CbMoB3`@56XXSA}e*g2zjLpR5FneN|`e-!tFjt1~!tv19EYQ} zxI2c#`pR|3PpQ1*;}qOb+8xW`c`)XN9QWP*J1)w1?=N3td4hxcY}BT*?jVl%g3S2v zoNdSCy0=eVwrMp7x8J6cmE9XTHZ5PWO^$o#(!v<|ZvW)sidqhC^8hbhlf4l)Lq&f|C6K#n`GaPRfwbNBB)zQW!lr9It4p8L0S z_;=~wG6z6&g!_L>f0qekf-Opsn>5ap7(u91|N@53T>se6#zzUH5mh-)yfUS8;I9 z>}xEQ|9ybHJHD|ZF>LIZVvA1#`>zl z71g`Hvt2#$R=)dc(#qKR9NfEB&soyFq5Dw6MxSnr?yP{y2#%TUtjqTE z?u2gl(4vS+jyiVkqu1*6f-=%$<>R}HH)eHnaJRPgPn7STz2>{@#_zJdlY4r5Cvmj) zMYVQzHl?t(t}L08(aoXsTj00)Z=v8-S^MM3y|NM`L$Ick$Gwh25*W=g&HR zg`;sYYe7L#KB!#LH2H00n_szT!@;xPgI}+lSCK2n{X6ly%x|gh5pv&IyWj0RafhRF zE~}%9XRbvz$8VP3GN-?HeD{3uo%6e@{CCmrqXx@$I2LuYayNZ1E9+)$Evv3C?@sSd zUIW@2^4tHn#&3n+>GIsxK0ekqr@U|8IeqfB{2iZD)^gm3cSi>J$3z7N$Lu|JY}ei+ z^4y_uaiPK6;twC%vHj4YxE&$#-0N26rl;rTrDf!;T)%$hiuLl`mTVJd^v&&F#POq; zy?a5&?6&C~ci7tMIx4$MIR2EdcbE57Pps$Q?oVf}n^`@*Vk!rBXG3R0cVl-`PfKqz z2YN=92j^rt?z!!(D^D+6u&w(@_qvSkpzg4I{~$k(-@Ly?yBojH>)!C9rk}Maqq#Gu zo8z}~_jji6%&aF)&Ogw7p!T-UOSIHu)lJ0_oD7uvzBbw z$?={0yT+^E3ao3Q=4N)+be9*jMuJ-Md*`u-O{|)dbd2M7$oFO6)xLYNp1!qU+Ub4{ z?(eFW-@Sidl;i%rsDkZQ%ao>WM{pm+xxUW6jiZ8{`}+pA?B*KpLZ&+47qGo_n@yP%{bKYwA#@)e5~u2^2O2vqFL*RZYbm<7r#jh&6{ zjT{=kr>lOS!rD5sV`lf1?zz(@tpklLM%1ux?3~iHU76!M&+pBwGZQAYPw1Z5J!!_Q z4IJM^zF%csc46|wm3A={jI&NpqZ5xq?wh{S$R7+ev5pMxc8lhb>6a- zvsZ(RHmYG?**UefyOyK7G%qhTkc0d1-vajU+}~Al*8Gn6o%B88cfzVQzq!AwuyP;% zS-|#Pb3t9<8Xl7Z^*7h*Rjk~9{{Hy; zJ$QEaj^CTQyMJ%m(LMWn@Q=TL{<3m|$lt-$-ErSHb$5T?6xUt-I~b&zmHY7D2=?zh zV1E?;t_AV$uzxT7UAt<{Zyu0uf`8s&|6TaKHYe*l57?zYBiMiQfc;STy_S_b<+u9p znr{CqC$6$`|48}nsPaShIos;xzbAcf`#s@z+sgds-Q9m=)w)@^HwCkQ7yHijUFzflpV##x=(k1SNzTnx=N}0boa619Vr~#-+zDq&AL8*VQ{x!_ix4D9KYFr zD|GvH2QQ3Y&%wR-cM!W_xAE2b_Z;8<@BRIsZQ83_x88Sid=J|D+mG$Hyuxpe-;%#2 zzkwDu%5z744`P4X{n&B3I>+z-k>CHbHR#ybsCRSx4vPHl#|FBOmgBqRcS(is9KYq| zxqtMW{nNw79ULDQ60{@k@S&YM4<3o%5h&07UH?}=@RZ*?e`|mHeZTqJ+-7mat6i+z zm%G0Q|FHNT{Kw*Q_wTA76Fj?Fxt+Uz2mi779sI+>x%+$7p9yC{Et8Y%zcs(JDS!^z z{m$|2yQbXtlb*lNvj1i>wO3L#dhkK+`&oAG?=0uPvwr80|0%})b>4@Ya!b1xHP5cA z=&Y#iF678xP`6;gf*H#X1+5JI&Eo!>^*4vyUom!t3Uyn#!tRpE)idVx&YRi2lw(=h z%(Akw+5)+dBe@6Vxc}6%|CU|i`kkZuGe`IPbKiNtD=gvQjsR)2lPl~lo?JbHgS))5 z5^TqOs2#x|JKVr_M6&-DE?4@^*!>%HY2CHoQr``WIJoCGu`b%be#5cuo83o$GrsA5 z-M#(f%*Pz%Q(3D6vm-paZM!|cGZ}W9bjO61*l=){+=@A1(rwu7`J2hM+p{}+U-eOr z@igWd-*e+ke@isia_F*8ob~*>%MYWo9LL!I7@Ri#?ovOS zgZu37ZR}b$w?Dl)2U>jdecRdJn=sO}=l5;wZ*Ezu=y(|aW|aTE&GY*vjFi0g%J-t~ z?o+=*x>uZV>(2B0-Q3;n^F6gY(}|V)>>rOGEI&L>|LOSC#?I~e!{ZOj9}n*z9Y5OG zxxZWemi{jN+v>N~Z|UD)`n&XR>F-wGt-ectm;P=g#|_JnzZHLjb1f|Qeh1|t_TP$f z+&`xMnf7NIJNIwL?~dOc*|-n?-t@gP_|LS%-z(X3Q9}!+&XeQr-uFlO zyXp_+?<(DU|4jMq12$v+pDEvcx+8xm|5o{<{9CmCixQfrvCORa?9AJ^!HDZ=bkoY z$~5_(bJ(XfO=*z(%gP2?P9^_$4tv9trfG8AjbSNo?G|<9?g}HvS#;JNCQD zZ`1EF-=n^pemDId{XP7*>356Yq4M0nJ%6kHR{QPo+v~ULceU@{-wnU>f4Bagdi{H+ z*YBBf+z-4?8Jqd|n#<;N}>u&F9 z;rPCF|L;<^9WTE7%QP1s${0-NxZ|JmU6~lSl5zbssC(5IUEGd-uZmE4%l0U-dt1)otA!7TICJaggoW zo*Ap;xE;Qq2xj}8{QE?A8q@A8Mf3w&q(ZpN{do%?F& zUR&$Ha96qRh=o~uOE|crs*`fMT{x^x1V4Io>ga8`?&Ad;0_SsVp1p2q_bCqU18cIv zBGWS>BD2=++rM`0UU}|gYcm3ZQ`mU~TVj>uu|9>~3uAZtQOBZSC*icwokwn_dy$9>ejQo2|a7si9#? z({%ao+-&`^^Ws)!aG05~wzqe-b+>hcQgHW#?g^kFmk0M**XM4p+||zUn+Kek!M4j*!vGNpT3_k_*~?fo3)4_H^Oo4ccbC&zalHjoZbCXxTm!`8m5 za(nK2j{6T-`}=z*bWiA>+BmhbyREyev%MYUf{c}M^J4osxV!i6-n?;taCi6q4I4M^ zkM0KXH}6gD?%sW5>(<@T-Ewi?rGKk^`EA<`LTXBJzoj|2+fz1FFI`c-j+J})#F@nv zWrfpNxtk`nOqnui(v&GJlbYn3T3VXqxnJM7_4d2Us^1D3x;9qY8NU^leb}eE z|2zA4&bP0>bARX3di$Fz{n&qW(y;a>I%qTt$-{_m7VEe>&K>zsJ1) z9mB@`J4XF`3>(--uWs9DBl{;p&sp=g{BN7@T)&lH{1)10uPWpVuij^AA06Ii*wvwRO@ojh~Ftm)k|x))VVY~=X#+bt;TyJ+I7 z?@}Q-znQ)Vv(_z4S(D$L+nrHVo&j3Co5;%jyM(PHwr4Zw4&7<}lQ}+pcRP}m|6TI8 zMDn5R-^{;*S(~a_i@VD>ywBIJmHXbn#{GTC?~LyizqkBe^thJBpmweCoUGaU( z_a)yezGr;jBF9}czkJD(`SX`7DW6|dR9;>r$NeMfPsE=ncJ4nCKcs$0uyMm=U@GOf z*X6HD&B)15%~+nZapUq;>*Tn@{8WU5(wp*}H$M-u=zi?bYp7;1$Mk zJNEDTo!_^xpFDhK*#*!+RNpo2zO#38aO*i-e*OH?jW_Qu+8P+R+G)shf0t4E&hcCA zx5{q@MNpOSol6OH@$IK~-*x1;TRIxs8`?R(3$st{o6uf@SUacI?(Cky!992B(zz>7{$_1&tg5e-EA39) zTDO*?xs+{j?Ap|=-5lH{hgg?3PwQOO!NHwkpwfKPtyJydktaa~A8uYynas+)Q18tY z^U%$P4(FZRH#{{v__m3ad;QY<%sKyaxmKS0JA>C{O+!~7ORLk~cjPv7 z&zLd2n`73r`s%`D!{7Sy9o?PXr9B*F?6bONc1;A`PW_$vJHvN6E=QN=sw*2 z!Tq{Hw^w&yVwr#Lg_t#--5h2f9_AM3-R{XR>7LWSdRC>gs-I>z$NZuR-K^iGy1(3C z_g!jD_|Aeu-AlXYPgp*)#9l8*y_=(hjr(^zTFn~`sd?pLRj(ZPZ`mOU_^yPOk-N^4ctlXEEuixK&yZfcjb<6H%&}Gw|9EIPxSg&2!_@MhcBgc2% zhzmt2{?@<6<+`IMN6!j1Fx1!W25lsg1f8HP+C8Uxe$U(q9NhEFrbKpEck6jnW_A~J zS4^#+%fY>C(fqCJ3fCtk<;A829g9D6;pm>r^4%x$KX*>z;O_Pg(TU7$TF}0vdl|>w zbx-!mEu31JCeIDp))QOK5%;}}_1x}Q2YK%NmWuZJZli94OWp6g@9n?)Il%je3xTsYZ^HwPhjl|Tys#qd-H}Z>n3w>ub4e;diMg3#ies|vx>_LWtUP!3+0`ey&vNW6Sri*rQWP)8{awlA!*A8!@xK#(cl-|jEqU`d%Xh`^Io}h$ zSAS2FVqj)qWSY*v$TWj#83Pm3a`q?&Mg|rJ1_mYu zh)R%Z2#bqRh4BOfpNpGM2t!zMWl;`8T1sk}5ko;rYFQ#fOL|t35yOo1qSS1LCF!ME zMGTvA5=-(J_T`oqCo`POFRC>I5RJU;af>!ZVn?$Nk(EOqX5`OhX4N= zm>F0ZxEXlhdVP;@QlVe5F z#mvBhlgGxu0heQhh7|)C@G1lax-bMJCY2R4geB#ar!mBU$)u#5@?wUJplfdbVk?9(g zz6BM(2cemHz%&!H33DC;8v_di6Z19}B?eHaGO|QL{Kd)wR>R1u!oUb&fn5)A87O56 zFo-b7FsOjjs1C#)unLfW(J>1HD3&0pn2AA%L5x9;L5+c#fq_8}ryiIY5WS!@2MK$G z3sB9;!s`YW1}+9M1{DSauuEX3foMhs5D77x1&Pgy#0I&68Eiht=OBAQJ^;C24X67- zdO)f{c^E{4bisTL@;4|naA76}HU=REh|54~Kw-hmzyx;z`C&g0I+1;@$6yEc1;|&} zF{lgy`5c5n{)X^jJhp#FKrzT10!o$tu0Yw4G$qC$22N893~FFIm>8HCcQKx1U}QYU zcoC$M0Td^YlnAn$iGhznh(U@$ih+?q39JJoii%-6K99aO@CG=V|{!UvUT zAp1Zi9Jp2jNi#4ph%iVnC@^S%%UlDn7|cvCjb;x>H^@v-iUs8nQ0gZ>XEb;64Gl50)1pHh~-jDvd#I0m-s~;}XQ;fU`K^EOt1H3(n$( zvv{B^NL+)`o(#x%uqeoU2xbANGf)UY)Dc$>f^xM0xV!*`4#-##4JjW$=5jIoVe~<% z+Y`Wbdk(m6uL9TY9pJisQaY$^p9iknSAgsGE#SI+AGmHm0j}FGf$DaKJD|Fq;TbsQ zKs7T1oCUHQsiHnD81DM6aJ&F4gj}%`O0~4bP zD2y0I7-bl^z;!$*4zc3@H~yddf8+m=|BwFP_v z@W%gLV37?B4FAvlKk^^M2kC_1Hy}B%%r2*ZkPk6LB0pM^BfK{J-}vyQUDh?9vML41o9)oSa7U>V&(=YjKT7d6a{e? zjLpQN1Tu?7h((G)m_Zt$pETD0Z~q^GLm3?6pm<|o0EO=VH{f*f2xb~I&Fq5lk!gr7 z28RFNkVOfx2cs@f%0jpsn+yNnK*(cfVbg_OC9)`77dRe}WzgB6^Z^TNxGqpy0by`_ zf!v5aE}(j$u?X@lNG-Arpws}$uOPAiZ$K#noB~1V6qKhysRb#P!D$3txRJMT1u}7ft2&M;Q z9<*$GgQn{nI20kec7gOj!VIJn-Cj_M1ri6@4WV(7pi-ED;r|g($;iO)e+~l!NCd&N|NH-MkWFCUgZ%j%rU5GQ z4HVMQrypFr54Fb88$xO{`CfQHlE|8JnWL1i2$u0U$QW`OkmfAjwdL5WdIi}DG6!S|gocp+ zeS#$Bl5)oc)Oj6 zp#_vbz#|>3pphi7foKG{b@S%`5s)d6Fa^6Al(x=6{0PwiPJJ6dY;e2i2-por{)1X3 zyFjG|Buz3fG9c`SxCtb}1ntRy`ZTbv)CPy||3{#b2V5h8YF2Pf z1l9*(L3#@}z}F7Y{07nobvY=sVeJ-h-3~DU92<}l5!~m1ih@MGf&2z` z%@ME*L2ZD$Ak#qQ2{;GefTyJ!AhnP(0oi^3K`Owh5ULMS+ktvGkHBVvTnP~a`5)BR z0fj3hb%4`6$Xu{qgel+>87u}4ACM0~B{?WGz;;9Jgo=Y)4Z)zW0o7JuldzZz76+RP zs^dT^p?U28F6<>RV*x0AF(xo(Fz_&dMwdaFapM1Pz%2++E8@}rkC43g9~{R2AA#y{ zQ0@c8Ke)dA4Q_LQT5umh{`&vs|8q#)400F93XuE3`TQf;&)~2Ei-X(@tI42w79@_0 zA$1x!%)ssdr5BJqsD48>4Qw-11;{?I8ZpkAAw8-rHB8Z+yQTMg3JWRG$_p- z0i_yZf!i%FV+ zi;0^-l5sNwJL7K#ArNL-$iT{UkU@g6ib0(5F%)lO;9`8jAk6rZL4o<%24hyn*9=@t{0tln{~350bwI->3|x#`7`T|~7&w^3 z88|?0W_--R!x+cF!#JCPi}3*i7voF@4kkVZE~f1atc;orqKujhOpN{vf{Z&D#2Ed- zYyn1127WLWXW(UIVqj-!oUnMk3ocK34;t1F9SQH7=tjg z5`zHaZw3L7T1H<6R>oEaW5$O6TNuAE2s3_R5Coae6vd#*RLda1)WIOl|o$x*v7!k@Qi_p;SB>T!x08n=yicg)xdjkTHpYmC=uZi_w*Vmr;~~ zgR%4fUq*%he;Gv?_!xs2B$(V8H1Xn#4E#(N8TgrX|Nmq*{QraL_y6xqfB*kry72!y z)5ZVaLH>neF(?fV17;=$MrNk}KbhP9e`22g{|ED$|38>l{Qtou!N89XGyDJl!W{ko zGxM$gpO`=W|G~oa{|5`}{~yfL{=a6P`Tq^09D@p@9D@y`-v9564*$P1>iqxCX!QR( zqs9MkjQaoIGv5CHiShUUUrd?*KQZzsF$m+s%trrzFyHzAgZb|N@65ITKQV1((8PzC zUH<=I_Wu8!`Q86dEDZmDFrWMXgZb9~AIzQqUotoRe*%jOSXjcsicyY%nNg0x5$1Nd zn_%`4h)3oM27cyB27cxn|GzQc|Nnz|{r|7bTmJuGF8TkFx$OU2m_4{?=AZw+urU4q z%+mP(6U*%XKUh}(|G~2U{|^?2|F2mb{=dQJXOJ8V*Z)5(0{_3W?E3$SQf5ZHbLPOIaC>??@$R7;97%Ulm7#bPBFsxww!jQ%!#*l{9?LQe< zm=zc#m~9ytnQIw@nTx^s-;MDLLk{B?h6F~r{~s9T{$F5}`+tK`?*9Wux&M#A*qwo& z(Vc;taX$kilO%&6;}-^h#xD%1j9(aH;b9L;8v+bQOdt&7&tl+bn#I7+Y{0GlK?GE&~sf z34qwA#Hbw^qXGRAG9!3WSS%UiG7&zeibr?h$)fxC1T^R%!y%@L|RT%i0b}^VU z&1R4RV;u$_#xD#!%v=ltOqUpVnAb3HGk;{@XVzulWlm+VVPasAW_rq?#B_;4i7A;u zkky8Po$)0D4^t$A08=)DBGV-XekL6TEv94!Eyhm_!c3M7icCBVB20Y@B1}&il$kCw zs4^{LkYe7&AjtTOL51leg96h(2012P24$w%3`)!i41$dR{=Z^LXJBXQWDsIzV31;b z#lQ)|%o+@QOsNdq%)SgfjQbh*nYkE*nf5YBF&8ihGD|Q>G5u!{Ve(~=V!Fv7#e9T; zpXoS*0Mlm%DW=&Bf*{O%fI);MhJl|&f`N zrd|edrZ@&=#zF>3CUFKvCKd)2W-bN==4J*l#<>jq%sdQo%uEb&%vTuXnBFnSF)K0% zFkN7fW0qkMV0yqH%Jh$c6C}=bi$MZJGcz%WF#TWo<`V&G!_!obDI z#J~k^vwUWdWaehzVzFWn1cfz=69YdeeKD?v&`bj0a1mf}We{LyW)NU*VUT0I!Jq)9 zzcA>6>CX(h%nA%v%nA&0P;3Pf2jdqEa?CFnL_lmtgnE7E-wbkK{FOltEdG_j97Kc5 zfj$N~<~{}uW)R)NAjeY8AP3UV6u_X%_=v#{88b~{U;>33i!Or#2s67d*fAerU}5%V zumR&1h+f9e4EGp6Gw6ZL0b$0^40qss4~RcN;><8MjE3rmxdo(#NsU1QN{fNSnba7> zm|rkxFu!0>V}8M)3TA`c-p8Q9V#XlHV#Xi>^&2F-VBr9wA28@JKLGnzjwyse7vyiI z5C%P_AO;;c4rb5=i-j=gF?TU&FwbMKVV=ie2UZVqpBIB3NFT^egfNpggCvs(g9_t2 z1}P?Q1|2Y-#vlh;c?8B13?@tx3?_`*84N%)2!q&+Ul@Fu%^4&?nCT<~AE?}fVNjfb z@dO5QP?^o>%izSM`u`ss*D%O1hccKj{$;RZvS$!t(q|B2{L7%saDu^u;TMA>xNe%l zz{O<3APmKzyer9gf`Nfqhk=2mk%56Ji$R&Wk3pH`8iO)Z8-p@aHx!#PC^K0wD1+)I zrcwq6u$#Uy$bsn^1`8%AX8g_|$dtywz^KXK0uy7ZVK9ec6Q&vl7>4UXQfJIm!{7+U zCQ!3s^nC_prq5_%FgXw%fkm!{!HKDc!Hx++J3!eW+Lft>0ZcQuFvu`BGsrN`0;>br z4-yCIfzcp$nnK+OqCs-_ur1Ucm>QUWk!g@UAU=XN0hgoT_+T<&P(Z=P42n!942n!O z41z>pko|DX%*9~G%*9~Bbb`SSgc&a}*nu!pFoPYF4}%@kF$O!PLkxCceh7mdQvw4k zm=6gzm>=LYs{n%?2s4B9nJ~yP{$enIrX^t}GX`NMD+WC#HwJm87YuS>{D;Ao=?}P` z)MEI>;Kg{I!GWoi!IG(l!3c^Sm<rZGvqLfGbn>FQy_x^lK}%elQDx381G^52IV_u9tInx3I-#_TMUehw-{;} zZ!s`1-eRa`TF25%*+QC1G!m* zS%CqJ8P|cu6hSl-D}w^l6b1#59ZYu^6qw5x6qpi_C@{WaP-JFeP++>s zpuil%pujYTL4oNbRQ)XmP}mzWMKIvTAT?K@>OuaHXHa08%y64&GJ^u+djtmgi)ku@ z0+RxR0#gEm0#hu50+S+x2D1u-0+Ty~0<#Q*0`oft1!hYI1(u}@3M>^6nrStI0<#8# z0!tNx0<$dGUy00+IGD<;z~IZQz>vzUz_5#1fdM3L%B;XJjah-ghgpFknOT8hCo>a6 zHZv20Co>a67090${CWljMqdUsCPoG|rb`gaSj=F-Sjk|)IElfCsfj^>sh>fCIh8?y zxsw3?!Jxzt}$paA7)Sh zxu1oPL4mP_L5uk*g95WKg976p1_fqK1_kDC3<}KA3<}If3<}I=7!;UaFt9TzF~~6s zFsL&NF{m>=Vo+z2V^C*`WKd_?!=TRipFy2DmO-5kpQe}{3vSN?}(ahltip>2C%FIg{l)>~y zh+d}83`!uqOurbEnSL@TGg<%t&$N_5h3PkgGSd$RWv1^8%1oaalo>(oa}5S%CP@Zm zCPxNkrY#K0%tA={6lAX?gEF%egEF%@g9NiVgEH7Xo?yN*vm=8tvjKxLvps_{^K1rX zrWfG8h%(b#u$z^bB^Z>MwlgR*b1*0~GcYKF`5?OwGbl41VNeFO{h9VMC^K6zC^PFr z%@$=)W;)EE$h4k8g=q(aG84#8LJUgG!VJnxCn4d$EXbe&@)vU^gEBJ@gDBXaAisjl z2H{8sIi^SkIYt2n4Q2rbWkz3yK!z>`Wkyh+V?Kj2V+R;3G0kC6W}3sG!q~w8VyiIS zVNhmLV6b3PU{C_JUzk9C`oW;gtiT}3*v6m?#$iEP-b?4;$#M8=41wCaQy6GP-X_T z_dw$$S&;O_yoo`XnVCU}IT1=%Feot}XHaGa(V(=F$)EzZ{}h8V^E3t}#yAE?CSL{- zM$mZsZU)G@SkMwge})RCg$xx;n;0sXE-`R2&1Mi_N@fsX`p&=$X3qwVA2SFrb1^70 zHZce={Q>u{IhogkX#u7#1}l&pvkrp10 zo56zVJcBG#EQ2bO8iNIs8iNdz8iN|+4h9v*_YC?>u?(C{Y79n-!pJBzGu*3Qe#kMie->uQe)6&+`*v1q{g7cxPyU%NsWPnX$pfnNFCE@22Q5a z49ZM;3?hsx7&Mq-!SZrUQ^5MQnNBlEGEM#epUL(AJEmj?PNpgUe={Zj|H>5i{}u*Eb*9<>|1vfH|H|z6{~goB|8JOo`jVlZNQ%fQ5x%)r2OiNS_x;{Rt% zAq=ieOBh0!E-?r*CI5fSti#~V^!)!Tu(>7-oQ!|}-(&p1pv1(%Aj7!e{~yLa1|G&= z3^L%pIUfTv<7);!#uxv;GJgGkmhl+_592xp2F8C3Y)lFNUormr|Ale+{~t^|3|x#W z8MqmLFi0|GG4L{;{=b-MD}y-GRt9cRzlBkPfs@IbL5N9=ft9I&frm-#{}-k;4BSk! z|L^oPySzKn*IMdQ!;}vGuQtYOqUp>8C(B9U|RbBHAo$E<^RV_m;OIv z>iYi#BnMWL{Qo(K&2;Pk6Q*1S8>UMPu1s$j7(iw+M={7VJ^lX#Y_8w`e@qhpw=k(N za51U<|IfJY|4YV445DB@FL+#tnaSk;FUEiWuQ2)kKgPtxz{>dO|8FLV|Nogv|6ga4 z`2U3Q+W(IrKH~)j9wrF}W~OEa4#r3S`$@&9W~p8xMME&D&2sr~;QrsDsbn3n!O z$MlGSfsyzBMW$u{uQ4tAKY>}`|5>K)|Hqh0|6gKy%fQHV;{P3{n*X<$-Z028760GH zRLtPYw2Z-$>B#>dOsD=&W4gy6z;yio4yI-Q|1*KcQ+WT+W0GQUW-|T%h>89GQ^q_0 zKQoCjFf)n$|HNqh|0@&k|L07m|K~F${J+7(#Gu3Y`2RyDo&TSiCNfAeS^YoCB*(zb zl=1%$lQe@F6VLz8Ot%>1nH>IiF->9+V!HYN7n3oAFQYYs1CuU;FO%5+3rueqY?u@n z#F>sW7=z0Z#{W+kzc5HJeqnF~&pCYo&qsmgpTroyFvvi~K(rLo3I>0s6$~9rD;P?c zRxso(n?arNH$yDrZw7hB-wd*hzZs+$e={gR#kClJ zGbl0sW>AKT+cExTkcP6w7=JTJBIywUi8D1ZR5DaDL@}f@6fmSSOkyZtsAQPLzyJXG C>tX2t literal 0 HcmV?d00001 diff --git a/guis/widgets.qrc b/guis/widgets.qrc index cfd6bf9..8e50a5e 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -3,6 +3,7 @@ images/edit-copy.svg images/edit-copy-light.svg images/internet.svg + images/Flowee-Symbols.otf Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml Flowee/Button.qml diff --git a/src/main.cpp b/src/main.cpp index 141a831..881b111 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,7 @@ #include // for ECC_Start() #include +#include #include #include #include @@ -163,6 +164,13 @@ int main(int argc, char *argv[]) }); FloweePay::instance()->startP2PInit(); + // We ship our own font to not have to depend on the host system's installed fonts + // for 'special' characters like arrows and stars. + int fontId = QFontDatabase::addApplicationFont(":/Flowee-Symbols.otf"); + if (fontId == -1) { + logCritical() << "Loading of our symbol font failed!"; + } + // Clean shutdown on SIGTERM struct sigaction sa; sa.sa_handler = HandleSigTerm; -- 2.54.0 From eb23c947fefb498d8b84a1ce4ca55d30852ec71c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 May 2023 15:58:36 +0200 Subject: [PATCH 0522/1428] Be nicer to touch-screens and increase hit area. The actual touch area was exactly the checkbox, which is really not that big an area for a finger to touch. This assumes that the layout has 10 pixels between components and thus we eat 5 above and 5 below this component to increase the touch-area to the max we can. --- guis/Flowee/CheckBox.qml | 7 +++++++ guis/Flowee/CheckBoxLabel.qml | 9 +++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index f58806a..43d59fd 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -67,6 +67,13 @@ T.CheckBox { } } + MouseArea { + anchors.fill: parent + anchors.margins: -5 // make it more finger friendly and assume a 10 pixel gap between elements. + onClicked: control.toggle() + cursorShape: Qt.PointingHandCursor + } + Label { id: title text: control.text diff --git a/guis/Flowee/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml index 411a742..ddad494 100644 --- a/guis/Flowee/CheckBoxLabel.qml +++ b/guis/Flowee/CheckBoxLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,11 +38,8 @@ Label { MouseArea { id: mouseArea anchors.fill: parent - onClicked: { - if (root.buddy != null) { - root.buddy.checked = !root.buddy.checked; - } - } + anchors.margins: -5 + onClicked: if (root.buddy != null) root.buddy.toggle(); hoverEnabled: root.toolTipText !== "" cursorShape: Qt.PointingHandCursor } -- 2.54.0 From 914e4a5d55e94e3637c2ffecadee42050053f400 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 May 2023 16:29:36 +0200 Subject: [PATCH 0523/1428] Also allow InstaPay config from send-tab --- guis/mobile.qrc | 1 + guis/mobile/AccountPageListItem.qml | 20 ++------------ guis/mobile/InstaPayConfigButton.qml | 40 ++++++++++++++++++++++++++++ guis/mobile/SendTransactionsTab.qml | 3 +++ 4 files changed, 46 insertions(+), 18 deletions(-) create mode 100644 guis/mobile/InstaPayConfigButton.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 8e293e5..a1fad5a 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -74,5 +74,6 @@ mobile/EditableLabel.qml mobile/SelectDefaultAccountPage.qml mobile/InstaPayConfigPage.qml + mobile/InstaPayConfigButton.qml diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 7923e97..8249b0a 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -67,25 +67,9 @@ QQC2.Control { } } - TextButton { + InstaPayConfigButton { visible: !root.account.isArchived - text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") - subtext: { - if (!root.account.allowsInstaPay) - return qsTr("Fast payments for low amounts") - - var currencies = root.account.instaPayLimitCurrencies() - if (currencies.length === 1) { - return ": => " + root.account.fiatInstaPayLimit(currencies[0]); - } - return ""; - } - - showPageIcon: true - onClicked: { - var newPage = thePile.push("./InstaPayConfigPage.qml") - newPage.account = root.account; - } + account: root.account } TextButton { diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml new file mode 100644 index 0000000..bb3985d --- /dev/null +++ b/guis/mobile/InstaPayConfigButton.qml @@ -0,0 +1,40 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +TextButton { + id: root + property QtObject account: portfolio.current + + text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") + subtext: { + if (!root.account.allowInstaPay) + return qsTr("Fast payments for low amounts") + + let limit = root.account.fiatInstaPayLimit(Fiat.currencyName); + if (limit === 0) + return qsTr("Not configured"); + return qsTr("Limit set to: %1").arg(Fiat.formattedPrice(limit)); + } + + showPageIcon: true + onClicked: { + var newPage = thePile.push("./InstaPayConfigPage.qml") + newPage.account = root.account; + } +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 9adf278..d04a9d6 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -27,6 +27,9 @@ FocusScope { width: parent.width - 20 x: 10 + InstaPayConfigButton { + } + TextButton { text: qsTr("Scan a QR code") showPageIcon: true -- 2.54.0 From 5c2581c5920cea7d0fe13b63808cfaa6a58808ad Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 May 2023 20:11:42 +0200 Subject: [PATCH 0524/1428] Add PaymentBackend helper class This makes the PriceInputWidget free to invoke. --- guis/mobile/PriceInputWidget.qml | 10 ++++-- src/CMakeLists.txt | 1 + src/PaymentBackend.cpp | 59 ++++++++++++++++++++++++++++++++ src/PaymentBackend.h | 47 +++++++++++++++++++++++++ src/main.cpp | 2 ++ 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/PaymentBackend.cpp create mode 100644 src/PaymentBackend.h diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index 90edeea..225df95 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -18,7 +18,7 @@ import QtQuick import QtQuick.Controls as QQC2 import "../Flowee" as Flowee - +import Flowee.org.pay FocusScope { id: root @@ -28,8 +28,12 @@ FocusScope { property bool fiatFollowsSats: true // made available for the NumericKeyboardWidget property var editor: fiatFollowsSats ? priceBch.money : priceFiat.money; - // Payment object or the PaymentDetailOutput object. - required property QtObject paymentBackend; + /** + * Payment Backend processes the fiat and satoshi based pair of payments. + * The default is a simple backend, notice that the Payment QML type + * actually provides its own backend(s), as each output is one. + */ + property QtObject paymentBackend : PaymentBackend {} onFiatFollowsSatsChanged: { if (!activeFocus) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8736aa9..d0725b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ set (PAY_SOURCES NewWalletConfig.cpp NotificationManager.cpp Payment.cpp + PaymentBackend.cpp PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp diff --git a/src/PaymentBackend.cpp b/src/PaymentBackend.cpp new file mode 100644 index 0000000..105dfc0 --- /dev/null +++ b/src/PaymentBackend.cpp @@ -0,0 +1,59 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "PaymentBackend.h" +#include "FloweePay.h" +#include "PriceDataProvider.h" + +PaymentBackend::PaymentBackend(QObject *parent) + : QObject{parent} +{ + +} + +double PaymentBackend::paymentAmount() const +{ + return m_paymentAmount; +} + +void PaymentBackend::setPaymentAmount(double amount) +{ + if (qFuzzyCompare(m_paymentAmount, amount)) + return; + if (amount < 0) + return; + m_paymentAmount = amount; + auto priceInFiat = FloweePay::instance()->prices()->price() * amount / 1E8; + m_paymentAmountFiat = std::round(priceInFiat); + emit amountChanged(); +} + +int PaymentBackend::paymentAmountFiat() const +{ + return m_paymentAmountFiat; +} + +void PaymentBackend::setPaymentAmountFiat(int amount) +{ + if (m_paymentAmountFiat == amount) + return; + if (amount < 0) + return; + m_paymentAmountFiat = amount; + m_paymentAmount = amount * 1E8 / FloweePay::instance()->prices()->price(); + emit amountChanged(); +} diff --git a/src/PaymentBackend.h b/src/PaymentBackend.h new file mode 100644 index 0000000..34d035e --- /dev/null +++ b/src/PaymentBackend.h @@ -0,0 +1,47 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef PAYMENTBACKEND_H +#define PAYMENTBACKEND_H + +#include + +class PaymentBackend : public QObject +{ + Q_OBJECT + /// The payment-wide amount of funds. + Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) + /// The payment-wide amount of funds in globally set fiat. + Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) +public: + explicit PaymentBackend(QObject *parent = nullptr); + + double paymentAmount() const; + void setPaymentAmount(double newPaymentAmount); + + int paymentAmountFiat() const; + void setPaymentAmountFiat(int newPaymentAmountFiat); + +signals: + void amountChanged(); + +private: + double m_paymentAmount = 0; + int m_paymentAmountFiat = 0; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 881b111..3db5a45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ #include "Payment.h" #include "TransactionInfo.h" #include "PaymentRequest.h" +#include "PaymentBackend.h" #include "QRCreator.h" #include "MenuModel.h" #ifdef NETWORK_LOGGER @@ -95,6 +96,7 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); + qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentBackend"); auto cld = createCLD(qapp); auto *logger = Log::Manager::instance(); -- 2.54.0 From 0eca2b680cca5ac6c00579896e4a8f627eeb1271 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 May 2023 20:55:54 +0200 Subject: [PATCH 0525/1428] have a default width --- guis/mobile/PageTitledBox.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index 11df606..42dd71b 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -34,6 +34,7 @@ Item { } Layout.fillWidth: true height: implicitHeight + implicitWidth: 50 // have SOME non-zero default. visible: implicitHeight > 0 Rectangle { -- 2.54.0 From 57daf604011bbefe14102199670dc0a78e0f40cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 May 2023 20:56:59 +0200 Subject: [PATCH 0526/1428] Add addressShort getter This returns the address, but without the chain prefix. --- src/PaymentRequest.cpp | 10 ++++++++++ src/PaymentRequest.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 7eb683c..3d9b852 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -92,6 +92,15 @@ QString PaymentRequest::address() const return QString::fromStdString(CashAddress::encodeCashAddr(FloweePay::instance()->chainPrefix(), c)); } +QString PaymentRequest::addressShort() const +{ + auto a = address(); + auto i = a.indexOf(':'); + if (i > 0) + return a.mid(i + 1); + return a; +} + QObject *PaymentRequest::account() const { return m_account; @@ -165,6 +174,7 @@ void PaymentRequest::start() assert(m_view); m_view->setPrivKeyIndex(m_privKeyId); emit qrCodeStringChanged(); + emit addressChanged(); #if 0 // by enabling this you can simulate the payment request being fulfilled diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 28758fb..3d3325b 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -33,6 +33,7 @@ class PaymentRequest : public QObject { Q_OBJECT Q_PROPERTY(QString address READ address NOTIFY addressChanged) + Q_PROPERTY(QString addressShort READ addressShort NOTIFY addressChanged) Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged) Q_PROPERTY(QString qr READ qrCodeString NOTIFY qrCodeStringChanged) Q_PROPERTY(double amount READ amount WRITE setAmount NOTIFY amountChanged) @@ -90,6 +91,7 @@ public: void setAccount(QObject *account); QString address() const; + QString addressShort() const; void setAddress(const QString &newAddress); PaymentRequest::FailReason failReason() const; -- 2.54.0 From 0ef5d2b0fc2b93a9a784f4fee86fa464d6ec15b4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 24 May 2023 11:35:25 +0200 Subject: [PATCH 0527/1428] Re-think the interaction of the ReceiveTab This solves the UX issue that the touch-screen keyboard would overlap on our main content and that we could actally not input a price properly on mobile at all. --- guis/Flowee/QRWidget.qml | 3 +- guis/mobile/ReceiveTab.qml | 190 ++++++++++++++++++++------ guis/mobile/images/edit-pen-light.svg | 2 +- guis/mobile/images/edit-pen.svg | 2 +- src/PaymentRequest.cpp | 1 + 5 files changed, 156 insertions(+), 42 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index e2306f8..4c99323 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -25,7 +25,7 @@ Item { property string qrText: "" implicitWidth: qrImage.width - implicitHeight: qrImage.width + (addressLine.visible ? addressLine.height : 0) + implicitHeight: qrImage.height + (addressLine.visible ? addressLine.height : 0) function handleOnClicked() { Pay.copyToClipboard(qrText); @@ -50,6 +50,7 @@ Item { anchors.fill: parent onClicked: root.handleOnClicked() } + Behavior on width { NumberAnimation { } } } Rectangle { id: addressLine diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 00a7a2b..51bea36 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,6 @@ import QtQuick import QtQuick.Shapes import QtQuick.Layouts -import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay @@ -29,9 +28,12 @@ FocusScope { focus: true property QtObject account: portfolio.current + property bool qrViewActive: true; + property bool editViewActive: false PaymentRequest { id: request + amount: priceInput.paymentBackend.paymentAmount } onAccountChanged: { @@ -44,24 +46,79 @@ FocusScope { } onActiveFocusChanged: { + // starting reserves an adderess, don't do that until + // this tab is actually viewed. if (activeFocus) request.start(); } + Column { + id: verticalTabs + y: 50 + x: -10 + Repeater { + model: ["qr-code", "edit-pen"] + + delegate: MouseArea { + property bool active: index === 0 + onActiveChanged: { + let a = active; + if (a) // disable the other one + verticalTabs.children[(index + 1) % 2].active = false + // update page-scope state variables + if (index == 1) + a = !a; + qrViewActive = a; + editViewActive = !a; + } + width: 50 + height: 50 + onClicked: active = true; + + Rectangle { + anchors.fill: parent + color: palette.window + } + Rectangle { + x: 46 + y: 5 + width: 4 + height: 40 + color: palette.highlight + visible: active + } + Rectangle { + anchors.fill: parent + color: palette.highlight + visible: active + opacity: 0.15 + } + Image { + anchors.fill: parent + anchors.margins: 10 + source: "qrc:/" + modelData + (Pay.useDarkSkin ? "-light" : "") + ".svg" + smooth: true + } + } + } + } + Flowee.Label { id: instructions + y: 35 - height anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 text: qsTr("Share this QR to receive") - opacity: 0.5 + opacity: editViewActive ? 0 : 0.5 + Behavior on opacity { OpacityAnimator { } } } Flowee.QRWidget { id: qr - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: instructions.bottom - anchors.topMargin: 20 width: parent.width + y: qrViewActive ? 40 : editBox.height + x: priceInput.activeFocus ? 0 - width : 0 + qrSize: qrViewActive ? 256 : 160 + textVisible: false qrText: request.qr Flowee.Label { @@ -81,52 +138,107 @@ FocusScope { wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 18 } + Behavior on y { NumberAnimation { } } + Behavior on x { NumberAnimation { } } } - // entry-fields - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: qr.bottom - anchors.topMargin: 30 - Flowee.Label { - text: qsTr("Description") + ":" - } - Flowee.TextField { - id: description - Layout.fillWidth: true - enabled: request.state === PaymentRequest.Unpaid - onTextChanged: request.message = text - } + // feedback-fields + Column { + width: parent.width + spacing: 10 + opacity: qrViewActive ? 1 : 0 + y: instructions.height + qr.height + 30 - Flowee.Label { - id: payAmount - text: qsTr("Amount") + ":" - } - RowLayout { - spacing: 10 - Flowee.BitcoinValueField { - id: bitcoinValueField - enabled: request.state === PaymentRequest.Unpaid - onValueChanged: request.amount = value - } + PageTitledBox { + width: parent.width + visible: request.message !== "" + title: qsTr("Description") Flowee.Label { - Layout.alignment: Qt.AlignBaseline - anchors.baselineOffset: bitcoinValueField.baselineOffset - text: Fiat.formattedPrice(bitcoinValueField.value, Fiat.price) + text: request.message + } + } + PageTitledBox { + width: parent.width + visible: request.amount > 0 + title: qsTr("Amount", "requested amount of coin") + Flowee.BitcoinAmountLabel { + colorize: false + value: request.amount + } + } + PageTitledBox { + width: parent.width + title: qsTr("Address", "Bitcoin Cash address") + Flowee.LabelWithClipboard { + width: parent.width + text: request.addressShort + font.pixelSize: instructions.font.pixelSize * 0.9 } } Flowee.Button { - Layout.alignment: Qt.AlignRight + anchors.right: parent.right text: qsTr("Clear") onClicked: { request.clear(); request.account = portfolio.current; description.text = ""; - bitcoinValueField.value = 0; + priceInput.paymentBackend.paymentAmount = 0; request.start(); } } + Behavior on opacity { OpacityAnimator { } } + } + + // edit fields + PageTitledBox { + id: editBox + width: parent.width - 50 + x: 50 + title: qsTr("Description") + opacity: editViewActive ? 1 : 0 + enabled: editViewActive + + // little state-machine to switch between the two + // input options + property bool editingTextField: true + + onEnabledChanged: { + // due to lack of a good statemachine, a bit hackish to + // switch states properly. + if (enabled) { + if (editingTextField) + description.forceActiveFocus() + else + priceInput.forceActiveFocus() + } + else { + priceInput.focus = false; + description.focus = false; + } + } + + Flowee.TextField { + id: description + width: parent.width + onTextChanged: if (!inputMethodComposing) request.message = text + onActiveFocusOnPressChanged: editBox.editingTextField = true + } + Behavior on opacity { OpacityAnimator { } } + + PriceInputWidget { + id: priceInput + fiatFollowsSats: false + width: parent.width + onActiveFocusChanged: if (activeFocus) editBox.editingTextField = false + } + } + + NumericKeyboardWidget { + id: numericKeyboard + width: parent.width + x: priceInput.activeFocus ? 0 : width + 10 + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 } // the "payment received" screen. @@ -282,7 +394,7 @@ FocusScope { onClicked: { request.clear(); description.text = ""; - bitcoinValueField.value = 0; + priceInput.paymentBackend.paymentAmount = 0; request.account = portfolio.current; request.start(); } diff --git a/guis/mobile/images/edit-pen-light.svg b/guis/mobile/images/edit-pen-light.svg index a4d86e1..f5be2d9 100644 --- a/guis/mobile/images/edit-pen-light.svg +++ b/guis/mobile/images/edit-pen-light.svg @@ -1,5 +1,5 @@ - + diff --git a/guis/mobile/images/edit-pen.svg b/guis/mobile/images/edit-pen.svg index 3b958bd..bd20e2f 100644 --- a/guis/mobile/images/edit-pen.svg +++ b/guis/mobile/images/edit-pen.svg @@ -1,5 +1,5 @@ - + diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 3d9b852..7009316 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -264,6 +264,7 @@ QString PaymentRequest::qrCodeString() const void PaymentRequest::clear() { m_message.clear(); + emit messageChanged(); m_address = KeyId(); m_amountRequested = 0; m_amountSeen = 0; -- 2.54.0 From fc3b31b8640f529f3e1e4daeea23ec9bdc7249ac Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 May 2023 08:31:36 +0200 Subject: [PATCH 0528/1428] minor fixes --- guis/Flowee/CheckBox.qml | 6 +++++- src/PaymentRequest.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 43d59fd..78f4e92 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -70,7 +70,11 @@ T.CheckBox { MouseArea { anchors.fill: parent anchors.margins: -5 // make it more finger friendly and assume a 10 pixel gap between elements. - onClicked: control.toggle() + onClicked: { + control.toggle() + control.clicked(); + control.toggled(); + } cursorShape: Qt.PointingHandCursor } diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 7009316..41f15d6 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -267,6 +267,7 @@ void PaymentRequest::clear() emit messageChanged(); m_address = KeyId(); m_amountRequested = 0; + emit amountChanged(); m_amountSeen = 0; setPaymentState(Unpaid); delete m_view; -- 2.54.0 From 311a788346173c1cdef8831c6f7d4214f78f2595 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 May 2023 08:35:03 +0200 Subject: [PATCH 0529/1428] tie a signal to the allowInstaPay property this may fire on no change, making it imperfect. It is better in that it actually allows propery changes to have a visual effect. --- src/AccountInfo.cpp | 1 + src/AccountInfo.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index fb45467..b1a24f9 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -241,6 +241,7 @@ void AccountInfo::setAllowInstaPay(bool newAllowInstaPay) { assert(m_config.isValid()); m_config.setAllowInstaPay(newAllowInstaPay); + emit instaPayAllowedChanged(); } int AccountInfo::fiatInstaPayLimit(const QString ¤cyCode) const diff --git a/src/AccountInfo.h b/src/AccountInfo.h index bc94692..ec053d9 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -65,7 +65,7 @@ class AccountInfo : public QObject * If true, please count the balance(s) of this wallet in the app-wide balance */ Q_PROPERTY(bool countBalance READ countBalance WRITE setCountBalance NOTIFY neverEmitted) - Q_PROPERTY(bool allowInstaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY neverEmitted) + Q_PROPERTY(bool allowInstaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY instaPayAllowedChanged) Q_PROPERTY(bool isPrivate READ isPrivate WRITE setIsPrivate NOTIFY neverEmitted) public: AccountInfo(Wallet *wallet, QObject *parent = nullptr); @@ -172,6 +172,7 @@ signals: void hasFreshTransactionsChanged(); void encryptionChanged(); void modelsChanged(); + void instaPayAllowedChanged(); void neverEmitted(); // to silence the lambs^Warnings // for the benefit of the portfolio data provider -- 2.54.0 From 8a9647551d66bf6a9c0b7f604b177791460af049 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 May 2023 12:19:39 +0200 Subject: [PATCH 0530/1428] Make BCH price popup show on bigger area This now makes not just the bch balance but also the fiat-balance clickable, all the way to the left border. --- guis/mobile/AccountHistory.qml | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 51d6288..d7c6026 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -98,6 +98,7 @@ ListView { y: 10 Flowee.BitcoinAmountLabel { + id: bchPriceWidget opacity: Pay.hideBalance ? 0.2 : 1 fontPixelSize: 30 anchors.horizontalCenter: parent.horizontalCenter @@ -108,24 +109,29 @@ ListView { } colorize: false showFiat: false - MouseArea { - anchors.fill: parent - onClicked: popupOverlay.open(priceDetails, parent) - } - Component { - id: priceDetails - PriceDetails { } - } } Flowee.Label { // fiat price opacity: Pay.hideBalance ? 0.2 : 1 - anchors.horizontalCenter: parent.horizontalCenter + width: parent.width + horizontalAlignment: Text.AlignHCenter text: { if (Pay.hideBalance) return Fiat.currencySymbolPrefix + "——" + Fiat.currencySymbolPost Fiat.formattedPrice(portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed, Fiat.price) } + MouseArea { + // make the click area nice and large + width: parent.width + height: parent.height + bchPriceWidget.height + 10 + y: 0 - 5- bchPriceWidget.height + onClicked: popupOverlay.open(priceDetails, parent) + cursorShape: Qt.PointingHandCursor + } + Component { + id: priceDetails + PriceDetails { } + } } AccountSyncState { -- 2.54.0 From 0991da4b367f071ae20f79ae0235bb62bf38a878 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 May 2023 21:57:52 +0200 Subject: [PATCH 0531/1428] Also cloak hd-main derivation addresses Lets face it, listing loads of addresses is not readable. People can't really get meaning out of that, its just 'data' after one or two. Showing the hd-path-id (main or change) and an integer is going to be much more useful for many UX situations, so we provide that information for the UI to display that instead. Naturally, the actual address is still available for copy/pasting etc. --- src/Wallet_support.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 85735ae..3eabab9 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -264,6 +264,9 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) assert(secret.fromHdWallet); in->setCloakedAddress(tr("Change #%1").arg(secret.hdDerivationIndex)); } + else if (secret.fromHdWallet) { + in->setCloakedAddress(tr("Main #%1").arg(secret.hdDerivationIndex)); + } info->m_inputs[pair.first] = in; } #ifndef NDEBUG @@ -298,6 +301,9 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) assert(secret.fromHdWallet); out->setCloakedAddress(tr("Change #%1").arg(secret.hdDerivationIndex)); } + else if (secret.fromHdWallet) { + out->setCloakedAddress(tr("Main #%1").arg(secret.hdDerivationIndex)); + } info->m_outputs[o.first] = out; } } -- 2.54.0 From aea210270a988c5186663b127e1d7d6c2559f937 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 May 2023 21:59:07 +0200 Subject: [PATCH 0532/1428] Remove unneeded code. This method is only called from two methods which do the locking already, making locking it again recursively just wasting time. --- src/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 5f845ed..70bf39e 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -207,7 +207,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co // used private keys close to the index we have created and keep track off. bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &updateBloom) { - QMutexLocker locker(&m_lock); + // mutex locked by caller if (m_hdData.get() == nullptr) return false; -- 2.54.0 From 885bb593b9d43edf506cd3e6155c922480585632 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 May 2023 22:06:41 +0200 Subject: [PATCH 0533/1428] Revisit the 'gap' question for the main derivation path This should fix the problem of a certain established wallet not importing properly. That wallet has shown to jump 15 or 20 addresses over the gap, which made me wonder if that actually is a privacy issue and the conclusion is that, no, if there is no matching transaction on chain, there is no privacy leakage from just sending the bloom filter and thus the gap has been increased for main. --- src/Wallet.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 70bf39e..176eeef 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -55,7 +55,7 @@ Wallet *Wallet::createWallet(const boost::filesystem::path &basedir, uint16_t se // this is a bit larger than others may use because we use a filter for a series // of blocks that can hold a large number of our transactions. constexpr int filter_unusedToInclude = 40; -constexpr int filter_hdUnusedToInclude = 30; +constexpr int filter_hdUnusedToInclude = 150; constexpr int filter_changeUnusedToInclude = 50; Wallet::Wallet() @@ -215,14 +215,24 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda int needChangeAddresses = 0; int needMainAddresses = 0; - const int changeGap = filter_changeUnusedToInclude + (m_walletStoresCashFusions ? 50 : 0); for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { auto privKey = m_walletSecrets.find(i->second.walletSecretId); assert(privKey != m_walletSecrets.end()); // invalid wtx const WalletSecret &ws = privKey->second; + if (ws.fromHdWallet) { + /* + * We previously sent a list of addresses to the peers in the bloom filter, + * we do this sequentually by HD-derivation index. We try to be conservative + * in what we share, which means that as those addresses are used we may need + * to send more addresses by rebuilding the bloom filter. + * + * This code looks at the 'gap', the amount of addresses between what is used + * and what we previously send in our bloom filter. If the gap is getting too small, + * we need to create a new filter in order to avoid missing transactions. + */ if (ws.fromChangeChain) { assert(m_hdData->lastChangeKey >= ws.hdDerivationIndex); if (m_hdData->lastChangeKey - ws.hdDerivationIndex < changeGap) { @@ -236,9 +246,22 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 15; // the gap } else { assert(m_hdData->lastMainKey >= ws.hdDerivationIndex); + /* + * 'main' addresses are the ones the user explicitly requests and we give as a QR + * in order to give to people. This has the effect that we can never stop asking + * servers if any new deposits are made. People may use this address for yearly + * contributions, for instance. So all main addresses always have to be included + * in the bloom filter. + * + * This is additionally not a privacy matter as unused addresses are not possible + * to get out of a bloom filter. Its a MATCHING-filter, afterall. + * So our gap here is much much larger than for change addresses: safe and secure. + * hdUnusedToInclude is set to something like 150 + */ + updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude; // the gap + // additionally, we may need to actually generate more addresses if (m_hdData->lastMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude + 10) needMainAddresses = ExtraAddresses; - updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < 15; // the gap } } } -- 2.54.0 From fbfb9941ea40794cc7e7ce7ab6d9010f4fbd931f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 12:23:04 +0200 Subject: [PATCH 0534/1428] Fix address gap in light of multiple gaps The design of HD wallets always has a gap, a number of addresses past the last used one we optimistically also look at. My code assumed that there would be only one gap, at the end while the number of skipped addresses were near zero. Some other wallet, however, had multiple large gaps which messed up the algorithm. Now we restart counting gap-addresses every time an address is used in the sequence. Fixes #12 --- src/Wallet.cpp | 59 +++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 176eeef..e9e0de9 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -55,8 +55,19 @@ Wallet *Wallet::createWallet(const boost::filesystem::path &basedir, uint16_t se // this is a bit larger than others may use because we use a filter for a series // of blocks that can hold a large number of our transactions. constexpr int filter_unusedToInclude = 40; -constexpr int filter_hdUnusedToInclude = 150; -constexpr int filter_changeUnusedToInclude = 50; +/* + * 'main' addresses are the ones the user explicitly requests and we give as a QR + * in order to give to people. This has the effect that we can never stop asking + * servers if any new deposits are made. People may use this address for yearly + * contributions, for instance. So all main addresses always have to be included + * in the bloom filter. + * + * This is additionally not a privacy matter as unused addresses are not possible + * to get out of a bloom filter. Its a MATCHING-filter, afterall. + * So our gap here is much much larger than for change addresses: safe and secure. + */ +constexpr int filter_hdUnusedToInclude = 200; +constexpr int filter_changeUnusedToInclude = 80; Wallet::Wallet() : m_walletChanged(true), @@ -239,28 +250,16 @@ bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &upda needChangeAddresses = ExtraAddresses + (m_walletStoresCashFusions ? 75 : 0); } - // notice that here we trigger on there only being 15 addresses between what we used and + // notice that here we trigger on there only being 30 addresses between what we used and // what was sent, while what we sent is (as set in filter_changeUnusedToInclude) much // heigher. This is intentional so we update the filter only when we used a larger list // of addresses instead of refreshing the filter after every single new key being used. - updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 15; // the gap + updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 30; // the gap } else { assert(m_hdData->lastMainKey >= ws.hdDerivationIndex); - /* - * 'main' addresses are the ones the user explicitly requests and we give as a QR - * in order to give to people. This has the effect that we can never stop asking - * servers if any new deposits are made. People may use this address for yearly - * contributions, for instance. So all main addresses always have to be included - * in the bloom filter. - * - * This is additionally not a privacy matter as unused addresses are not possible - * to get out of a bloom filter. Its a MATCHING-filter, afterall. - * So our gap here is much much larger than for change addresses: safe and secure. - * hdUnusedToInclude is set to something like 150 - */ - updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude; // the gap + updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < 50; // the gap // additionally, we may need to actually generate more addresses - if (m_hdData->lastMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude + 10) + if (m_hdData->lastMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude + 50) needMainAddresses = ExtraAddresses; } } @@ -962,12 +961,13 @@ void Wallet::rebuildBloom() { int unusedToInclude = filter_unusedToInclude; int hdUnusedToInclude = filter_hdUnusedToInclude; - int changeUnusedToInclude = filter_changeUnusedToInclude; + int changeUnusedToIncludeBase = filter_changeUnusedToInclude; if (m_walletStoresCashFusions) { // a wallet that uses fusions eats through change addresses like // cheap candy, let's avoid the need to upload it too often - changeUnusedToInclude += 50; + changeUnusedToIncludeBase += 50; } + int changeUnusedToInclude = changeUnusedToIncludeBase; // on wallet creation we may not have yet synced the entire header chain, // in that case the secrets are given a height that is in seconds, in order @@ -1026,6 +1026,15 @@ void Wallet::rebuildBloom() * and have no current balance on them (utxo does not refer to them) */ else if (secretsWithBalance.find(i.first) == secretsWithBalance.end()) { + /* + * The amount of unused change addresses we include is meant to be a + * 'gap'. Which means, after the last used addresss we should add that + * amount of addresses. + * This restoring of count is needed for some wallets that repeatedly have + * gaps between change addresses. + */ + changeUnusedToInclude = changeUnusedToIncludeBase; + /* * Ok, the above is true and is good for privacy. We want to avoid uploading stuff * we don't ever expect money on. @@ -1041,8 +1050,14 @@ void Wallet::rebuildBloom() } // HD from main chain. If we never saw this, include a limited set. else { - if (secretsUsed.find(i.first) == secretsUsed.end() && --hdUnusedToInclude < 0) - continue; + if (secretsUsed.find(i.first) == secretsUsed.end()) { + if (--hdUnusedToInclude < 0) + continue; + } + else { + // Didn't reach the gap yet. + hdUnusedToInclude = filter_hdUnusedToInclude; + } m_hdData->lastIncludedMainKey = std::max(m_hdData->lastIncludedMainKey, secret.hdDerivationIndex); } } -- 2.54.0 From e2dacffc33bdde8c3208abec22db396dc93f828f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 May 2023 12:21:52 +0200 Subject: [PATCH 0535/1428] Increase version number --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 61d733a..d07461a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="10" android:versionName="2023.05.2"> diff --git a/src/main.cpp b/src/main.cpp index 3db5a45..85e107f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.05.1"); + qapp.setApplicationVersion("2023.05.2"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 1e05c9f6adf98093f9a23ba21324878866fd407b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 19:19:57 +0200 Subject: [PATCH 0536/1428] Fix overlapping widgets issue --- guis/desktop/AccountDetails.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 91f3771..c72793a 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -251,6 +251,7 @@ Item { Flowee.WalletSecretsView { id: historyView Layout.fillWidth: true + clip: true ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 20 / activityView.height -- 2.54.0 From 2724e2cd670e585e7636985959fd36d6a4031d10 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 19:25:09 +0200 Subject: [PATCH 0537/1428] UX: make checkbox follow price indicator --- guis/desktop/SendTransactionPane.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 5ae90bc..15ccda3 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -351,7 +351,7 @@ Item { Flowee.BitcoinValueField { id: bitcoinValueField value: destinationPane.paymentDetail.paymentAmount - onValueChanged: destinationPane.paymentDetail.paymentAmount = value + onValueEdited: destinationPane.paymentDetail.paymentAmount = value } Flowee.Button { id: sendAll -- 2.54.0 From 3174b0706d3c0389d49e33d50e13bddd8aabaddb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 19:39:15 +0200 Subject: [PATCH 0538/1428] Avoid notifications from 'private' accounts. Send notifications via singleton and do not forward notitications from private accounts when private mode is enabled. --- src/FloweePay.cpp | 12 ++++++++++++ src/FloweePay.h | 1 + src/Wallet.cpp | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index aaba421..b5a4989 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -287,6 +287,18 @@ FloweePay *FloweePay::instance() return &s_app; } +void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ification) +{ + auto *me = FloweePay::instance(); + auto configs = me->m_walletConfigs; + auto i = configs.find(notification.privacySegment); + // don't broadcast notifications of private wallets when private mode is enabled + if (me->privateMode() && (i == configs.end() || i->privateWallet)) + return; + + me->p2pNet()->notifications().notifyNewTransaction(notification); +} + void FloweePay::init() { auto dl = p2pNet(); // this wil load the p2p layer. diff --git a/src/FloweePay.h b/src/FloweePay.h index 16ee11d..284d7bb 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -85,6 +85,7 @@ public: */ static void selectChain(P2PNet::Chain chain); static FloweePay *instance(); + static void sendTransactionNotification(const P2PNet::Notification ¬ification); QList wallets() const; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index e9e0de9..284e78b 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -413,7 +413,7 @@ void Wallet::newTransaction(const Tx &tx) emit utxosChanged(); emit appendedTransactions(firstNewTransaction, 1); - FloweePay::instance()->p2pNet()->notifications().notifyNewTransaction(notification); + FloweePay::instance()->sendTransactionNotification(notification); if (createdNewKeys) rebuildBloom(); } @@ -556,7 +556,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: if (wasUnconfirmed) emit transactionConfirmed(walletTransactionId); if (notification.blockHeight > 0) { - FloweePay::instance()->p2pNet()->notifications().notifyNewTransaction(notification); + FloweePay::instance()->sendTransactionNotification(notification); } } assert(m_nextWalletTransactionId - firstNewTransaction == int(transactionsToSave.size())); -- 2.54.0 From 5e40c6a31225757fc61d0a663c5bb9627c41c7bb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 20:23:56 +0200 Subject: [PATCH 0539/1428] Update translations from Crowdin --- translations/floweepay-common_nl.ts | 86 ++++++------ translations/floweepay-desktop_nl.ts | 62 ++++++--- translations/floweepay-mobile_nl.ts | 187 ++++++++++++++++++++++----- 3 files changed, 240 insertions(+), 95 deletions(-) diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 5d4c926..540a86e 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,12 +4,12 @@ AccountInfo - + Wallet: Up to date Portemonnee: gesynchroniseerd - + Behind: %1 weeks, %2 days counter on weeks @@ -18,7 +18,7 @@ - + Behind: %1 days %1 dag oude data @@ -26,17 +26,17 @@ - + Up to date Is volledig bijgewerkt - + Updating Aan het bijwerken - + Still %1 hours behind Nog één uur @@ -120,30 +120,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -152,13 +152,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -184,19 +184,19 @@ - Wallet Information - Portemonnee Informatie - - - Network Details Netwerk Details - + About Over Ons + + + Wallets + Portemonnees + NotificationManager @@ -246,22 +246,22 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. @@ -269,7 +269,7 @@ QRWidget - + Copied to clipboard Naar klembord gekopieerd @@ -277,11 +277,17 @@ Wallet - - + + Change #%1 Wisselmunt #%1 + + + + Main #%1 + Standaard#%1 + WalletCoinsModel @@ -335,27 +341,27 @@ WalletHistoryModel - + Today Vandaag - + Yesterday Gisteren - + Earlier this week Eerder deze week - + This week Deze week - + Earlier this month Eerder deze maand @@ -363,12 +369,12 @@ WalletSecretsView - + Explanation Uitleg - + Coins a / b a) active coin-count. b) historical coin-count. @@ -377,23 +383,23 @@ b) historische munten. - - + + Copy Address Kopieer adres - + Copy Private Key Kopieer privésleutel - + Coins: %1 / %2 Munten: %1 / %2 - + Signed with Schnorr signatures in the past In het verleden ondertekend met Schnorr diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 3debed7..fe27367 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -59,82 +59,92 @@ Naam - + Sync Status Synchronisatie status - + Encryption Encryptie - + Password Wachtwoord - + Open Open - + Invalid PIN Ongeldige PIN - + + Include balance in total + Saldo opnemen in totaal + + + + Hide in private mode + Verbergen in privémodus + + + Address List Adreslijst - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Backup details Back-up details - + Seed-phrase Herstelzin - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -342,7 +352,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Derivatie - + Alternate phrase Alternatieve zin @@ -746,22 +756,32 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Nachtmodus - + + Private Mode + Privé modus + + + + Hides private wallets while enabled + Verbergt privé portemonnees + + + Version Versie - + Library Version Bibliotheek versie - + Synchronization Synchronisatie - + Network Status Netwerk Status diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 2a3ebe3..3b32f6c 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -81,10 +81,6 @@ Sync Status Synchronisatie status - - Primary Wallet - Primaire portemonnee - Backup information Back-up Informatie @@ -136,12 +132,36 @@ Gebruikte adressen - Addresses and Keys + Switches between still in use addresses and formerly used, new empty, addresses + Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen + + + Name + Naam + + + Archived wallets do not check for activities. Balance may be out of date + Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd + + + xpub + xpub + + + Addresses and keys Adressen en sleutels - Switches between still in use addresses and formerly used, new empty, addresses - Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen + Hide in private mode + Verbergen in privémodus + + + Unarchive Wallet + Portemonnee De-archiveren + + + Archive Wallet + Portemonnee Archiveren @@ -152,7 +172,7 @@ last active - meest recent actief + Laatst actief @@ -165,8 +185,40 @@ AccountsList - Wallet Information - Portemonnee Informatie + Wallet + Portemonnee + + + Wallets + Portemonnees + + + Add Wallet + Portemonnee toevoegen + + + Default Wallet + Standaard portemonnee + + + %1 is used on startup + %1 wordt gebruikt bij start + + + Exit Private Mode + Verlaat privémodus + + + Enter Private Mode + Start privémodus + + + Reveals wallets marked private + Onthult portefeuilles gemarkeerd als privé + + + Hides wallets marked private + Verbergt portemonnees gemarkeerd als privé @@ -264,6 +316,52 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Derivatie + + InstaPayConfigButton + + Enable Instant Pay + Direct Betalen inschakelen + + + Configure Instant Pay + Configureer Direct Betalen + + + Fast payments for low amounts + Direct betalen bij lage bedragen + + + Not configured + Niet geconfigureerd + + + Limit set to: %1 + Limiet ingesteld op: %1 + + + + InstaPayConfigPage + + Instant Pay + Direct Betalen + + + Requests for payment can be approved automatically using Instant Pay. + Betalingsverzoeken worden automatisch goedgekeurd met behulp van Direct Betalen. + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Beveiligde portemonnees kunnen niet worden gebruikt voor Direct Betalen ze een PIN vereisen voor gebruik + + + Enable Instant Pay for this wallet + Actief maken voor portemonnee + + + Maximum Amount + Maximum Bedrag + + MenuOverlay @@ -313,10 +411,6 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee - - Next - Volgende - Create a New Wallet Maak een nieuwe portemonnee @@ -410,8 +504,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Nieuwe Portemonnee - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - Dit maakt een nieuwe lege portemonnee die slim adressen maakt van één enkele herstelzin + This creates a new wallet that can be backed up with a seed-phrase + Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin @@ -595,10 +689,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Description Omschrijving - - Amount - Bedrag - Clear Wissen @@ -635,6 +725,39 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Continue Doorgaan + + Amount + requested amount of coin + Bedrag + + + Address + Bitcoin Cash address + Adres + + + + SelectDefaultAccountPage + + Select Wallet + Selecteer portemonnee + + + Pick which wallet will be selected on starting Flowee Pay + Kies welke portefeuille zal worden geselecteerd bij het starten van Flowee Pay + + + No InstaPay limit set + Geen Direct betalen limiet ingesteld + + + InstaPay till %1 + Direct Betalen tot %1 + + + InstaPay is turned off + Direct Betalen staat uit + SendTransactionsTab @@ -672,17 +795,17 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Moving the world towards a Bitcoin Cash economy We lopen het pad naar een Bitcoin Cash economie - - I already have a wallet - Ik heb al een portemonnee - OR OF - I want to send funds to my new wallet - Ik wil geld naar mijn nieuwe portemonnee sturen + Scan me to send funds to your HD wallet + Scan mij om geld naar uw HD-portemonnee te sturen + + + Add a different wallet + Een andere portemonnee toevoegen @@ -703,14 +826,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Coinbase Coinbase - - Size - Grootte - - - %1 bytes - %1 bytes - Transaction Hash Transactie hash @@ -770,6 +885,10 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Received at my addresses Ontvangen op mijn adressen + + Size: %1 bytes + Grootte: %1 bytes + TxInfoSmall -- 2.54.0 From 3c3a166ef51835ed68f22a945e0a98fba29f4a89 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 20:33:33 +0200 Subject: [PATCH 0540/1428] Support an extra special case --- src/AccountInfo.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index b1a24f9..8050ddb 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -111,8 +111,13 @@ QDateTime AccountInfo::lastBlockSynchedTime() const QString AccountInfo::timeBehind() const { const int accountHeight = lastBlockSynched(); - if (accountHeight <= 0) // For accounts that only expect tx in the future. + if (accountHeight <= 0) { // Special case. + // May be accounts that only expect tx in the future. + // may be wallets that are fully encrypted. + if (m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted()) + return tr("Offline"); return tr("Wallet: Up to date"); + } const int chainHeight = FloweePay::instance()->chainHeight(); const int diff = chainHeight - accountHeight; -- 2.54.0 From 2fcf86a037390200eb1f758a988018b264fb8583 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 May 2023 22:01:12 +0200 Subject: [PATCH 0541/1428] Translate months in history view The headers and such items now get localized on the history pages. We use static strings for the formats to avoid every call converting a c string to a utf16 qstring, which takes an allocate and a conversion. A tiny little amount of data kept in global memory to avoid a large number of mallocs is a net gain, I'm pretty sure. --- src/WalletHistoryModel.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index e4fb5f5..f9afbde 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -221,11 +221,12 @@ QString WalletHistoryModel::groupingPeriod(int groupId) const return tr("Earlier this month"); case WalletEnums::Month: default: { - uint32_t timestamp = m_groups.at(groupId).endTime; - QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); - if (date.year() == m_today.year()) - return date.toString("MMMM"); - return date.toString("MMMM yyyy"); + static const QString wide("MMMM yyyy"); + static const QString lean("MMMM"); + const uint32_t timestamp = m_groups.at(groupId).endTime; + const QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); + return QLocale::system().toString(date, + date.year() == m_today.year() ? lean : wide); } } } @@ -255,7 +256,7 @@ QString WalletHistoryModel::dateForItem(qreal offset) const return QString(); if (std::isnan(offset) || offset < 0 || offset > 1.0) return QString(); - const size_t row = std::round(offset * m_rowsProxy.size()); + const int row = std::round(offset * m_rowsProxy.size()); if (row >= m_rowsProxy.size()) return QString(); auto item = m_wallet->m_walletTransactions.at(txIndexFromRow(row)); @@ -264,7 +265,9 @@ QString WalletHistoryModel::dateForItem(qreal offset) const auto timestamp = secsSinceEpochFor(item.minedBlockHeight); if (timestamp == 0) return QString(); - return QDateTime::fromSecsSinceEpoch(timestamp).toString("MMMM yyyy"); + auto dt = QDateTime::fromSecsSinceEpoch(timestamp); + static const QString format("MMMM yyyy"); + return QLocale::system().toString(dt, format); } void WalletHistoryModel::appendTransactions(int firstNew, int count) -- 2.54.0 From 00bcdfa7961054185e95f7b587d4a3a26d0a855f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 10:32:28 +0200 Subject: [PATCH 0542/1428] Make GUI respond to entering a instapay limit. --- guis/mobile/InstaPayConfigButton.qml | 15 +++++++++++++-- src/AccountInfo.cpp | 1 + src/AccountInfo.h | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index bb3985d..b92db8e 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -22,11 +22,22 @@ TextButton { property QtObject account: portfolio.current text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") + property int limit: 0 + + function updateLimit() { + limit = root.account.fiatInstaPayLimit(Fiat.currencyName); + } + Component.onCompleted: updateLimit(); + Connections { + target: root.account + function onInstaPayLimitChanged() { + updateLimit(); + } + } + subtext: { if (!root.account.allowInstaPay) return qsTr("Fast payments for low amounts") - - let limit = root.account.fiatInstaPayLimit(Fiat.currencyName); if (limit === 0) return qsTr("Not configured"); return qsTr("Limit set to: %1").arg(Fiat.formattedPrice(limit)); diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 8050ddb..9d2e900 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -259,6 +259,7 @@ void AccountInfo::setFiatInstaPayLimit(const QString ¤cyCode, int cents) { assert(m_config.isValid()); m_config.setFiatInstaPayLimit(currencyCode, cents); + emit instaPayLimitChanged(currencyCode); } bool AccountInfo::countBalance() const diff --git a/src/AccountInfo.h b/src/AccountInfo.h index ec053d9..bd7032c 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -173,6 +173,7 @@ signals: void encryptionChanged(); void modelsChanged(); void instaPayAllowedChanged(); + void instaPayLimitChanged(const QString ¤cyCode); void neverEmitted(); // to silence the lambs^Warnings // for the benefit of the portfolio data provider -- 2.54.0 From 98605711bffe55c6611d28364c6a56a97e89ba84 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 10:32:44 +0200 Subject: [PATCH 0543/1428] add and fix docs. --- src/WalletConfig.h | 4 ++++ src/Wallet_support.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/WalletConfig.h b/src/WalletConfig.h index 25cd5ff..15a36c3 100644 --- a/src/WalletConfig.h +++ b/src/WalletConfig.h @@ -41,6 +41,10 @@ public: bool isValid() const; + /** + * Returns true if the account balance should be counted in the portfolio total balance. + * Notice that an archived wallet will never be counted in the total portfolio balance. + */ bool countBalance() const; void setCountBalance(bool newCountBalance); diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 3eabab9..cfacb22 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -429,7 +429,7 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) } assert (lastToRemove <= m_nextWalletTransactionId); - // we remove mathcing ones them from most recent to least recent below. + // we remove matching ones them from most recent to least recent below. // Notice that we don't do it in the above loop because a map reverse iterator doesn't // have a matching 'erase' method. -- 2.54.0 From ce07ceb4c21251bb14971ed44200f995220c93c8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 10:42:05 +0200 Subject: [PATCH 0544/1428] Rename WalletConfig -> AccountConfig The account config is not owned by the wallet, which makes this naming more logical and clear. --- src/{WalletConfig.cpp => AccountConfig.cpp} | 60 ++++++++++----------- src/{WalletConfig.h => AccountConfig.h} | 16 +++--- src/AccountInfo.h | 4 +- src/CMakeLists.txt | 2 +- src/FloweePay.cpp | 22 ++++---- src/FloweePay.h | 6 +-- src/Payment.cpp | 4 +- src/PortfolioDataProvider.cpp | 8 +-- 8 files changed, 61 insertions(+), 61 deletions(-) rename src/{WalletConfig.cpp => AccountConfig.cpp} (63%) rename src/{WalletConfig.h => AccountConfig.h} (84%) diff --git a/src/WalletConfig.cpp b/src/AccountConfig.cpp similarity index 63% rename from src/WalletConfig.cpp rename to src/AccountConfig.cpp index 7d2264d..b3f37d4 100644 --- a/src/WalletConfig.cpp +++ b/src/AccountConfig.cpp @@ -16,132 +16,132 @@ * along with this program. If not, see . */ -#include "WalletConfig.h" +#include "AccountConfig.h" #include "FloweePay.h" #include "Wallet.h" -WalletConfig::WalletConfig(const WalletConfig &other) +AccountConfig::AccountConfig(const AccountConfig &other) : m_walletId(other.m_walletId) { } -WalletConfig::WalletConfig(uint16_t walletId) +AccountConfig::AccountConfig(uint16_t walletId) : m_walletId(walletId) { } -WalletConfig::WalletConfig(Wallet *wallet) - : WalletConfig(wallet->segment()->segmentId()) +AccountConfig::AccountConfig(Wallet *wallet) + : AccountConfig(wallet->segment()->segmentId()) { } -WalletConfig &WalletConfig::operator=(const WalletConfig &other) +AccountConfig &AccountConfig::operator=(const AccountConfig &other) { m_walletId = other.m_walletId; return *this; } -bool WalletConfig::isValid() const +bool AccountConfig::isValid() const { if (m_walletId == -1) return false; - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); if (i == configs.end()) return false; return true; } -bool WalletConfig::countBalance() const +bool AccountConfig::countBalance() const { - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); return i->countBalance; } -void WalletConfig::setCountBalance(bool newCountBalance) +void AccountConfig::setCountBalance(bool newCountBalance) { auto *fp = FloweePay::instance(); - auto configs = fp->m_walletConfigs; + auto configs = fp->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); if (i->countBalance == newCountBalance) return; i->countBalance = newCountBalance; - fp->m_walletConfigs = configs; + fp->m_accountConfigs = configs; fp->startSaveData_priv(); emit fp->totalBalanceConfigChanged(); } -bool WalletConfig::allowInstaPay() const +bool AccountConfig::allowInstaPay() const { - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); return i->allowInstaPay; } -void WalletConfig::setAllowInstaPay(bool on) +void AccountConfig::setAllowInstaPay(bool on) { auto *fp = FloweePay::instance(); - auto configs = fp->m_walletConfigs; + auto configs = fp->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); if (i->allowInstaPay == on) return; i->allowInstaPay = on; - fp->m_walletConfigs = configs; + fp->m_accountConfigs = configs; fp->startSaveData_priv(); } -const QMap &WalletConfig::fiatInstaPayLimits() const +const QMap &AccountConfig::fiatInstaPayLimits() const { - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); return i->fiatInstaPayLimits; } -int WalletConfig::fiatInstaPayLimit(const QString ¤cyCode) const +int AccountConfig::fiatInstaPayLimit(const QString ¤cyCode) const { - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); return i->fiatInstaPayLimits.value(currencyCode); } -void WalletConfig::setFiatInstaPayLimit(const QString ¤cyCode, int limitInCent) +void AccountConfig::setFiatInstaPayLimit(const QString ¤cyCode, int limitInCent) { auto *fp = FloweePay::instance(); - auto configs = fp->m_walletConfigs; + auto configs = fp->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); if (i->fiatInstaPayLimits.value(currencyCode) == limitInCent) return; i->fiatInstaPayLimits[currencyCode] = limitInCent; - fp->m_walletConfigs = configs; + fp->m_accountConfigs = configs; fp->startSaveData_priv(); } -bool WalletConfig::isPrivate() const +bool AccountConfig::isPrivate() const { - auto configs = FloweePay::instance()->m_walletConfigs; + auto configs = FloweePay::instance()->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); return i->privateWallet; } -void WalletConfig::setIsPrivate(bool newIsPrivate) +void AccountConfig::setIsPrivate(bool newIsPrivate) { auto *fp = FloweePay::instance(); - auto configs = fp->m_walletConfigs; + auto configs = fp->m_accountConfigs; auto i = configs.find(m_walletId); assert(i != configs.end()); if (i->privateWallet == newIsPrivate) return; i->privateWallet = newIsPrivate; - fp->m_walletConfigs = configs; + fp->m_accountConfigs = configs; fp->startSaveData_priv(); } diff --git a/src/WalletConfig.h b/src/AccountConfig.h similarity index 84% rename from src/WalletConfig.h rename to src/AccountConfig.h index 15a36c3..42050f8 100644 --- a/src/WalletConfig.h +++ b/src/AccountConfig.h @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef FLOWEE_WALLET_CONFIG_H -#define FLOWEE_WALLET_CONFIG_H +#ifndef FLOWEE_ACCOUNT_CONFIG_H +#define FLOWEE_ACCOUNT_CONFIG_H #include #include @@ -28,16 +28,16 @@ class Wallet; * A set of user-level config fields for a wallet. * This is owned by the FloweePay application singleton. */ -class WalletConfig +class AccountConfig { public: /// creates an invalid wallet config - WalletConfig() = default; - explicit WalletConfig(uint16_t walletId); - explicit WalletConfig(Wallet *wallet); - WalletConfig(const WalletConfig &other); + AccountConfig() = default; + explicit AccountConfig(uint16_t walletId); + explicit AccountConfig(Wallet *wallet); + AccountConfig(const AccountConfig &other); - WalletConfig &operator=(const WalletConfig &other); + AccountConfig &operator=(const AccountConfig &other); bool isValid() const; diff --git a/src/AccountInfo.h b/src/AccountInfo.h index bd7032c..d95b653 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -19,7 +19,7 @@ #define ACCOUNTINFO_H #include -#include "WalletConfig.h" +#include "AccountConfig.h" #include "WalletHistoryModel.h" #include "WalletSecretsModel.h" @@ -186,7 +186,7 @@ private slots: private: Wallet * const m_wallet; - WalletConfig m_config; + AccountConfig m_config; QTimer *m_closeWalletTimer = nullptr; std::unique_ptr m_model; std::unique_ptr m_secretsModel; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0725b3..a8278d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,7 +44,7 @@ set (PAY_SOURCES TransactionInfo.cpp Wallet.cpp WalletCoinsModel.cpp - WalletConfig.cpp + AccountConfig.cpp WalletHistoryModel.cpp WalletKeyView.cpp WalletSecretsModel.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b5a4989..1a7bea3 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -20,7 +20,7 @@ #include "NewWalletConfig.h" #include "AddressInfo.h" #include "PriceDataProvider.h" -#include "WalletConfig.h" +#include "AccountConfig.h" #include #include @@ -290,7 +290,7 @@ FloweePay *FloweePay::instance() void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ification) { auto *me = FloweePay::instance(); - auto configs = me->m_walletConfigs; + auto configs = me->m_accountConfigs; auto i = configs.find(notification.privacySegment); // don't broadcast notifications of private wallets when private mode is enabled if (me->privateMode() && (i == configs.end() || i->privateWallet)) @@ -321,7 +321,7 @@ void FloweePay::init() dl->addHeaderListener(w); dl->connectionManager().addPrivacySegment(w->segment()); m_wallets.append(w); - m_walletConfigs.insert(w->segment()->segmentId(), {}); + m_accountConfigs.insert(w->segment()->segmentId(), {}); connectToWallet(w); logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId(); lastOpened = w; @@ -354,13 +354,13 @@ void FloweePay::init() } else if (parser.tag() == WalletSetting_CountBalance) { if (lastOpened) - m_walletConfigs[lastOpened->segment()->segmentId()].countBalance = parser.boolData(); + m_accountConfigs[lastOpened->segment()->segmentId()].countBalance = parser.boolData(); else logWarning() << "Setting seen before walletId"; } else if (parser.tag() == WalletSetting_IsPrivate) { if (lastOpened) - m_walletConfigs[lastOpened->segment()->segmentId()].privateWallet = parser.boolData(); + m_accountConfigs[lastOpened->segment()->segmentId()].privateWallet = parser.boolData(); else logWarning() << "Setting seen before walletId"; } @@ -372,13 +372,13 @@ void FloweePay::init() } else if (parser.tag() == WalletSetting_FiatInstaPayEnabled) { if (lastOpened) - m_walletConfigs[lastOpened->segment()->segmentId()].allowInstaPay = parser.boolData(); + m_accountConfigs[lastOpened->segment()->segmentId()].allowInstaPay = parser.boolData(); else logWarning() << "Setting seen before walletId"; } else if (parser.tag() == WalletSetting_FiatInstaPayLimit) { if (lastOpened && !currencyCode.isEmpty()) - m_walletConfigs[lastOpened->segment()->segmentId()].fiatInstaPayLimits[currencyCode] + m_accountConfigs[lastOpened->segment()->segmentId()].fiatInstaPayLimits[currencyCode] = parser.intData(); else logWarning() << "Setting seen before walletId or currencyCode"; @@ -428,8 +428,8 @@ void FloweePay::saveData() } // each wallet should have a config file, lets save the content - auto conf = m_walletConfigs.find(wallet->segment()->segmentId()); - assert(conf != m_walletConfigs.end()); + auto conf = m_accountConfigs.find(wallet->segment()->segmentId()); + assert(conf != m_accountConfigs.end()); builder.add(WalletSetting_CountBalance, conf->countBalance); builder.add(WalletSetting_IsPrivate, conf->privateWallet); builder.add(WalletSetting_FiatInstaPayEnabled, conf->allowInstaPay); @@ -668,7 +668,7 @@ Wallet *FloweePay::createWallet(const QString &name) dl->connectionManager().addPrivacySegment(w->segment()); w->moveToThread(thread()); m_wallets.append(w); - m_walletConfigs.insert(id, {}); + m_accountConfigs.insert(id, {}); connectToWallet(w); emit startSaveData_priv(); // schedule a save of the m_wallets list @@ -1148,7 +1148,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const wallet->setUserOwnedWallet(true); if (!walletName.isEmpty()) wallet->setName(walletName); - assert(m_walletConfigs.contains(wallet->segment()->segmentId())); + assert(m_accountConfigs.contains(wallet->segment()->segmentId())); // little hacky to make listeners realize we really changed the wallet. m_wallets.clear(); emit walletsChanged(); diff --git a/src/FloweePay.h b/src/FloweePay.h index 284d7bb..2211941 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -316,7 +316,7 @@ private slots: void saveData(); private: - struct WalletConfigData { + struct AccountConfigData { // a per-wallet bool indicating its balance should be counted in the whole bool countBalance = true; bool privateWallet = false; // is hidden in private mode @@ -344,7 +344,7 @@ private: NotificationManager m_notifications; CameraController* m_cameraController; QList m_wallets; - QHash m_walletConfigs; // key is wallet-segment-id + QHash m_accountConfigs; // key is wallet-segment-id int m_dspTimeout = 5000; int m_windowWidth = 500; int m_windowHeight = 500; @@ -359,7 +359,7 @@ private: bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true - friend class WalletConfig; + friend class AccountConfig; }; #endif diff --git a/src/Payment.cpp b/src/Payment.cpp index 69f1a71..3a3aea1 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -23,7 +23,7 @@ #include "FloweePay.h" #include "AccountInfo.h" #include "PriceDataProvider.h" -#include "WalletConfig.h" +#include "AccountConfig.h" #include #include @@ -312,7 +312,7 @@ void Payment::prepare() assert(prices); auto currency = prices->currencyName(); assert(!currency.isEmpty()); - WalletConfig conf(m_wallet); + AccountConfig conf(m_wallet); if (!conf.allowInstaPay()) { logInfo() << "Payment-prepare. Insta-pay for wallet is turned off:" << m_wallet->name(); return; diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index e5cd819..45b9299 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -19,7 +19,7 @@ #include "AccountInfo.h" #include "FloweePay.h" #include "Wallet.h" -#include "WalletConfig.h" +#include "AccountConfig.h" #include @@ -67,7 +67,7 @@ QList PortfolioDataProvider::accounts() const continue; if (!account->userOwnedWallet()) continue; - if (privateModeOn && WalletConfig(account->id()).isPrivate()) + if (privateModeOn && AccountConfig(account->id()).isPrivate()) continue; answer.append(account); } @@ -141,7 +141,7 @@ void PortfolioDataProvider::selectDefaultWallet() } else if (prio < selectedPriority) { if (privateModeOn) { // downgrade wallets that are 'private' while in private mode. - WalletConfig config(wallet); + AccountConfig config(wallet); if (config.isPrivate()) { if (fallback == -1) fallback = i; @@ -171,7 +171,7 @@ double PortfolioDataProvider::totalBalance() const // skip archived wallet balances. if (!wallet->segment() || wallet->segment()->priority() == PrivacySegment::OnlyManual) continue; - WalletConfig config(wallet); + AccountConfig config(wallet); assert(config.isValid()); if (!config.countBalance() || (privateModeOn && config.isPrivate())) continue; -- 2.54.0 From 66ed55823d6e9709b85ebd5b84f7c71182d634f0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 11:14:12 +0200 Subject: [PATCH 0545/1428] Tie instapay check to payment-address parsing. The basic usecases of instapay is that the payment request is filled in one go (from QR for instance) and thus we try to send. If that fails (not enough funds or whatever), we disable instapay in order to avoid 'magic happens' user experiences. --- src/Payment.cpp | 32 +++++++++++++++++--------------- src/Payment.h | 4 ++-- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 3a3aea1..1a360be 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -82,7 +82,20 @@ void Payment::setTargetAddress(const QString &address) { soleOut()->setAddress(address); emit targetAddressChanged(); - doAutoPrepare(); + if (m_autoPrepare) { + try { prepare(); } catch (...) {} + /* + * InstaPay is typically enabled together with auto-prepare and that gives + * great UX for, well, instantly paying. + * + * The above will have created AND send the transaction. + * BUT, if the prepare() failed, we should stop trying to do the 'instaPay'. + * It failed, now let the user decide when to send. + * + * Either way, we can set it to false now. + */ + m_allowInstaPay = false; + } } QString Payment::targetAddress() @@ -415,23 +428,12 @@ bool Payment::allowInstaPay() const return m_allowInstaPay; } -void Payment::setAllowInstaPay(bool newAllowInstaPay) +void Payment::setAllowInstaPay(bool allowIt) { - if (m_allowInstaPay == newAllowInstaPay) + if (m_allowInstaPay == allowIt) return; - m_allowInstaPay = newAllowInstaPay; + m_allowInstaPay = allowIt; emit allowInstaPayChanged(); - - /* - * InstaPay is typically enabled together with auto-prepare and that gives - * great UX for, well, instantly paying. - * BUT, if the prepare() failed, we should stop trying to do the 'instaPay'. - * It failed, now let the user decide when to send. - */ - QTimer::singleShot(10, this, [=]() { - if (!m_error.isEmpty()) - m_allowInstaPay = false; - }); } bool Payment::autoPrepare() const diff --git a/src/Payment.h b/src/Payment.h index 8008bd9..5276193 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -63,7 +63,7 @@ class Payment : public QObject Q_PROPERTY(QString txid READ txid NOTIFY txCreated) Q_PROPERTY(int assignedFee READ assignedFee NOTIFY txCreated) Q_PROPERTY(int txSize READ txSize NOTIFY txCreated) - /// If Prepare failed, this is set. + /// If prepare() failed, this is set. Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_ENUMS(DetailType BroadcastStatus) @@ -204,7 +204,7 @@ public: void setAutoPrepare(bool newAutoPrepare); bool allowInstaPay() const; - void setAllowInstaPay(bool newAllowInstaPay); + void setAllowInstaPay(bool allowIt); private slots: void sentToPeer(); -- 2.54.0 From e75fb07d9c87153a79533468c505cdc0bce0448d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 15:12:17 +0200 Subject: [PATCH 0546/1428] whitespace --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2952108..f316775 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,7 +194,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" - ) + ) qt6_android_generate_deployment_settings(pay_mobile) qt6_android_add_apk_target(pay_mobile) endif () -- 2.54.0 From 752cc9b68aca186e60d72eac36ff47977233312e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 15:20:11 +0200 Subject: [PATCH 0547/1428] Respect amount of incoming payment request. A payment request, for instance a QR code, which includes an amount to be paid is expected to not change the to-be-paid amount, uses being dropped in the price editing UI is therefore a bit weird. Almost an anti-pattern. This honors that concept and when the price is included in the scanned QR, we change the UI layout to removed the editing widgets and show static display widgets instead. This also adds a header menu item to edit the amount to be paid and get back to the old UI. Additionally, a new UI is added to show the address we are about to pay to, likewise reachable from the header menu. --- guis/mobile/Page.qml | 3 ++ guis/mobile/PayWithQR.qml | 78 ++++++++++++++++++++++++++++++++++++--- src/Payment.cpp | 13 ++++++- src/Payment.h | 10 ++++- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 8ff15b8..418d222 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -49,6 +49,9 @@ QQC2.Control { function takeFocus() { focusScope.forceActiveFocus(); } + function closeHeaderMenu() { + headerMenu.close(); + } onMenuItemsChanged: { // remove old ones first diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index f673c32..7c905f9 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -35,13 +35,34 @@ Page { payment.prepare(); // auto-prepare doesn't act on changes done on the details. } } + property QtObject showTargetAddress: QQC2.Action { + checkable: true + text: qsTr("Show Address", "to show a bitcoincash address") + } + property QtObject editPrice: QQC2.Action { + text: qsTr("Edit Amount", "Edit amount of money to send") + onTriggered: { + root.closeHeaderMenu(); + // this action can only ever be used to start editing. + root.allowEditAmount = true; + priceInput.forceActiveFocus(); + priceInput.fiatFollowsSats = false + } + } menuItems: { // only have menu items as long as we are effectively // showing this page and not some overlay. - if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) - return [ sendAllAction ]; + if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) { + // a QR _with_ a bch-amount will turn off editing of amount-to-send + if (allowEditAmount) + return [ sendAllAction ]; + else + return [ showTargetAddress, editPrice ]; + } return []; } + // if true, show widgets to edit the amount-to-send + property bool allowEditAmount: true Item { // data QRScanner { @@ -55,8 +76,8 @@ Page { } else { payment.targetAddress = rc - priceInput.forceActiveFocus(); - priceInput.fiatFollowsSats = false + // should the price be included in the QR code, don't show editing widgets. + root.allowEditAmount = payment.paymentAmount <= 0; } } } @@ -75,9 +96,40 @@ Page { } PriceInputWidget { - id: priceInput + id: priceInput // when the price / amount is editable paymentBackend: payment width: parent.width + x: allowEditAmount ? 0 : 0 - width + + Behavior on x { NumberAnimation { } } + } + Item { + id: priceFeedback // when the price / amount is given by the scanned QR + width: parent.width + x: allowEditAmount ? 0 - width : 0 + y: 10 + height: col.height + + Column { + id: col + width: parent.width + Flowee.Label { + id: fiatPrice + anchors.horizontalCenter: parent.horizontalCenter + text: Fiat.formattedPrice(payment.paymentAmountFiat) + font.pixelSize: 38 + } + + Flowee.BitcoinAmountLabel { + value: payment.paymentAmount + colorize: false + showFiat: false + font.pixelSize: mainWindow.font.pixelSize * 0.8 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + Behavior on x { NumberAnimation { } } } Flowee.Label { @@ -121,7 +173,6 @@ Page { } } - AccountSelectorWidget { id: walletNameBackground anchors.bottom: numericKeyboard.top @@ -136,6 +187,21 @@ Page { anchors.bottomMargin: 15 width: parent.width enabled: !payment.details[0].maxSelected + x: allowEditAmount ? 0 : 0 - width + + Behavior on x { NumberAnimation { } } + } + + PageTitledBox { + anchors.top: numericKeyboard.top + width: parent.width + visible: !allowEditAmount && showTargetAddress.checked + title: qsTr("Destination Address") + Flowee.LabelWithClipboard { + text: payment.niceAddress + width: parent.width + font.pixelSize: mainWindow.font.pixelSize * 0.9 + } } SlideToApprove { diff --git a/src/Payment.cpp b/src/Payment.cpp index 1a360be..18ab112 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -103,11 +103,22 @@ QString Payment::targetAddress() return soleOut()->address(); } -QString Payment::formattedTargetAddress() +QString Payment::formattedTargetAddress() const { return soleOut()->formattedTarget(); } +QString Payment::niceAddress() const +{ + auto a = soleOut()->formattedTarget(); + if (a.isEmpty()) + a = soleOut()->address(); + int index = a.indexOf(':'); + if (index > 0) + return a.mid(index+1); + return a; +} + bool Payment::walletNeedsPin() const { if (!m_account) diff --git a/src/Payment.h b/src/Payment.h index 5276193..e483fff 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -44,6 +44,8 @@ class Payment : public QObject Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTargetAddress READ formattedTargetAddress NOTIFY targetAddressChanged) + // cleaned but short. + Q_PROPERTY(QString niceAddress READ niceAddress NOTIFY targetAddressChanged) Q_PROPERTY(bool preferSchnorr READ preferSchnorr WRITE setPreferSchnorr NOTIFY preferSchnorrChanged) /// If input is valid, tx can be prepared. \see prepare() Q_PROPERTY(bool isValid READ validate NOTIFY validChanged) @@ -142,7 +144,13 @@ public: * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. */ - QString formattedTargetAddress(); + QString formattedTargetAddress() const; + /** + * The nicest to display address version. + * This will always return (if available) the BCH style (cash-address) address, without + * the bitcoincash: prefix. + */ + QString niceAddress() const; bool walletNeedsPin() const; -- 2.54.0 From 49a8a9ce3495b728d16eb8f9f1c81534febaa62b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 15:47:14 +0200 Subject: [PATCH 0548/1428] Cleanup nice-address usage A Payment object now has a user-typed address, a formattedAddress which is a properly formatted cash-address and last a 'niceAddress' which is the same cash-address but without the chain prefix. This cleans up the usage and removes some code from the GUI that did the string manipulation there. --- guis/Flowee/AddressInfoWidget.qml | 5 +- guis/desktop/SendTransactionPane.qml | 2 +- guis/mobile/PayToOthers.qml | 68 ++++++++++++---------------- src/Payment.cpp | 8 +--- src/PaymentDetailOutput.cpp | 20 ++++---- src/PaymentDetailOutput_p.h | 7 +++ 6 files changed, 50 insertions(+), 60 deletions(-) diff --git a/guis/Flowee/AddressInfoWidget.qml b/guis/Flowee/AddressInfoWidget.qml index c09ec00..a13e665 100644 --- a/guis/Flowee/AddressInfoWidget.qml +++ b/guis/Flowee/AddressInfoWidget.qml @@ -36,10 +36,7 @@ Item { function createInfo() { if (addressOk) { - var address = paymentDetail.formattedTarget - if (address === "") // it didn't need reformatting - address = paymentDetail.address - info = Pay.researchAddress(address, addressInfo) + info = Pay.researchAddress(paymentDetail.formattedTarget, addressInfo) } else { delete info; diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 15ccda3..aa7cc09 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -317,7 +317,7 @@ Item { } } Flowee.LabelWithClipboard { - visible: text !== "" + visible: paymentDetail.address !== text Layout.fillWidth: true text: paymentDetail.formattedTarget horizontalAlignment: Qt.AlignRight diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index d7bd78f..39fbc0e 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -184,39 +184,35 @@ Page { /* This page just shows the results, editing is done * on its own page. */ - Flowee.Label { - text: qsTr("Destination") + ":" - } - Flowee.LabelWithClipboard { + PageTitledBox { width: parent.width - font.italic: paymentDetail.address === "" - text: { - var s = paymentDetail.address - if (s === "") - return qsTr("unset", "indication of empty"); - if (addressInfo.addressOk) { - let index = s.indexOf(":"); - if (index >= 0) - s = s.substr(index + 1); // cut off the prefix + title: qsTr("Destination") + + Flowee.LabelWithClipboard { + width: parent.width + font.italic: paymentDetail.address === "" + text: { + var s = paymentDetail.niceAddress + if (s === "") + return qsTr("unset", "indication of empty"); + return s; } - return s; + color: addressInfo.addressOk || paymentDetail.address === "" + ? palette.windowText : mainWindow.errorRed + font.pixelSize: mainWindow.font.pixelSize * 0.9 + menuText: qsTr("Copy Address") + clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: + } + + Flowee.AddressInfoWidget { + id: addressInfo + width: parent.width + addressType: Pay.identifyString(paymentDetail.address); + } + Flowee.BitcoinAmountLabel { + value: paymentDetail.paymentAmount + colorize: false } - color: addressInfo.addressOk || paymentDetail.address === "" - ? palette.windowText : mainWindow.errorRed - menuText: qsTr("Copy Address") - clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: - } - Flowee.AddressInfoWidget { - id: addressInfo - width: parent.width - addressType: Pay.identifyString(paymentDetail.address); - } - Flowee.Label { - text: qsTr("Amount")+ ":" - } - Flowee.BitcoinAmountLabel { - value: paymentDetail.paymentAmount - colorize: false } } } @@ -304,15 +300,9 @@ Page { anchors.top: destination.bottom anchors.topMargin: 10 - visible: text !== "" - text: { - let string = paymentDetail.formattedTarget - let index = string.indexOf(":"); - if (index >= 0) { - string = string.substr(index + 1); // cut off the prefix - } - return string; - } + // only show if its substantially different + visible: text!== "" && text !== destination.text && destination.text !== paymentDetail.formattedTarget + text: paymentDetail.niceAddress clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: font.italic: true menuText: qsTr("Copy Address") diff --git a/src/Payment.cpp b/src/Payment.cpp index 18ab112..0c590cb 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -110,13 +110,7 @@ QString Payment::formattedTargetAddress() const QString Payment::niceAddress() const { - auto a = soleOut()->formattedTarget(); - if (a.isEmpty()) - a = soleOut()->address(); - int index = a.indexOf(':'); - if (index > 0) - return a.mid(index+1); - return a; + return soleOut()->niceAddress(); } bool Payment::walletNeedsPin() const diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 757e3e4..ba51bbd 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -172,15 +172,8 @@ void PaymentDetailOutput::setAddress(const QString &address_) m_formattedTarget.clear(); m_addressOk = !encodedAddress.empty(); - if (m_addressOk) { - const auto size = chainPrefixCopy.size(); - // lets see if the encoded address is substantially different from the user-given address - auto full = QString::fromStdString(encodedAddress); - auto formattedTarget = full.mid(size + 1); - if (full != m_address && formattedTarget != m_address) - m_formattedTarget = full; - } - + if (m_addressOk) + m_formattedTarget = QString::fromStdString(encodedAddress); emit addressChanged(); checkValid(); } @@ -299,6 +292,15 @@ const QString &PaymentDetailOutput::formattedTarget() const return m_formattedTarget; } +QString PaymentDetailOutput::niceAddress() const +{ + auto a(m_formattedTarget); + int index = a.indexOf(':'); + if (index > 0) + return a.mid(index+1); + return a; +} + bool PaymentDetailOutput::maxAllowed() const { return m_maxAllowed; diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 6c25884..9a9da68 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -28,6 +28,7 @@ class PaymentDetailOutput : public PaymentDetail Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountChanged) // cleaned up and re-formatted Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY addressChanged) + Q_PROPERTY(QString niceAddress READ niceAddress NOTIFY addressChanged) Q_PROPERTY(bool maxAllowed READ maxAllowed WRITE setMaxAllowed NOTIFY maxAllowedChanged) Q_PROPERTY(bool fiatFollows READ fiatFollows WRITE setFiatFollows NOTIFY fiatFollowsChanged) Q_PROPERTY(bool maxSelected READ maxSelected WRITE setMaxSelected NOTIFY maxSelectedChanged) @@ -44,6 +45,12 @@ public: void setAddress(const QString &newAddress); /// is non-empty if the address() is proper. const QString &formattedTarget() const; + /** + * The nicest to display address version. + * This will always return (if available) the BCH style (cash-address) address, without + * the bitcoincash: prefix. + */ + QString niceAddress() const; /** * The mayment amount can be set to 'max' which means the wallet-total-value. -- 2.54.0 From 9adb6f2ae759ad61f5cb3f821c2f3c31c6287732 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 15:55:44 +0200 Subject: [PATCH 0549/1428] Fixlet (spello) in log message. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1a7bea3..85d1929 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -979,7 +979,7 @@ QObject *FloweePay::researchAddress(const QString &address, QObject *parent) { CashAddress::Content c = CashAddress::decodeCashAddrContent(address.toStdString(), m_chainPrefix); if (c.hash.empty() || c.type != CashAddress::PUBKEY_TYPE) { - logWarning() << "researchAddress() only works with a propertly formatted cash-address!"; + logWarning() << "researchAddress() only works with a properly formatted cash-address!"; return nullptr; } const KeyId key(reinterpret_cast(c.hash.data())); -- 2.54.0 From f34c1e199f3a7d9508a34f65a79498af1d14392a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 31 May 2023 16:23:23 +0200 Subject: [PATCH 0550/1428] UX fix, this makes the 'cursor' go away at edit end. --- guis/mobile/EditableLabel.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 6ffa710..937b55b 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -51,10 +51,8 @@ Item { anchors.margins: -15 onClicked: { editWidget.visible = !editWidget.visible; - if (editWidget.visible) { - editWidget.focus = true + if (editWidget.focus) editWidget.forceActiveFocus(); - } } } Behavior on opacity { NumberAnimation { } } @@ -65,6 +63,7 @@ Item { anchors.right: editIcon.left anchors.rightMargin: 10 visible: false + focus: visible text: ourLabel.text onTextChanged: { ourLabel.text = text; -- 2.54.0 From fc164996d7f5571f94f59e5223fa680c6f78d07e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Jun 2023 16:31:20 +0200 Subject: [PATCH 0551/1428] Improve UX about when to switch positions. As long as the user has not added a second wallet there is no need to show the name 'initial wallet' and thus we show the 'add wallet' button at the bottom. --- guis/mobile/MenuOverlay.qml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index c3d0cbf..dbeb503 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -55,8 +55,8 @@ Item { if (openAccounts) h = h + extraOptions.height + 10 // but we just don't show the accounts at all if - // this is the initial empty wallet. - if (!isLoading && portfolio.current.isUserOwned) + // this is the initial (empty) wallet. + if (!isLoading && portfolio.accounts.length > 1) h = h+ Math.max(currentAccountName.height, 12) + 10 return h; } @@ -125,7 +125,6 @@ Item { anchors.bottomMargin: 8 width: 20 height: 7 - } MouseArea { anchors.top: currentAccountName.top @@ -155,7 +154,7 @@ Item { } Loader { width: parent.width - active: !isLoading && portfolio.current.isUserOwned + active: !isLoading && portfolio.accounts.length > 1 sourceComponent: addWalletRow } } @@ -191,7 +190,7 @@ Item { } Loader { width: parent.width - active: isLoading || !portfolio.current.isUserOwned + active: isLoading || portfolio.accounts.length <= 1 sourceComponent: addWalletRow } } -- 2.54.0 From db7162f4d3450fd95426ca7f5fe99e6d45004f20 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 Jun 2023 23:39:03 +0200 Subject: [PATCH 0552/1428] Make editing fiat on scanning QR be default. When you scan a QR which has no price embedded (just an address) this makes the default go to fiat (euro/usd etc) for what you type. --- guis/mobile/PriceInputWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index 225df95..a110a2f 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -22,10 +22,10 @@ import Flowee.org.pay FocusScope { id: root - // if true, the bitcoin value is provided by the user (or QR), and the fiat follows + // if true, the bitcoin value is provided by the user (or QR), and the fiat follows. // if false, the user edits the fiat price and the bitcoin value is calculated. // Notice that 'send all' overrules both and gets the data from the wallet-total - property bool fiatFollowsSats: true + property bool fiatFollowsSats: false // made available for the NumericKeyboardWidget property var editor: fiatFollowsSats ? priceBch.money : priceFiat.money; /** -- 2.54.0 From cd5394c704c2ef1f67b0361b81d9346967101cb1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 18:22:39 +0200 Subject: [PATCH 0553/1428] Replace assert with check --- src/Wallet_support.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index cfacb22..78cb2ac 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -435,7 +435,11 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) while (txIndex >= lastToRemove) { auto i = m_walletTransactions.find(txIndex--); - assert (i != m_walletTransactions.end()); + if (i == m_walletTransactions.end()) { + // don't assume we didn't replay any transactions before, there may be + // gaps when addressing the transactions by index. + continue; + } // this transaction was appended to the UTXO and now something has // to be inserted before, which may alter the UTXO to such an extend that THIS // transaction may be found to spend more outputs. -- 2.54.0 From fe67c5273e130e3fa0761eb942ccd303c4d1d1d1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 19:20:18 +0200 Subject: [PATCH 0554/1428] Handle invalid addresses better. The user input now gets the user interface string 'incorrect' instead of 'unset' if no proper address could be resolved from it. --- guis/mobile/PayToOthers.qml | 7 ++-- src/PaymentDetailOutput.cpp | 69 ++++++++++++++++++++++--------------- src/PaymentDetailOutput_p.h | 14 ++++++-- 3 files changed, 58 insertions(+), 32 deletions(-) diff --git a/guis/mobile/PayToOthers.qml b/guis/mobile/PayToOthers.qml index 39fbc0e..7d4492a 100644 --- a/guis/mobile/PayToOthers.qml +++ b/guis/mobile/PayToOthers.qml @@ -193,8 +193,11 @@ Page { font.italic: paymentDetail.address === "" text: { var s = paymentDetail.niceAddress - if (s === "") - return qsTr("unset", "indication of empty"); + if (s === "") { + if (paymentDetail.address === "") // the user-specified text is empty + return qsTr("unset", "indication of empty"); + return qsTr("invalid", "address is not correct"); + } return s; } color: addressInfo.addressOk || paymentDetail.address === "" diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index ba51bbd..ad5f55e 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -101,8 +101,6 @@ void PaymentDetailOutput::setAddress(const QString &address_) if (m_address == addressOrURL) return; m_address = addressOrURL; - const std::string &chainPrefixCopy = chainPrefix(); - /* * Users may paste an address that is really a payment url. * This basically means we may have a price added after a questionmark. @@ -130,17 +128,27 @@ void PaymentDetailOutput::setAddress(const QString &address_) m_address = addressOrURL.left(urlStart); } + createFormattedAddress(); + emit addressChanged(); // always emit at least once. +} + +void PaymentDetailOutput::createFormattedAddress() +{ + const std::string &chainPrefixCopy = chainPrefix(); + std::string encodedAddress; switch (FloweePay::instance()->identifyString(m_address)) { case WalletEnums::LegacyPKH: { - CBase58Data legacy; - auto ok = legacy.SetString(m_address.toStdString()); - assert(ok); - assert(legacy.isMainnetPkh() || legacy.isTestnetPkh()); - CashAddress::Content c; - c.hash = legacy.data(); - c.type = CashAddress::PUBKEY_TYPE; - encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); + if (m_forceLegacyOk) { + CBase58Data legacy; + auto ok = legacy.SetString(m_address.toStdString()); + assert(ok); + assert(legacy.isMainnetPkh() || legacy.isTestnetPkh()); + CashAddress::Content c; + c.hash = legacy.data(); + c.type = CashAddress::PUBKEY_TYPE; + encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); + } break; } case WalletEnums::CashPKH: { @@ -150,14 +158,16 @@ void PaymentDetailOutput::setAddress(const QString &address_) break; } case WalletEnums::LegacySH: { - CBase58Data legacy; - auto ok = legacy.SetString(m_address.toStdString()); - assert(ok); - assert(legacy.isMainnetSh() || legacy.isTestnetSh()); - CashAddress::Content c; - c.hash = legacy.data(); - c.type = CashAddress::SCRIPT_TYPE; - encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); + if (m_forceLegacyOk) { + CBase58Data legacy; + auto ok = legacy.SetString(m_address.toStdString()); + assert(ok); + assert(legacy.isMainnetSh() || legacy.isTestnetSh()); + CashAddress::Content c; + c.hash = legacy.data(); + c.type = CashAddress::SCRIPT_TYPE; + encodedAddress = CashAddress::encodeCashAddr(chainPrefixCopy, c); + } break; } case WalletEnums::CashSH: { @@ -170,12 +180,16 @@ void PaymentDetailOutput::setAddress(const QString &address_) break; } - m_formattedTarget.clear(); - m_addressOk = !encodedAddress.empty(); - if (m_addressOk) - m_formattedTarget = QString::fromStdString(encodedAddress); - emit addressChanged(); - checkValid(); + bool addressOk = !encodedAddress.empty(); + if (addressOk != m_addressOk) { + m_addressOk = addressOk; + if (m_addressOk) + m_formattedTarget = QString::fromStdString(encodedAddress); + else + m_formattedTarget.clear(); + checkValid(); + emit correctAddressChanged(); // the formatted target is tied to this signal + } } void PaymentDetailOutput::checkValid() @@ -193,11 +207,12 @@ bool PaymentDetailOutput::forceLegacyOk() const return m_forceLegacyOk; } -void PaymentDetailOutput::setForceLegacyOk(bool newForceLegacyOk) +void PaymentDetailOutput::setForceLegacyOk(bool force) { - if (m_forceLegacyOk == newForceLegacyOk) + if (m_forceLegacyOk == force) return; - m_forceLegacyOk = newForceLegacyOk; + m_forceLegacyOk = force; + createFormattedAddress(); emit forceLegacyOkChanged(); } diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 9a9da68..0ad841e 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -23,12 +23,18 @@ class PaymentDetailOutput : public PaymentDetail { Q_OBJECT + /* + * The user set string which is the address. + * We verify this string and fill \a formattedTarget only when this is a correct address. + * Notice that a legacy address will only be seen as correct when the forceLegacyOk is true. + */ Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY paymentAmountChanged) Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountChanged) - // cleaned up and re-formatted - Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY addressChanged) - Q_PROPERTY(QString niceAddress READ niceAddress NOTIFY addressChanged) + // cleaned up and re-formatted, empty if invalid. + Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY correctAddressChanged) + // same as formatted, but without prefix. + Q_PROPERTY(QString niceAddress READ niceAddress NOTIFY correctAddressChanged) Q_PROPERTY(bool maxAllowed READ maxAllowed WRITE setMaxAllowed NOTIFY maxAllowedChanged) Q_PROPERTY(bool fiatFollows READ fiatFollows WRITE setFiatFollows NOTIFY fiatFollowsChanged) Q_PROPERTY(bool maxSelected READ maxSelected WRITE setMaxSelected NOTIFY maxSelectedChanged) @@ -79,6 +85,7 @@ public: signals: void paymentAmountChanged(); void addressChanged(); + void correctAddressChanged(); void fiatIsMainChanged(); void fiatFollowsChanged(); void maxSelectedChanged(); @@ -87,6 +94,7 @@ signals: private: void checkValid(); + void createFormattedAddress(); qint64 m_paymentAmount = 0; int m_fiatAmount = 0; -- 2.54.0 From 78830c8aa4bc1908e904730122590f0c92a6e6b9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 19:20:46 +0200 Subject: [PATCH 0555/1428] Cleanup call to no longer existing function --- guis/desktop/SendTransactionPane.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index aa7cc09..1dc444f 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -419,10 +419,7 @@ Item { } Button { text: qsTr("Cancel") - onClicked: { - destination.text = "" - destination.updateColor() - } + onClicked: destination.text = "" } } } -- 2.54.0 From 8d2f056aacb2ae2888bc2055d665bc293cb521d6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 21:40:46 +0200 Subject: [PATCH 0556/1428] Detect cashtokens being sent to us. Instead of silently ignoring a cashtoken that is for us, this makes us match the incoming transaction and remember its a token. --- src/Wallet.cpp | 38 +++++++++++++++++++++++++++++++++++--- src/Wallet.h | 3 ++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 284e78b..3a7c363 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -178,7 +178,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co output.value = iter.longData(); } else if (iter.tag() == Tx::OutputScript) { - output.walletSecretId = findSecretFor(iter.byteData()); + output.walletSecretId = findSecretFor(iter.byteData(), output.holdsCashToken); if (output.walletSecretId > 0) { logDebug(LOG_WALLET) << " output"<< outputIndex << "pays to wallet id" << output.walletSecretId; wtx.outputs.insert(std::make_pair(outputIndex, output)); @@ -924,11 +924,43 @@ void Wallet::performUpgrades() } } -int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript) const +int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const { + auto theScript(outputScript); + isCashToken = false; + constexpr int MinTokenSize = /* prefix */ 1 + /* category */ + 32 + /* flags */ 1; + if (outputScript.size() > MinTokenSize + && static_cast(outputScript.begin()[0]) == 0xef) { + /* + * This output holds a cash-token. + * We eat the bytes that hold the cashtoken data and continue identifying the outputScript + */ + const uint8_t* tokenData = reinterpret_cast(outputScript.begin()); + uint8_t flags = tokenData[33]; + int skipAmount = MinTokenSize; + if ((flags & 0x40) == 0x40) // has-commitment-length bit is set. + skipAmount += tokenData[skipAmount++]; + if ((flags & 0x10) == 0x10) { // has-amount bit is set. + const uint8_t amount = tokenData[skipAmount++]; + if (amount == 253) // compact-size design. + skipAmount += 2; + else if (amount == 254) + skipAmount += 4; + else if (amount == 255) + skipAmount += 8; + } + + isCashToken = true; + if (outputScript.size() <= skipAmount) { + logWarning() << "Invalid token data"; + return -1; + } + theScript = outputScript.mid(skipAmount); + } + std::vector > vSolutions; Script::TxnOutType whichType; - if (!Script::solver(outputScript, whichType, vSolutions)) + if (!Script::solver(theScript, whichType, vSolutions)) return -1; KeyId keyID; diff --git a/src/Wallet.h b/src/Wallet.h index bea025d..0aad95a 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -412,7 +412,7 @@ private: * * @return the wallet-secret-id or -1 if none match. */ - int findSecretFor(const Streaming::ConstBuffer &outputScript) const; + int findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const; /// Fill the bloom filter with all the unspent transactions and addresses we handle. void rebuildBloom(); @@ -469,6 +469,7 @@ private: struct Output { int walletSecretId = -1; uint64_t value = 0; + bool holdsCashToken = false; }; struct WalletTransaction { -- 2.54.0 From a4fd05d43654230a008a195d04f04d0f55a43d29 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 22:12:57 +0200 Subject: [PATCH 0557/1428] Load save cashtoken bool --- src/Wallet.cpp | 5 +++++ src/Wallet_p.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 3a7c363..0c14222 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1885,6 +1885,9 @@ void Wallet::loadWallet() else if (parser.tag() == WalletPriv::OutputValue) { output.value = parser.longData(); } + else if (parser.tag() == WalletPriv::OutputHoldsToken) { + output.holdsCashToken = parser.boolData(); + } // an ouput can get locked, stopping the user from spending it. else if (parser.tag() == WalletPriv::OutputLockState) { @@ -2085,6 +2088,8 @@ void Wallet::saveWallet() for (auto i = item.second.outputs.begin(); i != item.second.outputs.end(); ++i) { builder.add(WalletPriv::OutputIndex, i->first); builder.add(WalletPriv::OutputValue, i->second.value); + if (i->second.holdsCashToken) + builder.add(WalletPriv::OutputHoldsToken, true); builder.add(WalletPriv::KeyStoreIndex, i->second.walletSecretId); // outputs that have been locked for some reason. diff --git a/src/Wallet_p.h b/src/Wallet_p.h index c110a8a..ab0b921 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -108,6 +108,7 @@ enum WalletDataSaveTags { OutputFromCoinbase, // bool KeyStoreIndex, // int that refers to the index of the privkey for the current tx-output. TxIsCashFusion, // bool + OutputHoldsToken, // bool }; enum OutputLockStateEnum { -- 2.54.0 From b6165a73083f49c34f63f6c7b5af841b167b30e6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 23:05:52 +0200 Subject: [PATCH 0558/1428] Remove not needed semicolon --- src/WalletHistoryModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 5c7d54d..0910395 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -36,7 +36,7 @@ class WalletHistoryModel : public QAbstractListModel * Freezing the model will stop the model from reporting new rows and un-freezing it aferwards * will cause all the new items that were found while frozen to be reported for UI update. */ - Q_PROPERTY(bool freezeModel READ isModelFrozen WRITE freezeModel NOTIFY freezeModelChanged); + Q_PROPERTY(bool freezeModel READ isModelFrozen WRITE freezeModel NOTIFY freezeModelChanged) public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); -- 2.54.0 From 4883de304bcf5221d16599e0ae60a7af83af6053 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 23:38:12 +0200 Subject: [PATCH 0559/1428] Export the cashToken bool to the UI Make clear that we have a cashtoken output in the UI. --- guis/desktop/WalletTransactionDetails.qml | 24 +++++++- guis/images/CashTokens.svg | 75 +++++++++++++++++++++++ guis/widgets.qrc | 1 + src/TransactionInfo.cpp | 10 +++ src/TransactionInfo.h | 5 ++ src/Wallet_support.cpp | 1 + 6 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 guis/images/CashTokens.svg diff --git a/guis/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml index d928c80..b4c6e50 100644 --- a/guis/desktop/WalletTransactionDetails.qml +++ b/guis/desktop/WalletTransactionDetails.qml @@ -172,7 +172,29 @@ GridLayout { delegate: Item { width: rightColumn.width - height: modelData === null ? 6 : (5 + outAddress.height) + height: { + if (modelData === null) + return 6; + + var h = 5 + outAddress.height; + if (modelData.hasCashToken) + h += 25; + return h; + } + + Image { + width: 20 + height: 20 + smooth: true + visible: modelData !== null && modelData.hasCashToken + source: visible ? "qrc:/CashTokens.svg" : "" + Label { + x: 30 + text: "A CashToken!" + anchors.verticalCenter: parent.verticalCenter + } + } + Rectangle { id: outArrowLine /* diff --git a/guis/images/CashTokens.svg b/guis/images/CashTokens.svg new file mode 100644 index 0000000..bbbec61 --- /dev/null +++ b/guis/images/CashTokens.svg @@ -0,0 +1,75 @@ + + + + diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 8e50a5e..9d62e55 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -3,6 +3,7 @@ images/edit-copy.svg images/edit-copy-light.svg images/internet.svg + images/CashTokens.svg images/Flowee-Symbols.otf Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 34fe59b..3958441 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -212,3 +212,13 @@ void TransactionOutputInfo::setCloakedAddress(const QString &ad) { m_cloakedAddress = ad; } + +bool TransactionOutputInfo::hasCashToken() const +{ + return m_hasCashToken; +} + +void TransactionOutputInfo::setHasCashToken(bool ct) +{ + m_hasCashToken = ct; +} diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 1413e84..a8578d0 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -56,6 +56,7 @@ class TransactionOutputInfo : public QObject Q_PROPERTY(qint64 value READ value CONSTANT) Q_PROPERTY(bool spent READ spent CONSTANT) Q_PROPERTY(bool forMe READ forMe CONSTANT) + Q_PROPERTY(bool hasCashToken READ hasCashToken CONSTANT) public: TransactionOutputInfo(QObject *parent) : QObject(parent) {} QString address() const; @@ -74,12 +75,16 @@ public: const QString &cloakedAddress() const; void setCloakedAddress(const QString &ad); + bool hasCashToken() const; + void setHasCashToken(bool ct); + private: QString m_address; QString m_cloakedAddress; qint64 m_value; bool m_spent = false; bool m_forMe = true; + bool m_hasCashToken = false; }; class TransactionInfo : public QObject diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 78cb2ac..02fa292 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -297,6 +297,7 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) out->setAddress(renderAddress(secret.address)); } out->setSpent(m_unspentOutputs.find(OutputRef(txIndex, o.first).encoded()) == m_unspentOutputs.end()); + out->setHasCashToken(o.second.holdsCashToken); if (secret.fromChangeChain) { assert(secret.fromHdWallet); out->setCloakedAddress(tr("Change #%1").arg(secret.hdDerivationIndex)); -- 2.54.0 From 37a4e38e5dca2cc412b6bc4e145b8e9fbec06233 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Jun 2023 23:38:52 +0200 Subject: [PATCH 0560/1428] Handle token UTXOs specially. Mostly they can not be spent as money, which is basically what this code does. --- src/WalletCoinsModel.cpp | 12 ++++++++++++ src/Wallet_spending.cpp | 14 +++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp index 7300c18..ccfd3df 100644 --- a/src/WalletCoinsModel.cpp +++ b/src/WalletCoinsModel.cpp @@ -243,6 +243,18 @@ void WalletCoinsModel::createMap() if (lockIter->second > 0) continue; } + + Wallet::OutputRef ref(i->first); + auto wtxIter = m_wallet->m_walletTransactions.find(ref.txIndex()); + assert(wtxIter != m_wallet->m_walletTransactions.end()); + const auto &wtx = wtxIter->second; + const auto outputIter = wtx.outputs.find(ref.outputIndex()); + assert(outputIter != wtx.outputs.end()); + if (outputIter->second.holdsCashToken) { + // CashToken holding UTXOs are hidden until we figure out how they can be handled in a future release. + continue; + } + m_rowsToOutputRefs.insert(std::make_pair(index++, i->first)); } } diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index 6fb6ba0..ed15cb3 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -141,7 +141,7 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz out.value = iter->second; out.outputRef = OutputRef(iter->first); auto wtxIter = m_walletTransactions.find(out.outputRef.txIndex()); - Q_ASSERT(wtxIter != m_walletTransactions.end()); + assert(wtxIter != m_walletTransactions.end()); const auto &wtx = wtxIter->second; int h = wtx.minedBlockHeight; @@ -163,11 +163,15 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz utxosBySize.insert(std::make_pair(iter->second, unspentOutputs.size())); if (wtx.isCashFusionTx) out.score += 50; - if (!m_singleAddressWallet) { - const auto outputIter = wtx.outputs.find(out.outputRef.outputIndex()); - assert(outputIter != wtx.outputs.end()); - out.walletSecretId = outputIter->second.walletSecretId; // TODO use + + const auto outputIter = wtx.outputs.find(out.outputRef.outputIndex()); + assert(outputIter != wtx.outputs.end()); + if (outputIter->second.holdsCashToken) { + // CashToken holding UTXOs can not be spent in normal payments + continue; } + if (!m_singleAddressWallet) + out.walletSecretId = outputIter->second.walletSecretId; unspentOutputs.push_back(out); } -- 2.54.0 From 49daf7b87ef5721a5c5b21f666abd240abaec7a2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Jun 2023 10:47:50 +0200 Subject: [PATCH 0561/1428] Also show cashtoken indicator on mobile --- guis/mobile/TxInfoSmall.qml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 9c8bfea..5d6bfe8 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -33,8 +33,6 @@ ColumnLayout { property QtObject infoObject: null property int minedHeight: model.height // local cache - spacing: 10 - QQC2.Label { property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' text: { @@ -56,7 +54,6 @@ ColumnLayout { GridLayout { columns: 2 - rowSpacing: 10 width: parent.width Flowee.Label { @@ -101,6 +98,29 @@ ColumnLayout { } } + Image { + sourceSize.width: 22 + sourceSize.height: 22 + smooth: true + visible: { + if (infoObject == null) + return false; + // visible if at least one output has a token. + var outputs = infoObject.outputs; + for (let o of outputs) { + if (o.hasCashToken) + return true; + } + return false; + } + source: visible ? "qrc:/CashTokens.svg" : "" + Flowee.Label { + x: 30 + text: qsTr("Holds a token") + anchors.verticalCenter: parent.verticalCenter + } + } + Flowee.Label { text: qsTr("Sent to") + ":" visible: receiverName.text !== "" @@ -146,6 +166,8 @@ ColumnLayout { return false; if (valueThenLabel.fiatPrice === 0) return false; + if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later + return false; return true; } text: qsTr("Value then") + ":" -- 2.54.0 From 961041cc117c3b3922f135946fac2bad08454e46 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Jun 2023 16:45:20 +0200 Subject: [PATCH 0562/1428] fix token check Outputs can be null, this is when the outputs are not send to an address we own. --- guis/mobile/TxInfoSmall.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 5d6bfe8..6f7b605 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -108,7 +108,7 @@ ColumnLayout { // visible if at least one output has a token. var outputs = infoObject.outputs; for (let o of outputs) { - if (o.hasCashToken) + if (o != null && o.hasCashToken) return true; } return false; -- 2.54.0 From 32f5e5cc95f930604428cf510ec3ad0c078b7021 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Jun 2023 18:10:50 +0200 Subject: [PATCH 0563/1428] UX (spacing) fixes This positions the Dialog more logically and avoids taking space for an empty label. --- guis/Flowee/Dialog.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index abbd986..cba59d7 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -49,7 +49,8 @@ QQC2.Popup { // 'mainWindow' is defined in main.qml var window = mainWindow.contentItem; var globalX = (window.width - root.width) / 2; - var globalY = window.height / 3 - root.height; + var unit = window.height / 3; + var globalY = unit - Math.min(unit - 10, root.height / 2); var local = parent.mapFromItem(null, globalX, globalY); x = local.x; y = local.y; @@ -92,6 +93,7 @@ QQC2.Popup { Label { id: mainTextLabel width: parent.width + visible: text !== "" wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Loader { -- 2.54.0 From c84af22bc0c7834a15af1074845001864803b8b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 Jun 2023 18:12:29 +0200 Subject: [PATCH 0564/1428] Add feedback on incorrect QR. The scanner doesn't validate the QR, but the Payment object does. So, verify the result after setting the url on the Payment object and raise a dialog when the address did not work. --- guis/mobile/PayWithQR.qml | 54 +++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 7c905f9..b9ecbb9 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -71,14 +71,20 @@ Page { autostart: true onFinished: { var rc = scanResult - if (rc === "") { // scanning failed + if (rc === "") { // scanning interrupted thePile.pop(); + return; } - else { - payment.targetAddress = rc - // should the price be included in the QR code, don't show editing widgets. - root.allowEditAmount = payment.paymentAmount <= 0; + // Take the entire QR-url and let the Payment object parse it. + // this updates things like amount, comment and indeed address. + payment.targetAddress = rc + if (payment.formattedTargetAddress == "") { + // that means that the address is invalid. + scannedUrlFaultyDialog.open(); } + + // should the price be included in the QR code, don't show editing widgets. + root.allowEditAmount = payment.paymentAmount <= 0; } } Payment { @@ -93,6 +99,44 @@ Page { // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" // userComment: "bla bla bla" } + Flowee.Dialog { + id: scannedUrlFaultyDialog + title: qsTr("Invalid QR code") + standardButtons: QQC2.DialogButtonBox.Close + onRejected: thePile.pop(); // remove this entire page + contentComponent: dialogForFaultyUrl + } + Component { + id: dialogForFaultyUrl + Column { + width: parent.width + spacing: 10 + Flowee.Label { + id: mainText + width: parent.width + text: qsTr("I don't understand the scanned code. I'm sorry, I can't start a payment.") + wrapMode: Text.Wrap + } + Flowee.Label { + text: qsTr("details") + font.italic: true + color: palette.link + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: detailsLabel.visible = true; + } + } + Flowee.Label { + id: detailsLabel + text: qsTr("Scanned text:

%1
").arg(scanner.scanResult); + visible: false + font.pixelSize: mainText.pixelSize * 0.8 + wrapMode: Text.Wrap + width: parent.width + } + } + } } PriceInputWidget { -- 2.54.0 From 2043f4cf5a3a38068b847f5510f2e74035385f67 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Jun 2023 22:27:48 +0200 Subject: [PATCH 0565/1428] Import translations from crowdIn --- translations/floweepay-common_nl.ts | 31 +- translations/floweepay-mobile_nl.ts | 424 ++++++++++++++++++++++------ 2 files changed, 360 insertions(+), 95 deletions(-) diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 540a86e..c7976ef 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,12 +4,17 @@ AccountInfo - + + Offline + Offline + + + Wallet: Up to date Portemonnee: gesynchroniseerd - + Behind: %1 weeks, %2 days counter on weeks @@ -18,7 +23,7 @@ - + Behind: %1 days %1 dag oude data @@ -26,17 +31,17 @@ - + Up to date Is volledig bijgewerkt - + Updating Aan het bijwerken - + Still %1 hours behind Nog één uur @@ -65,7 +70,7 @@ AddressInfoWidget - + self payment to self zelf @@ -246,22 +251,22 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. @@ -278,13 +283,13 @@ Wallet - + Change #%1 Wisselmunt #%1 - + Main #%1 Standaard#%1 diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 3b32f6c..244f665 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -4,34 +4,43 @@ About + About Over Ons + Help translate this app Help deze app te vertalen + License Licentie + + Credits Met dank aan + © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander en bijdragers + Project Home Startpagina project + With git repository and issues tracker Met git data en takenlijst + Telegram Telegram @@ -39,38 +48,47 @@ AccountHistory + Home Start + Miner Reward Mijnwerker Beloning + Cash Fusion Cash Fusion + Received Ontvangen + Moved Zelf-betaling + Sent Verzonden + Sending Verzenden + Seen Gezien + Rejected Geweigerd @@ -78,88 +96,109 @@ AccountPageListItem + Sync Status Synchronisatie status + Backup information Back-up Informatie + Backup Details Back-up gegevens + Wallet seed-phrase Herstelzin opslaan + Derivation Path Derivatie pad + Starting Height height refers to block-height Beginhoogte + + Name + Naam + + + + Archived wallets do not check for activities. Balance may be out of date + Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd + + + + xpub + xpub + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! + Wallet keys Sleutels van portemonnee + Show Index toggle to show numbers Toon Index + Change Addresses Wisselgeldadres + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. + Used Addresses Gebruikte adressen + Switches between still in use addresses and formerly used, new empty, addresses Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen - Name - Naam - - - Archived wallets do not check for activities. Balance may be out of date - Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd - - - xpub - xpub - - + Addresses and keys Adressen en sleutels + Hide in private mode Verbergen in privémodus + Unarchive Wallet Portemonnee De-archiveren + Archive Wallet Portemonnee Archiveren @@ -167,10 +206,12 @@ AccountSelectorPopup + Your Wallets Uw portemonnees + last active Laatst actief @@ -178,6 +219,7 @@ AccountSyncState + Status: Offline Status: offline @@ -185,38 +227,47 @@ AccountsList + Wallet Portemonnee + Wallets Portemonnees + Add Wallet Portemonnee toevoegen + Default Wallet Standaard portemonnee + %1 is used on startup %1 wordt gebruikt bij start + Exit Private Mode Verlaat privémodus + Enter Private Mode Start privémodus + Reveals wallets marked private Onthult portefeuilles gemarkeerd als privé + Hides wallets marked private Verbergt portemonnees gemarkeerd als privé @@ -224,6 +275,7 @@ CurrencySelector + Select Currency Selecteer valuta @@ -231,26 +283,32 @@ GuiSettings + Display Settings Scherminstellingen + Font sizing Lettertypegrootte + Unit Eenheid + Change Currency (%1) Verander valuta (%1) + Main View Hoofd overzicht + Show Bitcoin Cash value Toon Bitcoin Cash waarde @@ -258,60 +316,73 @@ ImportWalletPage + Import Wallet Portemonnee importeren + Create Creëer + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. + Secret The seed-phrase or private key Geheim + Private key description of type Privé-sleutel + BIP 39 seed-phrase description of type BIP 39 herstelzin + Unrecognized word Word from the seed-phrases lexicon Niet herkend woord + Name Naam + Force Single Address Forceer één adres + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. + Alternate phrase Alternatieve zin + Oldest Transaction Oudste transactie + Derivation Derivatie @@ -319,22 +390,27 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. InstaPayConfigButton + Enable Instant Pay Direct Betalen inschakelen + Configure Instant Pay Configureer Direct Betalen + Fast payments for low amounts Direct betalen bij lage bedragen + Not configured Niet geconfigureerd + Limit set to: %1 Limiet ingesteld op: %1 @@ -342,22 +418,27 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. InstaPayConfigPage + Instant Pay Direct Betalen + Requests for payment can be approved automatically using Instant Pay. Betalingsverzoeken worden automatisch goedgekeurd met behulp van Direct Betalen. + Protected wallets can not be used for InstaPay because they require a PIN before usage Beveiligde portemonnees kunnen niet worden gebruikt voor Direct Betalen ze een PIN vereisen voor gebruik + Enable Instant Pay for this wallet Actief maken voor portemonnee + Maximum Amount Maximum Bedrag @@ -365,6 +446,7 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MenuOverlay + Add Wallet Portemonnee toevoegen @@ -372,35 +454,43 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. NetView + Peers Peers + Address network address (IP) Adres + Start-height: %1 Beginhoogte: %1 + ban-score: %1 ban-score: %1 + initializing connection verbinding initialiseren + Verifying peer Peer verifiëren + Peer for wallet: %1 Peer voor portemonnee: %1 + Peer for wallet Peer voor portemonnee @@ -408,215 +498,269 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. NewAccount + New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee + Create a New Wallet Maak een nieuwe portemonnee + Basic Eenvoudig + Private keys based Property of a wallet Privé sleutels gebaseerd + Difficult to backup Context: wallet type Back-up moeilijk + Great for brief usage Context: wallet type Ideaal voor kort gebruik + HD wallet HD portemonnee + Seed-phrase based Context: wallet type Herstelzin gebaseerd + Easy to backup Context: wallet type Back-upt makkelijk + Most compatible The most compatible wallet type Meest compatibel + Import Existing Wallet Importeer bestaande portemonnee + Import Importeer + Imports seed-phrase Importeert herstelzin + Imports private key Importeert Privésleutel + New Wallet Nieuwe Portemonnee + + Create Creëer + This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden + + Name Naam + Force Single Address Forceer één adres + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + + This creates a new wallet that can be backed up with a seed-phrase + Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin + + + Derivation Derivatie + New HD-Wallet Nieuwe Portemonnee - - This creates a new wallet that can be backed up with a seed-phrase - Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin - PayToOthers + Build Transaction Bouw Transactie + Building Error error during build Fout bij bouwen + Add Payment Detail page title Voeg betalingsgegevens toe + + Add Destination Voeg bestemming toe + an address to send money to een adres om geld naar te sturen + Confirm Sending confirm we want to send the transaction Verzenden bevestigen + TXID TXID + Copy transaction-ID Transactie-ID kopiëren + Fee Transactiekosten + Transaction size Transactie grootte + %1 bytes %1 bytes + Fee per byte Transactiekosten per byte + %1 sat/byte fee %1 sats/byte + Destination Bestemming + unset indication of empty niet ingesteld + + invalid + address is not correct + ongeldig + + + + Copy Address Kopieer adres - Amount - Bedrag - - + Edit Destination Bewerk Bestemming + Send All all money in wallet Alles verzenden + Bitcoin Cash Address Bitcoin Cash-adres + Warning Waarschuwing + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? + I am certain Ik ben zeker + Drag to Edit Sleep om te bewerken + Drag to Delete Sleep om te verwijderen + Prepare Payment... Betaling voorbereiden... @@ -624,37 +768,81 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PayWithQR + Approve Payment Betaling goedkeuren + Send All all money in wallet Alles verzenden + + Show Address + to show a bitcoincash address + Toon adres + + + + Edit Amount + Edit amount of money to send + Bedrag aanpassen + + + + Invalid QR code + Ongeldige QR-code + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Gelezen tekst: <pre>%1</pre> + + + Payment description Omschrijving betaling + + + Destination Address + Bestemmingsadres + PriceDetails + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 + 7d 7 days 7d + 1m 1 month 1m + 3m 3 months 3m @@ -663,6 +851,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PriceInputWidget + All Currencies Alle valuta's @@ -670,91 +859,114 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt ReceiveTab + Receive Ontvangen + Share this QR to receive Betaler moet deze QR lezen - Encrypted Wallet - Versleutelde portemonnee - - - Import Running... - Bezig met importeren... - - - Description - Omschrijving - - - Clear - Wissen - - - Payment Seen - Betaling gezien - - - Checking... - Controleren... - - + Transaction high risk Transactie met hoog risico - Partially Paid - Deels betaald + + + Payment Seen + Betaling gezien - Payment Accepted - Betaling geaccepteerd + + Encrypted Wallet + Versleutelde portemonnee - Payment Settled - Betaling Afgewikkeld - - - Instant payment failed. Wait for confirmation. (double spent proof received) - Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - - - Continue - Doorgaan + + Import Running... + Bezig met importeren... + Amount requested amount of coin Bedrag + Address Bitcoin Cash address Adres + + + Checking... + Controleren... + + + + Partially Paid + Deels betaald + + + + Payment Accepted + Betaling geaccepteerd + + + + Payment Settled + Betaling Afgewikkeld + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) + + + + Continue + Doorgaan + + + + + Description + Omschrijving + + + + Clear + Wissen + SelectDefaultAccountPage + Select Wallet Selecteer portemonnee + Pick which wallet will be selected on starting Flowee Pay Kies welke portefeuille zal worden geselecteerd bij het starten van Flowee Pay + No InstaPay limit set Geen Direct betalen limiet ingesteld + InstaPay till %1 Direct Betalen tot %1 + InstaPay is turned off Direct Betalen staat uit @@ -762,14 +974,17 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt SendTransactionsTab + Send Versturen + Scan a QR code Scan QR-code + Build transaction Bouw transactie @@ -777,6 +992,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt SlideToApprove + SLIDE TO SEND SWIPE VOOR VERZENDEN @@ -784,57 +1000,65 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt StartupScreen + Welcome! Welkom! + Continue Doorgaan + Moving the world towards a Bitcoin Cash economy We lopen het pad naar een Bitcoin Cash economie - OR - OF - - + Scan me to send funds to your HD wallet Scan mij om geld naar uw HD-portemonnee te sturen + Add a different wallet Een andere portemonnee toevoegen + + + OR + OF + TransactionDetails + Transaction Details Transactiedetails - Rejected - Afgewezen - - - Unconfirmed - Onbevestigd - - - Coinbase - Coinbase - - + Transaction Hash Transactie hash + + Rejected + Afgewezen + + + + Unconfirmed + Onbevestigd + + + Mined at Gedolven in + %1 blocks ago Meest recente blok @@ -842,69 +1066,91 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + Transaction comment Transactie omschrijving + + Size: %1 bytes + Grootte: %1 bytes + + + Is a CashFusion transaction. CashFusion transactie. + Fees paid Betaalde kosten + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes + Fused from my addresses Mijn gefuseerde adressen + Sent from my addresses Verzonden vanaf mijn adressen + Sent from addresses Verzonden vanaf adressen + + Copy Address Kopieer adres + Fused into my addresses Gefuseerd naar mijn adressen + Received at addresses Ontvangen op adressen + Received at my addresses Ontvangen op mijn adressen - Size: %1 bytes - Grootte: %1 bytes + + Coinbase + Coinbase TxInfoSmall + Transaction is rejected Transactie geweigerd + Processing In behandeling + Mined Gedolven + %1 blocks ago Confirmations @@ -913,40 +1159,54 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + Miner Reward Mijnwerker Beloning + Cash Fusion Cash Fusion + Received Ontvangen - Payment to self - Betaling aan uzelf - - + Sent Verzonden + Value then Waarde toen + Value now Waarde nu - Transaction Details - Transactiedetails + + Payment to self + Betaling aan uzelf + + Holds a token + Heeft een token + + + Sent to Verstuurd naar + + + Transaction Details + Transactiedetails + -- 2.54.0 From 1d2fa6728f00138b1ed4e357fb5e55f91044425b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 8 Jun 2023 23:20:53 +0200 Subject: [PATCH 0566/1428] new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d07461a..b31246e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="11" android:versionName="2023.06.0"> diff --git a/src/main.cpp b/src/main.cpp index 85e107f..91fca5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.05.2"); + qapp.setApplicationVersion("2023.06.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From efa39e033726dc99dc779f5ff4728f5214d92e3a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Jun 2023 22:13:59 +0200 Subject: [PATCH 0567/1428] Improve QRcode images based on feedback from A@ron --- guis/mobile/images/qr-code-light.svg | 24 +++++++++++++++--------- guis/mobile/images/qr-code.svg | 23 ++++++++++++++--------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/guis/mobile/images/qr-code-light.svg b/guis/mobile/images/qr-code-light.svg index 4445f99..88be44a 100644 --- a/guis/mobile/images/qr-code-light.svg +++ b/guis/mobile/images/qr-code-light.svg @@ -1,11 +1,17 @@ - - - - - - - - - + + + + + + + + + + + diff --git a/guis/mobile/images/qr-code.svg b/guis/mobile/images/qr-code.svg index 01b7dc7..4783779 100644 --- a/guis/mobile/images/qr-code.svg +++ b/guis/mobile/images/qr-code.svg @@ -1,11 +1,16 @@ - - - - - - - - - + + + + + + + + + + -- 2.54.0 From ecc5d0397547564bb2f7228ed47b19d372e886cd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Jun 2023 22:41:53 +0200 Subject: [PATCH 0568/1428] UX improvements to prioritize normal usage. When looking at the real usage of flowee pay, we aim for this to be used 90% (or more) of the time as a better wallet. Wallet in the physical sense. This implies that the vast majority of usage is going to be about paying or receiving. A small number of times you'll use it to look up the history of payments. As such, its Ok to downgrade the account history and make the main usage more prominent and easy to find. --- guis/Flowee/ImageButton.qml | 23 +++++++++++++++++------ guis/mobile/AccountHistory.qml | 20 ++++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index d953ea7..dde45b3 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,14 @@ import QtQuick import QtQuick.Controls as QQC2 QQC2.Control { - width: 42 - height: width + implicitWidth: iconSize + 8 + implicitHeight: iconSize + 8 + (label.visible ? label.height + 6 : 0) signal clicked; property alias source: imageIcon.source; property alias responseText: comment.text; - property int iconSize: width + property int iconSize: 42 + property alias text: label.text Rectangle { id: highlight @@ -38,10 +39,20 @@ QQC2.Control { Image { id: imageIcon width: Math.min(parent.width - 8, iconSize) - height: Math.min(parent.height - 8, iconSize) - anchors.centerIn: parent + height: width + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 4 smooth: true } + Label { + id: label + width: parent.width + visible: text !== "" + horizontalAlignment: Text.AlignHCenter + anchors.top: imageIcon.bottom + anchors.topMargin: 4 + } MouseArea { id: mouseArea anchors.fill: parent diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index d7c6026..dae4754 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -139,34 +139,30 @@ ListView { width: parent.width } + Item { width: 10; height: 25 } // spacer + Row { - height: startScan.height + height: startScan.height + 20 x: (parent.width - width) / 2 - spacing: 16 + spacing: 25 Flowee.ImageButton { id: startScan source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - width: 60 onClicked: thePile.push("PayWithQR.qml") - iconSize: 40 - height: 60 + iconSize: 70 + text: qsTr("Pay") } Flowee.ImageButton { - width: 60 - height: startScan.height - iconSize: 50 + iconSize: 70 source: "qrc:/receive.svg" onClicked: switchToTab(2) // receive tab + text: qsTr("Receive") } } /* TODO - "Is archive" / "Unrchive"" - Is Encryopted / Decrypt */ - - Item { width: 10; height: 15 } // spacer } } -- 2.54.0 From 94f52e0c7ff2ef2f07004432d252919eb63e7bd2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Jun 2023 17:52:54 +0200 Subject: [PATCH 0569/1428] Add feature; paste address. Allow users that start by 'pay' to fulfill the payment also using clipboard instead by providing a 'paste' button on the scanning screen. --- guis/mobile.qrc | 2 + guis/mobile/QRScannerOverlay.qml | 44 ++++++++++++++++++++- guis/mobile/images/edit-clipboard-light.svg | 5 +++ guis/mobile/images/edit-clipboard.svg | 4 ++ src/CameraController.cpp | 29 ++++++++++++++ src/CameraController.h | 9 +++++ src/QRScanner.cpp | 13 +++--- src/QRScanner.h | 2 +- 8 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 guis/mobile/images/edit-clipboard-light.svg create mode 100644 guis/mobile/images/edit-clipboard.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index a1fad5a..ad99785 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -34,6 +34,8 @@ mobile/images/tx-coinbase-light.svg mobile/images/tx-move.svg mobile/images/tx-move-light.svg + mobile/images/edit-clipboard.svg + mobile/images/edit-clipboard-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 6da076d..e3dc9da 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -115,6 +115,47 @@ FocusScope { onClicked: CameraController.abort(); } + + Rectangle { + x: 50 + anchors.bottom: parent.bottom + anchors.bottomMargin: 50 + visible: CameraController.supportsPaste + width: pasteButton.width + height: pasteButton.height + color: palette.base + Flowee.ImageButton { + id: pasteButton + source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + text: qsTr("Paste") + onClicked: pasteFeedback.visible = !CameraController.importScanFromClipboard(); + } + + Rectangle { + id: pasteFeedback + color: palette.toolTipBase + border.color: palette.toolTipText + border.width: 2 + width: errorLabel.width + 10 + height: errorLabel.height + 10 + radius: 5 + anchors.top : pasteButton.bottom + visible: false + + Flowee.Label { + id: errorLabel + anchors.centerIn: parent + text: qsTr("Failed") + color: palette.toolTipText + } + Timer { + interval: 4000 + running: parent.visible + onTriggered: parent.visible = false + } + } + } + Component { id: videoFeedPanel Item { @@ -145,7 +186,8 @@ FocusScope { } VideoOutput { id: videoOutput - fillMode: VideoOutput.Stretch + fillMode: VideoOutput.PreserveAspectCrop + orientation: 90 width: parent.width height: parent.height } diff --git a/guis/mobile/images/edit-clipboard-light.svg b/guis/mobile/images/edit-clipboard-light.svg new file mode 100644 index 0000000..516328e --- /dev/null +++ b/guis/mobile/images/edit-clipboard-light.svg @@ -0,0 +1,5 @@ + + + + diff --git a/guis/mobile/images/edit-clipboard.svg b/guis/mobile/images/edit-clipboard.svg new file mode 100644 index 0000000..b76fde8 --- /dev/null +++ b/guis/mobile/images/edit-clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/CameraController.cpp b/src/CameraController.cpp index b5a6e05..6e2dfde 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -18,6 +18,7 @@ */ #include "CameraController.h" #include "QRScanner.h" +#include "qclipboard.h" #include @@ -523,6 +524,34 @@ void CameraController::abort() abortRequest(d->scanRequest); } +bool CameraController::supportsPaste() const +{ + if (d->scanRequest == nullptr) + return false; + // + return d->scanRequest->scanType() == QRScanner::PaymentDetails; +} + +bool CameraController::importScanFromClipboard() +{ + if (d->scanRequest == nullptr) + return false; + if (d->scanRequest->scanType() != QRScanner::PaymentDetails) + return false; + + auto text = QGuiApplication::clipboard()->text(); + auto index = text.indexOf("bitcoincash:"); + if (index >= 0) { + auto end = text.indexOf(' ', index + 10); + d->scanRequest->setScanResult(text.mid(index, end)); + // stop camera + d->cameraStarted = false; + emit cameraActiveChanged(); + return true; + } + return false; +} + void CameraController::setCamera(QObject *object) { if (object == d->camera) diff --git a/src/CameraController.h b/src/CameraController.h index e09d0d4..91a8bbc 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -42,6 +42,8 @@ class CameraController : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) + /// return true if calling importScanFromClipboard can work with current QRScanner::ScanType + Q_PROPERTY(bool supportsPaste READ supportsPaste NOTIFY visibleChanged) Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) Q_PROPERTY(QObject* camera READ camera WRITE setCamera NOTIFY cameraChanged) @@ -53,6 +55,11 @@ public: void abortRequest(QRScanner *request); Q_INVOKABLE void abort(); + /** + * Try to complete the current scan request by instead looking at the clipboard + * for some data that could be used to fulfill the scan. + */ + Q_INVOKABLE bool importScanFromClipboard(); void setCamera(QObject *object); QObject *camera() const; @@ -64,6 +71,8 @@ public: bool cameraActive() const; bool visible() const; + bool supportsPaste() const; + signals: void cameraChanged(); void videoSinkChanged(); diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index f706c52..b42b330 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -75,18 +75,21 @@ QString QRScanner::scanResult() const return m_scanResult; } -void QRScanner::setScanResult(const QString &newScanResult) +void QRScanner::setScanResult(const QString &result) { - if (m_scanResult == newScanResult) + if (m_scanResult == result || result.isEmpty()) return; - m_scanResult = newScanResult; + m_scanResult = result; emit scanResultChanged(); setIsScanning(false); } void QRScanner::resetScanResult() { - setScanResult({}); + if (!m_scanResult.isEmpty()) { + m_scanResult.clear(); + emit scanResultChanged(); + } } bool QRScanner::autostart() const diff --git a/src/QRScanner.h b/src/QRScanner.h index 4b89b8f..64e01f5 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -51,7 +51,7 @@ public: void finishedScan(const QString &resultString); QString scanResult() const; - void setScanResult(const QString &newScanResult); + void setScanResult(const QString &result); void resetScanResult(); bool autostart() const; -- 2.54.0 From 973fc9495087e86d2564720727cdf8ed54ca2f05 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Jun 2023 18:10:06 +0200 Subject: [PATCH 0570/1428] when paste is used turn instaPay off clipboards based addresses are a lot less secure, physically, than scanning a QR. So the user really should validate the payment and thus we turn off instaPay in those cases. --- guis/mobile/PayWithQR.qml | 5 +++++ src/CameraController.cpp | 2 +- src/QRScanner.cpp | 8 +++++++- src/QRScanner.h | 12 +++++++++++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index b9ecbb9..99295eb 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -75,6 +75,11 @@ Page { thePile.pop(); return; } + // if the scanner got bypassed with a 'paste' instead then + // we don't allow instpay + if (resultSource === QRScanner.Clipboard) + payment.instaPay = false; + // Take the entire QR-url and let the Payment object parse it. // this updates things like amount, comment and indeed address. payment.targetAddress = rc diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 6e2dfde..5a5f497 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -543,7 +543,7 @@ bool CameraController::importScanFromClipboard() auto index = text.indexOf("bitcoincash:"); if (index >= 0) { auto end = text.indexOf(' ', index + 10); - d->scanRequest->setScanResult(text.mid(index, end)); + d->scanRequest->setScanResult(text.mid(index, end), QRScanner::Clipboard); // stop camera d->cameraStarted = false; emit cameraActiveChanged(); diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index b42b330..c3f0d2e 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -75,11 +75,12 @@ QString QRScanner::scanResult() const return m_scanResult; } -void QRScanner::setScanResult(const QString &result) +void QRScanner::setScanResult(const QString &result, ResultSource source) { if (m_scanResult == result || result.isEmpty()) return; m_scanResult = result; + m_resultSource = source; emit scanResultChanged(); setIsScanning(false); } @@ -123,3 +124,8 @@ void QRScanner::completed() if (m_autostart) start(); } + +QRScanner::ResultSource QRScanner::resultSource() const +{ + return m_resultSource; +} diff --git a/src/QRScanner.h b/src/QRScanner.h index 64e01f5..1f7dda9 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -29,6 +29,7 @@ class QRScanner : public QObject Q_PROPERTY(QString scanResult READ scanResult WRITE setScanResult RESET resetScanResult NOTIFY scanResultChanged) Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) + Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) public: explicit QRScanner(QObject *parent = nullptr); ~QRScanner(); @@ -50,8 +51,14 @@ public: /// Notice that resultString may be empty if we didn't scan any valid QR void finishedScan(const QString &resultString); + enum ResultSource { + Camera, + Clipboard + }; + Q_ENUM(ResultSource) + QString scanResult() const; - void setScanResult(const QString &result); + void setScanResult(const QString &result, ResultSource source = Camera); void resetScanResult(); bool autostart() const; @@ -60,6 +67,8 @@ public: bool isScanning() const; void setIsScanning(bool now); + ResultSource resultSource() const; + private slots: void completed(); @@ -72,6 +81,7 @@ signals: private: ScanType m_scanType; + ResultSource m_resultSource = Camera; QString m_scanResult; bool m_autostart = false; bool m_isScanning = false; -- 2.54.0 From f904e5d413b6255309716fcb065d22182d247817 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Jun 2023 18:10:39 +0200 Subject: [PATCH 0571/1428] Make the scanner overlay eat mouse events. This stops clicks from acting on the underlying UI. --- guis/mobile/QRScannerOverlay.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index e3dc9da..3ea4d09 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -38,6 +38,10 @@ FocusScope { anchors.fill: parent color: palette.window } + MouseArea { + anchors.fill: parent + // eat all clicks + } // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the // feature is actually requisted by the user. -- 2.54.0 From 717eafcb1ee99d2ce2fd248dad69cc833736535c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Jun 2023 18:20:23 +0200 Subject: [PATCH 0572/1428] remove commented out code --- guis/mobile/SendTransactionsTab.qml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index d04a9d6..e57bcf7 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -40,12 +40,5 @@ FocusScope { showPageIcon: true onClicked: thePile.push("PayToOthers.qml") } - /* - TextButton { - text: qsTr("My Wallets") - subtext: qsTr("Move between wallets") - showPageIcon: true - onClicked: thePile.push("MoveBetweeWallets.qml") - } */ } } -- 2.54.0 From 1220a3839cfa73388be49c4e56885e95fe2cd160 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Jun 2023 19:06:16 +0200 Subject: [PATCH 0573/1428] Start a setup for modules. This sets up a basic system for creating modules on top of the flowee pay static lib in a way that is ensured to be isolated (modules can't accidentally use each other's classes) The setup is made such that the buildsystem does the hard work on plugging in a new module, making it so that all you need to do is create a new dir and a "{something}ModuleInfo.h" file and it will get compiled in. The point there is to make it not have any merge conflicts and just make it dead easy to get started. --- CMakeLists.txt | 28 ++++++++- modules/CMakeLists.txt | 57 +++++++++++++++++++ .../BuildTransactionModuleInfo.cpp | 7 +++ .../BuildTransactionModuleInfo.h | 8 +++ modules/build-transaction/CMakeLists.txt | 24 ++++++++ modules/example/CMakeLists.txt | 24 ++++++++ modules/example/ExampleModuleInfo.cpp | 9 +++ modules/example/ExampleModuleInfo.h | 8 +++ src/CMakeLists.txt | 6 +- src/FloweePay.cpp | 12 +++- src/FloweePay.h | 3 + src/ModuleInfo.cpp | 16 ++++++ src/ModuleInfo.h | 26 +++++++++ src/ModuleManager.cpp | 25 ++++++++ src/ModuleManager.h | 23 ++++++++ src/ModuleManager_empty.cpp | 1 + src/ModuleSection.cpp | 6 ++ src/ModuleSection.h | 24 ++++++++ src/main.cpp | 2 + testing/priceHistory/CMakeLists.txt | 1 + testing/value/CMakeLists.txt | 2 +- testing/wallet/CMakeLists.txt | 1 + testing/walletHistoryModel/CMakeLists.txt | 1 + 23 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 modules/CMakeLists.txt create mode 100644 modules/build-transaction/BuildTransactionModuleInfo.cpp create mode 100644 modules/build-transaction/BuildTransactionModuleInfo.h create mode 100644 modules/build-transaction/CMakeLists.txt create mode 100644 modules/example/CMakeLists.txt create mode 100644 modules/example/ExampleModuleInfo.cpp create mode 100644 modules/example/ExampleModuleInfo.h create mode 100644 src/ModuleInfo.cpp create mode 100644 src/ModuleInfo.h create mode 100644 src/ModuleManager.cpp create mode 100644 src/ModuleManager.h create mode 100644 src/ModuleManager_empty.cpp create mode 100644 src/ModuleSection.cpp create mode 100644 src/ModuleSection.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f316775..f5f68f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,7 @@ if (BUILD_DESKTOP_PAY) set (SOURCES_PAY src/main.cpp guis/desktop/qml_path_helper.cpp + src/ModuleManager_empty.cpp # because we don't have modules in the desktop one ) qt6_add_resources(SOURCES_PAY guis/desktop.qrc @@ -147,6 +148,26 @@ if (NOT "${Qt6Multimedia_FOUND}" OR NOT "${ZXing_FOUND}") set (BUILD_MOBILE_PAY OFF) endif () +set (PAY_MOBILE_LIBS "") +if (BUILD_MOBILE_PAY) + add_subdirectory(modules) + + # find all modules present in the source-tree and make sure we link them into the + # flowee-pay-mobile executable. + # Notice that the 'modules' subdir has similar code to actually compile these libs. + file(GLOB _module_sub_directories ${CMAKE_CURRENT_SOURCE_DIR}/modules/*) + foreach (child ${_module_sub_directories}) + if (IS_DIRECTORY ${child} AND NOT IS_SYMLINK ${child}) + file(GLOB subModule ${child}/*ModuleInfo.h) + file(RELATIVE_PATH moduleName ${CMAKE_CURRENT_SOURCE_DIR}/modules ${child}) + list(APPEND PAY_MOBILE_LIBS "${moduleName}_module_lib") + endif() + endforeach() + + include_directories(${CMAKE_SOURCE_DIR}/modules) +endif() + + if (ANDROID AND BUILD_MOBILE_PAY) # blockheaders to be included in the APK set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) @@ -172,6 +193,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) src/CameraController.cpp src/QRScanner.cpp guis/mobile/qml_path_helper.cpp + ${CMAKE_BINARY_DIR}/modules/modules-load.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE guis/mobile.qrc @@ -187,7 +209,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis if (${Qt6Multimedia_FOUND} AND ${QT_VERSION_MINOR} GREATER_EQUAL 2 AND ${QT_VERSION_MINOR} LESS_EQUAL 4) - set (PAY_MOBILE_LIBS Qt6::CorePrivate) + LIST (APPEND PAY_MOBILE_LIBS Qt6::CorePrivate) message(STATUS "including private QtCore APIs for Android support") endif () target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) @@ -210,6 +232,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) src/main.cpp src/CameraController.cpp src/QRScanner.cpp + ${CMAKE_BINARY_DIR}/modules/modules-load.cpp guis/mobile/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -221,7 +244,8 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) set_target_properties(pay_mobile PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia) + + target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) install(TARGETS pay_mobile DESTINATION bin) endif() diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt new file mode 100644 index 0000000..425fac0 --- /dev/null +++ b/modules/CMakeLists.txt @@ -0,0 +1,57 @@ +# This file is part of the Flowee project +# Copyright (C) 2023 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +#add_subdirectory(example) +#add_subdirectory(build-transaction) + +# Find all modules to auto-invoke and write them to a file. +message("") +message("Modules") +message("=======") +set(INCLUDES_LIST "") +set(CLASSES_LIST "") +file(GLOB sub_directories ${CMAKE_CURRENT_SOURCE_DIR}/*) +foreach (child ${sub_directories}) + if (IS_DIRECTORY ${child}) + file(GLOB subModule ${child}/*ModuleInfo.h) + if (EXISTS ${subModule}) + add_subdirectory(${child}) + file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) + message("-- Including module '${module}'") + get_filename_component(className ${subModule} NAME_WE) + message(" ${className}") + list (APPEND CLASSES_LIST ${className}) + + file (RELATIVE_PATH include_file ${CMAKE_CURRENT_SOURCE_DIR} ${subModule}) + list (APPEND INCLUDES_LIST ${include_file}) + endif() + endif() +endforeach() + +# After compiling them, generate a little cpp file that actually loads those modules +# into the ModulesManager +set (MODULES_LOAD_CPP "#include \n") +foreach (include ${INCLUDES_LIST}) + set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP}#include <${include}>\n") +endforeach() + +set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP}\nvoid load_all_modules() {\n auto *m=FloweePay::instance()->modules();\n") +foreach (className ${CLASSES_LIST}) + set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP} ${className}::init(m);\n") +endforeach() + +# finally, write out the cpp file. +file (WRITE ${CMAKE_CURRENT_BINARY_DIR}/modules-load.cpp "${MODULES_LOAD_CPP}}") diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp new file mode 100644 index 0000000..6d6ee83 --- /dev/null +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -0,0 +1,7 @@ +#include "BuildTransactionModuleInfo.h" + + +void BuildTransactionModuleInfo::init(ModuleManager *manager) +{ + // TODO +} diff --git a/modules/build-transaction/BuildTransactionModuleInfo.h b/modules/build-transaction/BuildTransactionModuleInfo.h new file mode 100644 index 0000000..604540a --- /dev/null +++ b/modules/build-transaction/BuildTransactionModuleInfo.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace BuildTransactionModuleInfo +{ + void init(ModuleManager *manager); +} diff --git a/modules/build-transaction/CMakeLists.txt b/modules/build-transaction/CMakeLists.txt new file mode 100644 index 0000000..b41761b --- /dev/null +++ b/modules/build-transaction/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2023 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(build-transaction_module) + +set (SOURCES + BuildTransactionModuleInfo.cpp +) +add_library (build-transaction_module_lib STATIC ${SOURCES}) +target_link_libraries(build-transaction_module_lib pay_lib) + diff --git a/modules/example/CMakeLists.txt b/modules/example/CMakeLists.txt new file mode 100644 index 0000000..5566e44 --- /dev/null +++ b/modules/example/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2023 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(example_module) + +set (SOURCES + ExampleModuleInfo.cpp +) +add_library (example_module_lib STATIC ${SOURCES}) +target_link_libraries(example_module_lib pay_lib) + diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp new file mode 100644 index 0000000..9878230 --- /dev/null +++ b/modules/example/ExampleModuleInfo.cpp @@ -0,0 +1,9 @@ +#include "ExampleModuleInfo.h" + +void ExampleModuleInfo::init(ModuleManager *manager) +{ + ModuleInfo *info = new ModuleInfo(manager); + info->setId("exampleModule"); + + manager->registerModule(info); +} diff --git a/modules/example/ExampleModuleInfo.h b/modules/example/ExampleModuleInfo.h new file mode 100644 index 0000000..e441b7f --- /dev/null +++ b/modules/example/ExampleModuleInfo.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace ExampleModuleInfo +{ + void init(ModuleManager *manager); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8278d3..4e46bfc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -52,6 +52,11 @@ set (PAY_SOURCES Wallet_support.cpp Wallet_spending.cpp WalletEnums.cpp + + # Modules support + ModuleInfo.cpp + ModuleManager.cpp + ModuleSection.cpp ) if (NetworkLogClient) @@ -73,4 +78,3 @@ target_link_libraries(pay_lib ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ${QREncode_LIBRARIES}) - diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 85d1929..c82187f 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -20,7 +20,7 @@ #include "NewWalletConfig.h" #include "AddressInfo.h" #include "PriceDataProvider.h" -#include "AccountConfig.h" +#include "ModuleManager.h" #include #include @@ -887,6 +887,16 @@ PriceDataProvider *FloweePay::prices() const return m_prices.get(); } +ModuleManager *FloweePay::modules() +{ + if (m_modulesManager.get() == nullptr) { + m_modulesManager.reset(new ModuleManager(this)); + // actually load the available modules: + m_modulesManager->init(); + } + return m_modulesManager.get(); +} + bool FloweePay::isOffline() const { return m_offline; diff --git a/src/FloweePay.h b/src/FloweePay.h index 2211941..2b0793d 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -102,6 +102,8 @@ public: */ PriceDataProvider *prices() const; + ModuleManager *modules(); + /// return the amount of milli-seconds we wait for a double-spent-proof int dspTimeout() const; void setDspTimeout(int milliseconds); @@ -341,6 +343,7 @@ private: std::string m_chainPrefix; std::unique_ptr m_downloadManager; std::unique_ptr m_prices; + std::unique_ptr m_modulesManager; NotificationManager m_notifications; CameraController* m_cameraController; QList m_wallets; diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp new file mode 100644 index 0000000..05089da --- /dev/null +++ b/src/ModuleInfo.cpp @@ -0,0 +1,16 @@ +#include "ModuleInfo.h" + +ModuleInfo::ModuleInfo(QObject *parent) + : QObject(parent) +{ +} + +QString ModuleInfo::id() const +{ + return m_id; +} + +void ModuleInfo::setId(const QString &newId) +{ + m_id = newId; +} diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h new file mode 100644 index 0000000..e43f732 --- /dev/null +++ b/src/ModuleInfo.h @@ -0,0 +1,26 @@ +#ifndef MODULE_INFO_H +#define MODULE_INFO_H + +#include + +#include "ModuleSection.h" + +class ModuleInfo : public QObject +{ + Q_OBJECT +public: + explicit ModuleInfo(QObject *parent = nullptr); + + + QString id() const; + void setId(const QString &newId); + +private: + QString m_id; + QString m_title; + QString m_description; + + QList m_sections; +}; + +#endif diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp new file mode 100644 index 0000000..bd2662a --- /dev/null +++ b/src/ModuleManager.cpp @@ -0,0 +1,25 @@ +#include "ModuleManager.h" + +#include +#include + +#include + + // : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), + // boost::filesystem::create_directories(boost::filesystem::path(m_basedir.toStdString())); + +ModuleManager::ModuleManager(QObject *parent) + : QObject(parent) +{ +} + +void ModuleManager::init() +{ + extern void load_all_modules(); + load_all_modules(); +} + +void ModuleManager::registerModule(ModuleInfo *info) +{ + logFatal() << "haha"; +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h new file mode 100644 index 0000000..7c98e3d --- /dev/null +++ b/src/ModuleManager.h @@ -0,0 +1,23 @@ +#ifndef MODULE_MANAGER_H +#define MODULE_MANAGER_H + +#include + +#include "ModuleInfo.h" + +class ModuleManager : public QObject +{ + Q_OBJECT +public: + explicit ModuleManager(QObject *parent = nullptr); + + void init(); + + void registerModule(ModuleInfo *info); + + +private: + +}; + +#endif diff --git a/src/ModuleManager_empty.cpp b/src/ModuleManager_empty.cpp new file mode 100644 index 0000000..b960876 --- /dev/null +++ b/src/ModuleManager_empty.cpp @@ -0,0 +1 @@ +void load_all_modules() { } diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp new file mode 100644 index 0000000..1a73a5f --- /dev/null +++ b/src/ModuleSection.cpp @@ -0,0 +1,6 @@ +#include "ModuleSection.h" + +ModuleSection::ModuleSection(QObject *parent) + : QObject(parent) +{ +} diff --git a/src/ModuleSection.h b/src/ModuleSection.h new file mode 100644 index 0000000..6808236 --- /dev/null +++ b/src/ModuleSection.h @@ -0,0 +1,24 @@ +#ifndef MODULE_SECTION_H +#define MODULE_SECTION_H + +#include + +class ModuleSection : public QObject +{ + Q_OBJECT +public: + explicit ModuleSection(QObject *parent = nullptr); + +/* + * some info about how this section can be plugged into the UI. + */ + +private: + /// generic texts, for the UI button to activates this module. + QString m_text; + QString m_subtext; + + QStringList m_requiredModules; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 91fca5d..e382110 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ #include "PaymentBackend.h" #include "QRCreator.h" #include "MenuModel.h" +#include "ModuleManager.h" #ifdef NETWORK_LOGGER # include "NetworkLogClient.h" #endif @@ -143,6 +144,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel; engine.rootContext()->setContextProperty("MenuModel", &menuModel); + engine.rootContext()->setContextProperty("ModuleManager", app->modules()); #ifdef MOBILE qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); diff --git a/testing/priceHistory/CMakeLists.txt b/testing/priceHistory/CMakeLists.txt index 8b27f2a..0679e5d 100644 --- a/testing/priceHistory/CMakeLists.txt +++ b/testing/priceHistory/CMakeLists.txt @@ -20,6 +20,7 @@ include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_price_history TestPriceHistory.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp ) target_link_libraries(test_price_history pay_lib Qt6::Test) add_test(NAME Pay_test_price_history COMMAND test_price_history) diff --git a/testing/value/CMakeLists.txt b/testing/value/CMakeLists.txt index 580820e..fd60642 100644 --- a/testing/value/CMakeLists.txt +++ b/testing/value/CMakeLists.txt @@ -18,7 +18,7 @@ set(CMAKE_AUTOMOC ON) include_directories(${Qt6Test_INCLUDE_DIRS}) -add_executable(test_value TestValue.cpp) +add_executable(test_value TestValue.cpp ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp) target_link_libraries(test_value pay_lib Qt6::Test) diff --git a/testing/wallet/CMakeLists.txt b/testing/wallet/CMakeLists.txt index c462b93..96cd250 100644 --- a/testing/wallet/CMakeLists.txt +++ b/testing/wallet/CMakeLists.txt @@ -21,6 +21,7 @@ include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_wallet TestWallet.cpp ${CMAKE_SOURCE_DIR}/src/Wallet_test.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp ) target_link_libraries(test_wallet pay_lib Qt6::Test) add_test(NAME Pay_test_wallet COMMAND test_wallet) diff --git a/testing/walletHistoryModel/CMakeLists.txt b/testing/walletHistoryModel/CMakeLists.txt index 2362770..b8d76c5 100644 --- a/testing/walletHistoryModel/CMakeLists.txt +++ b/testing/walletHistoryModel/CMakeLists.txt @@ -20,6 +20,7 @@ include_directories(${Qt6Test_INCLUDE_DIRS}) add_executable(test_wallethistorymodel TestWalletHistoryModel.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp ) target_link_libraries(test_wallethistorymodel pay_lib Qt6::Test) add_test(NAME Pay_test_wallet_history_model COMMAND test_wallethistorymodel) -- 2.54.0 From a73e004998e131565d57b775d8e5794a2f1f95f1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Jun 2023 10:46:54 +0200 Subject: [PATCH 0574/1428] Move QML and introduce i18n concept for modules --- CMakeLists.txt | 11 +- guis/mobile.qrc | 1 - .../BuildTransactionModuleInfo.cpp | 22 ++- .../BuildTransactionModuleInfo.h | 25 ++- modules/build-transaction/CMakeLists.txt | 1 + .../build-transaction}/PayToOthers.qml | 0 modules/build-transaction/data.qrc | 5 + modules/example/ExampleModuleInfo.cpp | 17 ++ modules/example/ExampleModuleInfo.h | 17 ++ src/ModuleInfo.cpp | 27 +++ src/ModuleInfo.h | 30 +++- src/ModuleManager.cpp | 40 ++++- src/ModuleManager.h | 17 ++ src/ModuleSection.cpp | 17 ++ src/ModuleSection.h | 17 ++ src/main.cpp | 5 +- translations/mobile-i18n.qrc | 1 + translations/module-build-transaction.ts | 154 ++++++++++++++++++ translations/module-build-transaction_en.ts | 154 ++++++++++++++++++ 19 files changed, 550 insertions(+), 11 deletions(-) rename {guis/mobile => modules/build-transaction}/PayToOthers.qml (100%) create mode 100644 modules/build-transaction/data.qrc create mode 100644 translations/module-build-transaction.ts create mode 100644 translations/module-build-transaction_en.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index f5f68f5..ba6d4a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,14 +89,19 @@ if(NOT ANDROID) translations/floweepay-mobile_en.ts translations/floweepay-mobile_nl.ts translations/floweepay-mobile_pl.ts + + translations/module-build-transaction_en.ts ) qt6_add_translation(qmFiles ${TS_FILES}) add_custom_target(i18n - COMMAND lupdate guis/desktop.qrc -ts ${TS_FILES_DESKTOP} translations/floweepay-desktop.ts + COMMAND lupdate guis/desktop.qrc -ts translations/floweepay-desktop.ts COMMAND lupdate src guis/widgets.qrc - -ts ${TS_FILES_GENERIC} translations/floweepay-common.ts - COMMAND lupdate guis/mobile.qrc -ts ${TS_FILES_MOBILE} translations/floweepay-mobile.ts + -ts translations/floweepay-common.ts + COMMAND lupdate guis/mobile.qrc -ts translations/floweepay-mobile.ts + COMMAND lupdate modules/build-transaction/data.qrc + modules/build-transaction/BuildTransactionModuleInfo.cpp + -ts translations/module-build-transaction.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/guis/mobile.qrc b/guis/mobile.qrc index ad99785..fefe409 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -53,7 +53,6 @@ mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml mobile/QRScannerOverlay.qml - mobile/PayToOthers.qml mobile/PayWithQR.qml mobile/SlideToApprove.qml mobile/ReceiveTab.qml diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 6d6ee83..d30af6c 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -1,7 +1,27 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "BuildTransactionModuleInfo.h" void BuildTransactionModuleInfo::init(ModuleManager *manager) { - // TODO + auto module = new ModuleInfo(manager); + module->setTranslationUnit("module-build-transaction"); + + manager->registerModule(module); } diff --git a/modules/build-transaction/BuildTransactionModuleInfo.h b/modules/build-transaction/BuildTransactionModuleInfo.h index 604540a..80eb7a1 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.h +++ b/modules/build-transaction/BuildTransactionModuleInfo.h @@ -1,8 +1,27 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #pragma once #include -namespace BuildTransactionModuleInfo +class BuildTransactionModuleInfo : public QObject { - void init(ModuleManager *manager); -} + Q_OBJECT +public: + static void init(ModuleManager *manager); +}; diff --git a/modules/build-transaction/CMakeLists.txt b/modules/build-transaction/CMakeLists.txt index b41761b..af51920 100644 --- a/modules/build-transaction/CMakeLists.txt +++ b/modules/build-transaction/CMakeLists.txt @@ -19,6 +19,7 @@ project(build-transaction_module) set (SOURCES BuildTransactionModuleInfo.cpp ) +qt6_add_resources(SOURCES data.qrc) add_library (build-transaction_module_lib STATIC ${SOURCES}) target_link_libraries(build-transaction_module_lib pay_lib) diff --git a/guis/mobile/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml similarity index 100% rename from guis/mobile/PayToOthers.qml rename to modules/build-transaction/PayToOthers.qml diff --git a/modules/build-transaction/data.qrc b/modules/build-transaction/data.qrc new file mode 100644 index 0000000..f316dc3 --- /dev/null +++ b/modules/build-transaction/data.qrc @@ -0,0 +1,5 @@ + + + PayToOthers.qml + + diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index 9878230..484a0ba 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ExampleModuleInfo.h" void ExampleModuleInfo::init(ModuleManager *manager) diff --git a/modules/example/ExampleModuleInfo.h b/modules/example/ExampleModuleInfo.h index e441b7f..f37a275 100644 --- a/modules/example/ExampleModuleInfo.h +++ b/modules/example/ExampleModuleInfo.h @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #pragma once #include diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 05089da..e8dad74 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ModuleInfo.h" ModuleInfo::ModuleInfo(QObject *parent) @@ -14,3 +31,13 @@ void ModuleInfo::setId(const QString &newId) { m_id = newId; } + +QString ModuleInfo::translationUnit() const +{ + return m_translationUnit; +} + +void ModuleInfo::setTranslationUnit(const QString &name) +{ + m_translationUnit = name; +} diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index e43f732..3dbd4be 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef MODULE_INFO_H #define MODULE_INFO_H @@ -11,14 +28,25 @@ class ModuleInfo : public QObject public: explicit ModuleInfo(QObject *parent = nullptr); - QString id() const; void setId(const QString &newId); + /** + * A module ships its own translations, to make the wallet load + * those translations you need to put them into the QRC system + * and then let us know the base of the filenames you picked + * for them. + * Example: "module-build-transaction" + */ + void setTranslationUnit(const QString &name); + /// returns the set translation unit, or empty string if none + QString translationUnit() const; + private: QString m_id; QString m_title; QString m_description; + QString m_translationUnit; QList m_sections; }; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index bd2662a..714271e 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -1,8 +1,28 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ModuleManager.h" #include #include +#include +#include +#include #include // : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), @@ -21,5 +41,23 @@ void ModuleManager::init() void ModuleManager::registerModule(ModuleInfo *info) { - logFatal() << "haha"; + assert(info); + if (info == nullptr) + return; + + auto translations = info->translationUnit(); + if (!translations.isEmpty()) { + auto *translator = new QTranslator(this); + /* + * We try to load it from the default location as used in the translations subdir (:/i18n) + * and if that fails we try again at the root level of the QRC because individual modules + * can then just use one data.qrc and include translations, if they manage their own. + */ + if (translator->load(QLocale(), translations, QLatin1String("_"), QLatin1String(":/i18n"))) + QCoreApplication::installTranslator(translator); + else if (translator->load(QLocale(), translations, QLatin1String("_"), QLatin1String(":"))) + QCoreApplication::installTranslator(translator); + else + delete translator; + } } diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 7c98e3d..c9e2d3e 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef MODULE_MANAGER_H #define MODULE_MANAGER_H diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index 1a73a5f..eeabc47 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ModuleSection.h" ModuleSection::ModuleSection(QObject *parent) diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 6808236..acc8dc2 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef MODULE_SECTION_H #define MODULE_SECTION_H diff --git a/src/main.cpp b/src/main.cpp index e382110..91d8ba4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,6 +108,10 @@ int main(int argc, char *argv[]) logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); #endif + auto app = FloweePay::instance(); + // load the modules and its translations units first, which gives them the lowest priority + app->modules(); + static const char* languagePacks[] = { #ifdef DESKTOP "floweepay-desktop", @@ -139,7 +143,6 @@ int main(int argc, char *argv[]) #endif engine.addImageProvider(QLatin1String("qr"), new QRCreator()); - auto app = FloweePay::instance(); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel; diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index 900e5e4..9f8f820 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -6,5 +6,6 @@ floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm + module-build-transaction_en.qm
diff --git a/translations/module-build-transaction.ts b/translations/module-build-transaction.ts new file mode 100644 index 0000000..f46af6f --- /dev/null +++ b/translations/module-build-transaction.ts @@ -0,0 +1,154 @@ + + + + + BuildTransactionModuleInfo + + + This is a nice text + + + + + PayToOthers + + + Build Transaction + + + + + Building Error + error during build + + + + + Add Payment Detail + page title + + + + + + Add Destination + + + + + an address to send money to + + + + + Confirm Sending + confirm we want to send the transaction + + + + + TXID + + + + + Copy transaction-ID + + + + + Fee + + + + + Transaction size + + + + + %1 bytes + + + + + Fee per byte + + + + + %1 sat/byte + fee + + + + + Destination + + + + + unset + indication of empty + + + + + invalid + address is not correct + + + + + + Copy Address + + + + + Edit Destination + + + + + Send All + all money in wallet + + + + + Bitcoin Cash Address + + + + + Warning + + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + + I am certain + + + + + Drag to Edit + + + + + Drag to Delete + + + + + Prepare Payment... + + + + diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts new file mode 100644 index 0000000..534054a --- /dev/null +++ b/translations/module-build-transaction_en.ts @@ -0,0 +1,154 @@ + + + + + BuildTransactionModuleInfo + + + This is a nice text + bla + + + + PayToOthers + + + Build Transaction + + + + + Building Error + error during build + + + + + Add Payment Detail + page title + + + + + + Add Destination + + + + + an address to send money to + + + + + Confirm Sending + confirm we want to send the transaction + + + + + TXID + + + + + Copy transaction-ID + + + + + Fee + + + + + Transaction size + + + + + %1 bytes + + + + + Fee per byte + + + + + %1 sat/byte + fee + + + + + Destination + + + + + unset + indication of empty + + + + + invalid + address is not correct + + + + + + Copy Address + + + + + Edit Destination + + + + + Send All + all money in wallet + + + + + Bitcoin Cash Address + + + + + Warning + + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + + I am certain + + + + + Drag to Edit + + + + + Drag to Delete + + + + + Prepare Payment... + + + + -- 2.54.0 From 42d3c2e96ba885042b09eae7c5b4a7b33ff40abb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Jun 2023 15:26:31 +0200 Subject: [PATCH 0575/1428] Make the build-transactions module function This moves the last of the assets to the module and provides the base requirements to actually make the featue function from a module. --- CMakeLists.txt | 8 ++- guis/mobile.qrc | 2 - guis/mobile/SendTransactionsTab.qml | 12 +++-- .../BuildTransactionModuleInfo.cpp | 7 ++- modules/build-transaction/CMakeLists.txt | 2 +- modules/build-transaction/PayToOthers.qml | 11 ++-- modules/build-transaction/data.qrc | 4 +- .../build-transaction}/edit-light.svg | 0 .../build-transaction}/edit.svg | 0 src/ModuleInfo.cpp | 46 +++++++++++++++++ src/ModuleInfo.h | 31 +++++++++++- src/ModuleManager.cpp | 41 ++++++++++++++- src/ModuleManager.h | 13 ++++- src/ModuleSection.cpp | 50 ++++++++++++++++++- src/ModuleSection.h | 41 +++++++++++++-- 15 files changed, 242 insertions(+), 26 deletions(-) rename {guis/mobile/images => modules/build-transaction}/edit-light.svg (100%) rename {guis/mobile/images => modules/build-transaction}/edit.svg (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6d4a6..25fd49b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,7 +154,8 @@ if (NOT "${Qt6Multimedia_FOUND}" OR NOT "${ZXing_FOUND}") endif () set (PAY_MOBILE_LIBS "") -if (BUILD_MOBILE_PAY) +set (PAY_MOBILE_RESOURCES "") +if (BUILD_MOBILE_PAY) # support modules, which get enabled currently for mobile only add_subdirectory(modules) # find all modules present in the source-tree and make sure we link them into the @@ -166,6 +167,9 @@ if (BUILD_MOBILE_PAY) file(GLOB subModule ${child}/*ModuleInfo.h) file(RELATIVE_PATH moduleName ${CMAKE_CURRENT_SOURCE_DIR}/modules ${child}) list(APPEND PAY_MOBILE_LIBS "${moduleName}_module_lib") + + file(GLOB resources ${child}/*qrc) + list(APPEND PAY_MOBILE_RESOURCES "${resources}") endif() endforeach() @@ -203,6 +207,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) qt6_add_resources(SOURCES_PAY_MOBILE guis/mobile.qrc guis/widgets.qrc + ${PAY_MOBILE_RESOURCES} ) if (EXISTS "${CMAKE_BINARY_DIR}/floweepay-mobile_nl.qm") message ("pay_mobile: Found pre-compiled translations") @@ -244,6 +249,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) guis/mobile.qrc guis/widgets.qrc ${CMAKE_BINARY_DIR}/mobile-i18n.qrc + ${PAY_MOBILE_RESOURCES} ) add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) set_target_properties(pay_mobile PROPERTIES diff --git a/guis/mobile.qrc b/guis/mobile.qrc index fefe409..cfe1aa2 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -22,8 +22,6 @@ mobile/images/backspace-light.svg mobile/images/settingsIcon-light.svg mobile/images/settingsIcon.svg - mobile/images/edit.svg - mobile/images/edit-light.svg mobile/images/edit-pen.svg mobile/images/edit-pen-light.svg mobile/images/tx-receiving.svg diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index e57bcf7..40434a6 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -35,10 +35,14 @@ FocusScope { showPageIcon: true onClicked: thePile.push("PayWithQR.qml") } - TextButton { - text: qsTr("Build transaction") - showPageIcon: true - onClicked: thePile.push("PayToOthers.qml") + Repeater { + model: ModuleManager.sendMenuItems + TextButton { + text: modelData.text + subtext: modelData.subtext + showPageIcon: true + onClicked: thePile.push(modelData.qml) + } } } } diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index d30af6c..814f0d9 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -17,11 +17,16 @@ */ #include "BuildTransactionModuleInfo.h" - void BuildTransactionModuleInfo::init(ModuleManager *manager) { auto module = new ModuleInfo(manager); module->setTranslationUnit("module-build-transaction"); + auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); + sendButton->setText(tr("Build Transaction")); + // notice that the directory it is in is registered in the data.qrc header + sendButton->setStartQMLFile("qrc:/build-transaction/PayToOthers.qml"); + module->addSection(sendButton); + manager->registerModule(module); } diff --git a/modules/build-transaction/CMakeLists.txt b/modules/build-transaction/CMakeLists.txt index af51920..1b45944 100644 --- a/modules/build-transaction/CMakeLists.txt +++ b/modules/build-transaction/CMakeLists.txt @@ -19,7 +19,7 @@ project(build-transaction_module) set (SOURCES BuildTransactionModuleInfo.cpp ) -qt6_add_resources(SOURCES data.qrc) +#qt6_add_resources(SOURCES data.qrc) add_library (build-transaction_module_lib STATIC ${SOURCES}) target_link_libraries(build-transaction_module_lib pay_lib) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 7d4492a..835ff79 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -15,10 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls as QQC2 -import "../Flowee" as Flowee +import QtQuick; +import QtQuick.Layouts; +import QtQuick.Controls as QQC2; +import "../Flowee" as Flowee; +import "../mobile"; import Flowee.org.pay; @@ -547,7 +548,7 @@ Page { let additional = Math.max(0, Math.min(listItem.x * -1 - width, 16)) return parent.width + additional; } - source: "qrc:/edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + source: "./edit" + (Pay.useDarkSkin ? "-light" : "") + ".svg" smooth: true } diff --git a/modules/build-transaction/data.qrc b/modules/build-transaction/data.qrc index f316dc3..cb06914 100644 --- a/modules/build-transaction/data.qrc +++ b/modules/build-transaction/data.qrc @@ -1,5 +1,7 @@ - + PayToOthers.qml + edit.svg + edit-light.svg diff --git a/guis/mobile/images/edit-light.svg b/modules/build-transaction/edit-light.svg similarity index 100% rename from guis/mobile/images/edit-light.svg rename to modules/build-transaction/edit-light.svg diff --git a/guis/mobile/images/edit.svg b/modules/build-transaction/edit.svg similarity index 100% rename from guis/mobile/images/edit.svg rename to modules/build-transaction/edit.svg diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index e8dad74..e5ba6f3 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -37,6 +37,52 @@ QString ModuleInfo::translationUnit() const return m_translationUnit; } +QString ModuleInfo::title() const +{ + return m_title; +} + +void ModuleInfo::setTitle(const QString &newTitle) +{ + m_title = newTitle; +} + +QString ModuleInfo::description() const +{ + return m_description; +} + +void ModuleInfo::addSection(ModuleSection *section) +{ + // defensive programming here... + assert(section); + if (!section) + return; + section->setParent(this); + m_sections.removeAll(section); + m_sections.append(section); +} + +void ModuleInfo::setDescription(const QString &newDescription) +{ + m_description = newDescription; +} + +QList ModuleInfo::sections() const +{ + return m_sections; +} + +bool ModuleInfo::enabled() const +{ + return m_enabled; +} + +void ModuleInfo::setEnabled(bool newEnabled) +{ + m_enabled = newEnabled; +} + void ModuleInfo::setTranslationUnit(const QString &name) { m_translationUnit = name; diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 3dbd4be..078be31 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -25,6 +25,10 @@ class ModuleInfo : public QObject { Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(QString title READ title CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(QList sections READ sections CONSTANT) public: explicit ModuleInfo(QObject *parent = nullptr); @@ -42,13 +46,38 @@ public: /// returns the set translation unit, or empty string if none QString translationUnit() const; + /** + * >The title as shown in the module selector UI. + */ + void setTitle(const QString &newTitle); + QString title() const; + + /** + * >The description of this module, as shown in the module selector UI. + */ + void setDescription(const QString &newDescription); + QString description() const; + + /** + * Sections define where a module is used, add them to "plug" your module into the UI + */ + void addSection(ModuleSection *section); + QList sections() const; + + bool enabled() const; + void setEnabled(bool newEnabled); + +signals: + void enabledChanged(); + private: QString m_id; QString m_title; QString m_description; QString m_translationUnit; + bool m_enabled; - QList m_sections; + QList m_sections; }; #endif diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 714271e..3d2be74 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -22,21 +22,44 @@ #include #include +#include #include #include - // : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), - // boost::filesystem::create_directories(boost::filesystem::path(m_basedir.toStdString())); +#include ModuleManager::ModuleManager(QObject *parent) : QObject(parent) { + m_configFile = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "modules.conf"); + if (m_configFile.isEmpty()) { + // make sure the directory exists + auto path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + boost::filesystem::create_directories(boost::filesystem::path(path.toStdString())); + m_configFile = path + '/' + "modules.conf"; + } } void ModuleManager::init() { + assert(m_modules.isEmpty()); // please don't call multiple times. extern void load_all_modules(); + // the load_all_modules method is generated by cmake in the modules builddir. load_all_modules(); + + QFile config(m_configFile); + if (config.open(QIODevice::ReadOnly)) { + // TODO + + } + // for now we just assume all are enabled ;-) + for (auto *m : m_modules) { + for (auto *s : m->sections()) { + if (s->type() == ModuleSection::SendMethod) { + m_sendMenuSections.append(s); + } + } + } } void ModuleManager::registerModule(ModuleInfo *info) @@ -44,6 +67,10 @@ void ModuleManager::registerModule(ModuleInfo *info) assert(info); if (info == nullptr) return; + if (m_modules.contains(info)) + return; + info->setParent(this); + m_modules.append(info); auto translations = info->translationUnit(); if (!translations.isEmpty()) { @@ -61,3 +88,13 @@ void ModuleManager::registerModule(ModuleInfo *info) delete translator; } } + +QList ModuleManager::sendMenuSections() const +{ + return m_sendMenuSections; +} + +QList ModuleManager::registeredModules() const +{ + return m_modules; +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h index c9e2d3e..0405dd0 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -25,16 +25,27 @@ class ModuleManager : public QObject { Q_OBJECT + Q_PROPERTY(QList registeredModules READ registeredModules CONSTANT) + Q_PROPERTY(QList sendMenuItems READ sendMenuSections CONSTANT) public: explicit ModuleManager(QObject *parent = nullptr); + /** + * Finds all intalled modules and loads them. + */ void init(); void registerModule(ModuleInfo *info); + QList registeredModules() const; + + // lists per type + QList sendMenuSections() const; private: - + QList m_modules; + QList m_sendMenuSections; + QString m_configFile; }; #endif diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index eeabc47..829d9c4 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -17,7 +17,53 @@ */ #include "ModuleSection.h" -ModuleSection::ModuleSection(QObject *parent) - : QObject(parent) +ModuleSection::ModuleSection(SectionType type, QObject *parent) + : QObject(parent), + m_type(type) { } + +QString ModuleSection::text() const +{ + return m_text; +} + +QString ModuleSection::subtext() const +{ + return m_subtext; +} + +QStringList ModuleSection::requiredModules() const +{ + return m_requiredModules; +} + +QString ModuleSection::startQMLFile() const +{ + return m_startQMLfile; +} + +ModuleSection::SectionType ModuleSection::type() const +{ + return m_type; +} + +void ModuleSection::setStartQMLFile(const QString &filename) +{ + m_startQMLfile = filename; +} + +void ModuleSection::setRequiredModules(const QStringList &modules) +{ + m_requiredModules = modules; +} + +void ModuleSection::setSubtext(const QString &newSubtext) +{ + m_subtext = newSubtext; +} + +void ModuleSection::setText(const QString &newText) +{ + m_text = newText; +} diff --git a/src/ModuleSection.h b/src/ModuleSection.h index acc8dc2..0d1cebc 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -23,17 +23,48 @@ class ModuleSection : public QObject { Q_OBJECT + Q_PROPERTY(QString text READ text CONSTANT) + Q_PROPERTY(QString subtext READ subtext CONSTANT) + Q_PROPERTY(QString qml READ startQMLFile CONSTANT) public: - explicit ModuleSection(QObject *parent = nullptr); + /// The placement in the main app of this section. + enum SectionType { + SendMethod, //< A specific way to send coin, shown in list of send-methods. + }; -/* - * some info about how this section can be plugged into the UI. - */ + explicit ModuleSection(SectionType type, QObject *parent = nullptr); + + /** + * This text is used on in the chosen UI-type to select this module. + */ + void setText(const QString &newText); + QString text() const; + + /// This text is the alt-text + void setSubtext(const QString &newSubtext); + QString subtext() const; + + /** + * This section may only show if another module is enabled, + * name that module (by id) here. + * \sa ModuleInfo::id() + */ + void setRequiredModules(const QStringList &modules); + QStringList requiredModules() const; + + /** + * The QML file that should be loaded to invoke this module. + */ + void setStartQMLFile(const QString &filename); + QString startQMLFile() const; + + SectionType type() const; private: - /// generic texts, for the UI button to activates this module. + const SectionType m_type; QString m_text; QString m_subtext; + QString m_startQMLfile; QStringList m_requiredModules; }; -- 2.54.0 From c25adfffa08455cf3b9628c225da4bb38e72b6b0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Jun 2023 17:11:29 +0200 Subject: [PATCH 0576/1428] After an API review. This simplyfies things and ensures the translations are loaded before any possible tr() calls. --- modules/CMakeLists.txt | 7 +++-- .../BuildTransactionModuleInfo.cpp | 10 +++---- .../BuildTransactionModuleInfo.h | 6 +++- modules/build-transaction/CMakeLists.txt | 1 - modules/example/ExampleModuleInfo.cpp | 7 ++--- modules/example/ExampleModuleInfo.h | 20 +++++++++++-- src/FloweePay.cpp | 11 -------- src/FloweePay.h | 3 -- src/ModuleInfo.cpp | 10 ------- src/ModuleInfo.h | 21 ++++++-------- src/ModuleManager.cpp | 28 ++++++++----------- src/ModuleManager.h | 9 +++--- src/ModuleManager_empty.cpp | 4 ++- src/main.cpp | 8 +++--- 14 files changed, 65 insertions(+), 80 deletions(-) diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 425fac0..99bba7e 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -43,14 +43,15 @@ endforeach() # After compiling them, generate a little cpp file that actually loads those modules # into the ModulesManager -set (MODULES_LOAD_CPP "#include \n") +set (MODULES_LOAD_CPP "#include \n") foreach (include ${INCLUDES_LIST}) set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP}#include <${include}>\n") endforeach() -set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP}\nvoid load_all_modules() {\n auto *m=FloweePay::instance()->modules();\n") +set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP}\nvoid load_all_modules(ModuleManager *m) {\n") foreach (className ${CLASSES_LIST}) - set (MODULES_LOAD_CPP "${MODULES_LOAD_CPP} ${className}::init(m);\n") + set (MODULES_LOAD_CPP + "${MODULES_LOAD_CPP} m->load(${className}::translationUnit(), &${className}::build);\n") endforeach() # finally, write out the cpp file. diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 814f0d9..263ebd8 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -17,16 +17,16 @@ */ #include "BuildTransactionModuleInfo.h" -void BuildTransactionModuleInfo::init(ModuleManager *manager) +ModuleInfo * BuildTransactionModuleInfo::build() { - auto module = new ModuleInfo(manager); - module->setTranslationUnit("module-build-transaction"); + auto module = new ModuleInfo(); + module->setTitle(tr("Create Transactions")); + module->setDescription(tr("blabla")); auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); sendButton->setText(tr("Build Transaction")); // notice that the directory it is in is registered in the data.qrc header sendButton->setStartQMLFile("qrc:/build-transaction/PayToOthers.qml"); module->addSection(sendButton); - - manager->registerModule(module); + return module; } diff --git a/modules/build-transaction/BuildTransactionModuleInfo.h b/modules/build-transaction/BuildTransactionModuleInfo.h index 80eb7a1..7e1abfc 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.h +++ b/modules/build-transaction/BuildTransactionModuleInfo.h @@ -23,5 +23,9 @@ class BuildTransactionModuleInfo : public QObject { Q_OBJECT public: - static void init(ModuleManager *manager); + static ModuleInfo *build(); + + static const char *translationUnit() { + return "module-build-transaction"; + } }; diff --git a/modules/build-transaction/CMakeLists.txt b/modules/build-transaction/CMakeLists.txt index 1b45944..b41761b 100644 --- a/modules/build-transaction/CMakeLists.txt +++ b/modules/build-transaction/CMakeLists.txt @@ -19,7 +19,6 @@ project(build-transaction_module) set (SOURCES BuildTransactionModuleInfo.cpp ) -#qt6_add_resources(SOURCES data.qrc) add_library (build-transaction_module_lib STATIC ${SOURCES}) target_link_libraries(build-transaction_module_lib pay_lib) diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index 484a0ba..8f8b738 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -17,10 +17,9 @@ */ #include "ExampleModuleInfo.h" -void ExampleModuleInfo::init(ModuleManager *manager) +ModuleInfo * ExampleModuleInfo::build() { - ModuleInfo *info = new ModuleInfo(manager); + ModuleInfo *info = new ModuleInfo(); info->setId("exampleModule"); - - manager->registerModule(info); + return info; } diff --git a/modules/example/ExampleModuleInfo.h b/modules/example/ExampleModuleInfo.h index f37a275..e70ee21 100644 --- a/modules/example/ExampleModuleInfo.h +++ b/modules/example/ExampleModuleInfo.h @@ -19,7 +19,21 @@ #include -namespace ExampleModuleInfo +class ExampleModuleInfo : public QObject { - void init(ModuleManager *manager); -} + Q_OBJECT +public: + static ModuleInfo *build(); + + /** + * A module has its own translations, to make the wallet load + * those translations we need to report what the basename of + * our translation-unit is. + * Note that returning nullptr is legal and just skips this part. + * + * Example: "module-build-transaction" + */ + static const char *translationUnit() { + return nullptr; + } +}; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index c82187f..841a4aa 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -20,7 +20,6 @@ #include "NewWalletConfig.h" #include "AddressInfo.h" #include "PriceDataProvider.h" -#include "ModuleManager.h" #include #include @@ -887,16 +886,6 @@ PriceDataProvider *FloweePay::prices() const return m_prices.get(); } -ModuleManager *FloweePay::modules() -{ - if (m_modulesManager.get() == nullptr) { - m_modulesManager.reset(new ModuleManager(this)); - // actually load the available modules: - m_modulesManager->init(); - } - return m_modulesManager.get(); -} - bool FloweePay::isOffline() const { return m_offline; diff --git a/src/FloweePay.h b/src/FloweePay.h index 2b0793d..2211941 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -102,8 +102,6 @@ public: */ PriceDataProvider *prices() const; - ModuleManager *modules(); - /// return the amount of milli-seconds we wait for a double-spent-proof int dspTimeout() const; void setDspTimeout(int milliseconds); @@ -343,7 +341,6 @@ private: std::string m_chainPrefix; std::unique_ptr m_downloadManager; std::unique_ptr m_prices; - std::unique_ptr m_modulesManager; NotificationManager m_notifications; CameraController* m_cameraController; QList m_wallets; diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index e5ba6f3..a315763 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -32,11 +32,6 @@ void ModuleInfo::setId(const QString &newId) m_id = newId; } -QString ModuleInfo::translationUnit() const -{ - return m_translationUnit; -} - QString ModuleInfo::title() const { return m_title; @@ -82,8 +77,3 @@ void ModuleInfo::setEnabled(bool newEnabled) { m_enabled = newEnabled; } - -void ModuleInfo::setTranslationUnit(const QString &name) -{ - m_translationUnit = name; -} diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 078be31..f11713e 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -22,6 +22,14 @@ #include "ModuleSection.h" +/** + * This is part of the Flowee Pay Modules section. + * + * A module is an optional product that is shipped in the Flowee Pay application + * in a way that it can be enabled or disabled by the user. + * The ModuleInfo is the metadata about the module, data that is shown to the user + * to allow them to select if they like to enable this module or not. + */ class ModuleInfo : public QObject { Q_OBJECT @@ -35,17 +43,6 @@ public: QString id() const; void setId(const QString &newId); - /** - * A module ships its own translations, to make the wallet load - * those translations you need to put them into the QRC system - * and then let us know the base of the filenames you picked - * for them. - * Example: "module-build-transaction" - */ - void setTranslationUnit(const QString &name); - /// returns the set translation unit, or empty string if none - QString translationUnit() const; - /** * >The title as shown in the module selector UI. */ @@ -75,7 +72,7 @@ private: QString m_title; QString m_description; QString m_translationUnit; - bool m_enabled; + bool m_enabled = true; QList m_sections; }; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 3d2be74..bed2c79 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -38,14 +38,11 @@ ModuleManager::ModuleManager(QObject *parent) boost::filesystem::create_directories(boost::filesystem::path(path.toStdString())); m_configFile = path + '/' + "modules.conf"; } -} -void ModuleManager::init() -{ - assert(m_modules.isEmpty()); // please don't call multiple times. - extern void load_all_modules(); + extern void load_all_modules(ModuleManager*); // the load_all_modules method is generated by cmake in the modules builddir. - load_all_modules(); + // it essentially makes each module call 'load' on this class. + load_all_modules(this); QFile config(m_configFile); if (config.open(QIODevice::ReadOnly)) { @@ -62,18 +59,10 @@ void ModuleManager::init() } } -void ModuleManager::registerModule(ModuleInfo *info) +void ModuleManager::load(const char *translationUnit, const std::function &function) { - assert(info); - if (info == nullptr) - return; - if (m_modules.contains(info)) - return; - info->setParent(this); - m_modules.append(info); - - auto translations = info->translationUnit(); - if (!translations.isEmpty()) { + if (translationUnit) { + QString translations = QString::fromUtf8(translationUnit); auto *translator = new QTranslator(this); /* * We try to load it from the default location as used in the translations subdir (:/i18n) @@ -87,6 +76,11 @@ void ModuleManager::registerModule(ModuleInfo *info) else delete translator; } + auto *info = function(); + if (info == nullptr) + return; + info->setParent(this); // take ownership + m_modules.append(info); } QList ModuleManager::sendMenuSections() const diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 0405dd0..9d222c2 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -22,6 +22,8 @@ #include "ModuleInfo.h" +#include + class ModuleManager : public QObject { Q_OBJECT @@ -30,12 +32,9 @@ class ModuleManager : public QObject public: explicit ModuleManager(QObject *parent = nullptr); - /** - * Finds all intalled modules and loads them. - */ - void init(); + void load(const char *translationUnit, const std::function &function); - void registerModule(ModuleInfo *info); + // void registerModule(ModuleInfo *info); QList registeredModules() const; diff --git a/src/ModuleManager_empty.cpp b/src/ModuleManager_empty.cpp index b960876..7984938 100644 --- a/src/ModuleManager_empty.cpp +++ b/src/ModuleManager_empty.cpp @@ -1 +1,3 @@ -void load_all_modules() { } +class ModuleManager; + +void load_all_modules(ModuleManager*) { } diff --git a/src/main.cpp b/src/main.cpp index 91d8ba4..bb27e66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,10 +108,9 @@ int main(int argc, char *argv[]) logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); #endif - auto app = FloweePay::instance(); // load the modules and its translations units first, which gives them the lowest priority - app->modules(); - + ModuleManager modules; + // then the core translations. static const char* languagePacks[] = { #ifdef DESKTOP "floweepay-desktop", @@ -142,12 +141,13 @@ int main(int argc, char *argv[]) }, Qt::QueuedConnection); #endif + auto app = FloweePay::instance(); engine.addImageProvider(QLatin1String("qr"), new QRCreator()); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel; engine.rootContext()->setContextProperty("MenuModel", &menuModel); - engine.rootContext()->setContextProperty("ModuleManager", app->modules()); + engine.rootContext()->setContextProperty("ModuleManager", &modules); #ifdef MOBILE qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); -- 2.54.0 From 58614e17a6cdfd7ee168420bca53611d465dc0cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Jun 2023 23:43:22 +0200 Subject: [PATCH 0577/1428] Add a module explorer page in mobile view. This allows discovering of the modules and enabling / disabling them. --- guis/mobile.qrc | 1 + guis/mobile/ExploreModules.qml | 128 ++++++++++++++++++ .../BuildTransactionModuleInfo.cpp | 2 +- .../{data.qrc => build-transactions-data.qrc} | 0 modules/example/ExampleModuleInfo.cpp | 12 ++ modules/example/ExamplePage.qml | 30 ++++ modules/example/example-data.qrc | 6 + modules/example/example.svg | 13 ++ src/MenuModel.cpp | 1 + src/ModuleInfo.cpp | 19 ++- src/ModuleInfo.h | 13 +- src/ModuleSection.cpp | 13 ++ src/ModuleSection.h | 12 ++ 13 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 guis/mobile/ExploreModules.qml rename modules/build-transaction/{data.qrc => build-transactions-data.qrc} (100%) create mode 100644 modules/example/ExamplePage.qml create mode 100644 modules/example/example-data.qrc create mode 100644 modules/example/example.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index cfe1aa2..96ad731 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -74,5 +74,6 @@ mobile/SelectDefaultAccountPage.qml mobile/InstaPayConfigPage.qml mobile/InstaPayConfigButton.qml + mobile/ExploreModules.qml diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml new file mode 100644 index 0000000..9f6b2d6 --- /dev/null +++ b/guis/mobile/ExploreModules.qml @@ -0,0 +1,128 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + id: root + headerText: qsTr("Explore") + + Flickable { + anchors.fill: parent + anchors.topMargin: 10 + anchors.bottomMargin: 10 + clip: true + contentWidth: width + contentHeight: content.height + + Column { + id: content + width: parent.width + spacing: 15 + + Repeater { + model: ModuleManager.registeredModules + Rectangle { + width: root.width - 30 + height: 35 + titleLabel.height + statusField.height + Math.min(120, descriptionLabel.implicitHeight) + radius: 20 + color: palette.alternateBase + border.width: 1 + border.color: palette.midlight + + Flowee.Label { + id: titleLabel + y: 15 + text: modelData.title + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter + } + Image { + x: 10 + width: 42 + anchors.top: descriptionFrame.top + source: modelData.iconSource + visible: modelData.iconSource !== "" + smooth: true + fillMode: Image.PreserveAspectFit + } + + Item { + id: descriptionFrame + anchors.top: titleLabel.bottom + anchors.topMargin: 10 + width: parent.width - (modelData.iconSource === "" ? 0 : 52) + clip: true + anchors.bottom: statusField.top + anchors.bottomMargin: 10 + anchors.right: parent.right + Rectangle { + color: palette.alternateBase + anchors.fill: descriptionLabel + } + Flowee.Label { + id: descriptionLabel + width: parent.width - 20 + x: 10 + text: modelData.description + horizontalAlignment: Text.AlignJustify + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + MouseArea { + anchors.fill: parent + onClicked: descriptionFrame.clip = !descriptionFrame.clip + } + } + } + + Item { + id: statusField + width: parent.width + height: 40 + anchors.bottom: parent.bottom + Flowee.CheckBox { + anchors.verticalCenter: parent.verticalCenter + x: 10 + checked: modelData.enabled + enabled: false + } + + Flowee.ImageButton { + source: "qrc:/sending" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + iconSize: 24 + anchors.right: parent.right + anchors.rightMargin: 15 + anchors.verticalCenter: parent.verticalCenter + visible: section != null + opacity: (visible && section.enabled) ? 1 : 0.3 + property QtObject section: { + for (let s of modelData.sections) { + if (s.isSendMethod) + return s; + } + return null; + } + + onClicked: if (section != null) section.enabled = !section.enabled + } + } + } + } + } + } +} diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 263ebd8..a24bf6e 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -21,7 +21,7 @@ ModuleInfo * BuildTransactionModuleInfo::build() { auto module = new ModuleInfo(); module->setTitle(tr("Create Transactions")); - module->setDescription(tr("blabla")); + module->setDescription(tr("This module allows building more powerful transactions in one simple user interface.")); auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); sendButton->setText(tr("Build Transaction")); diff --git a/modules/build-transaction/data.qrc b/modules/build-transaction/build-transactions-data.qrc similarity index 100% rename from modules/build-transaction/data.qrc rename to modules/build-transaction/build-transactions-data.qrc diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index 8f8b738..1a3d4af 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -21,5 +21,17 @@ ModuleInfo * ExampleModuleInfo::build() { ModuleInfo *info = new ModuleInfo(); info->setId("exampleModule"); + info->setTitle(tr("Example Module")); + info->setDescription(tr("The example module is helpful in order to show the concept of a module " + "and what it can do in Flowee Pay. This text is shown in the module explorer UI.")); + info->setIconSource("qrc:/example/example.svg"); + + auto sendButtonExample = new ModuleSection(ModuleSection::SendMethod, info); + sendButtonExample->setText(tr("Example Module")); + sendButtonExample->setSubtext(tr("This is some helptext")); + // notice that the directory it is in is registered in the data.qrc header + sendButtonExample->setStartQMLFile("qrc:/example/ExamplePage.qml"); + info->addSection(sendButtonExample); + return info; } diff --git a/modules/example/ExamplePage.qml b/modules/example/ExamplePage.qml new file mode 100644 index 0000000..48f264d --- /dev/null +++ b/modules/example/ExamplePage.qml @@ -0,0 +1,30 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee +import "../mobile"; + +Page { + headerText: qsTr("Example") + + Flowee.Label { + y: 10 + text: "This is an example page" + } +} diff --git a/modules/example/example-data.qrc b/modules/example/example-data.qrc new file mode 100644 index 0000000..b033088 --- /dev/null +++ b/modules/example/example-data.qrc @@ -0,0 +1,6 @@ + + + example.svg + ExamplePage.qml + + diff --git a/modules/example/example.svg b/modules/example/example.svg new file mode 100644 index 0000000..d8efb34 --- /dev/null +++ b/modules/example/example.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index bf05120..fffa5bd 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -21,6 +21,7 @@ MenuModel::MenuModel(QObject *parent) : QAbstractListModel{parent}, m_current(&m_root) { + m_root.children.append({tr("Explore"), "./ExploreModules.qml", {}}); m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); m_root.children.append({tr("About"), "./About.qml", {}}); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index a315763..10ddd6d 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -54,8 +54,10 @@ void ModuleInfo::addSection(ModuleSection *section) if (!section) return; section->setParent(this); - m_sections.removeAll(section); + if (m_sections.contains(section)) + return; m_sections.append(section); + connect (section, SIGNAL(enabledChanged()), this, SIGNAL(enabledChanged())); } void ModuleInfo::setDescription(const QString &newDescription) @@ -70,10 +72,19 @@ QList ModuleInfo::sections() const bool ModuleInfo::enabled() const { - return m_enabled; + for (auto s : m_sections) { + if (s->enabled()) + return true; + } + return false; } -void ModuleInfo::setEnabled(bool newEnabled) +QString ModuleInfo::iconSource() const { - m_enabled = newEnabled; + return m_iconSource; +} + +void ModuleInfo::setIconSource(const QString &newIconSource) +{ + m_iconSource = newIconSource; } diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index f11713e..db282a3 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -33,10 +33,11 @@ class ModuleInfo : public QObject { Q_OBJECT - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(QString description READ description CONSTANT) Q_PROPERTY(QList sections READ sections CONSTANT) + Q_PROPERTY(QString iconSource READ iconSource CONSTANT) public: explicit ModuleInfo(QObject *parent = nullptr); @@ -62,7 +63,13 @@ public: QList sections() const; bool enabled() const; - void setEnabled(bool newEnabled); + + /** + * Sets the path to the icon for this module. + * This should be part of the QRC system and inside the module dir. + */ + void setIconSource(const QString &newIconSource); + QString iconSource() const; signals: void enabledChanged(); @@ -72,7 +79,7 @@ private: QString m_title; QString m_description; QString m_translationUnit; - bool m_enabled = true; + QString m_iconSource; QList m_sections; }; diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index 829d9c4..9c4d649 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -48,6 +48,19 @@ ModuleSection::SectionType ModuleSection::type() const return m_type; } +bool ModuleSection::enabled() const +{ + return m_enabled; +} + +void ModuleSection::setEnabled(bool on) +{ + if (m_enabled == on) + return; + m_enabled = on; + emit enabledChanged(); +} + void ModuleSection::setStartQMLFile(const QString &filename) { m_startQMLfile = filename; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 0d1cebc..bbf3bbc 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -26,6 +26,7 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) + Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) public: /// The placement in the main app of this section. enum SectionType { @@ -59,14 +60,25 @@ public: QString startQMLFile() const; SectionType type() const; + bool isSendMethod() const { + return m_type == SendMethod; + } + + bool enabled() const; + void setEnabled(bool on); + +signals: + void enabledChanged(); private: const SectionType m_type; QString m_text; QString m_subtext; QString m_startQMLfile; + bool m_enabled = true; QStringList m_requiredModules; + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) }; #endif -- 2.54.0 From 8cef14fd80c36630d519e82863e450495ce7fd3e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Jun 2023 23:44:28 +0200 Subject: [PATCH 0578/1428] Remove dead code --- guis/Flowee/ImageButton.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index dde45b3..1040c26 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -86,9 +86,4 @@ QQC2.Control { anchors.centerIn: parent } } - - Label { - id: textArea - - } } -- 2.54.0 From 5a8d22c05e215426a7e2e55b6646c4e79f86522e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 13:01:17 +0200 Subject: [PATCH 0579/1428] Docs. --- src/ModuleManager.h | 10 ++++++++-- src/ModuleSection.h | 10 ++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 9d222c2..0d209c9 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -27,15 +27,21 @@ class ModuleManager : public QObject { Q_OBJECT + /** + * This property holds all the registered modules, regardless of being enabled or not. + */ Q_PROPERTY(QList registeredModules READ registeredModules CONSTANT) + /** + * This property holds all the module-sections which are enabled and have the type 'send-menu'. + * \see ModduleSection::SectionType + */ Q_PROPERTY(QList sendMenuItems READ sendMenuSections CONSTANT) public: explicit ModuleManager(QObject *parent = nullptr); + /// This is automatically called by a module at statup to register itself. void load(const char *translationUnit, const std::function &function); - // void registerModule(ModuleInfo *info); - QList registeredModules() const; // lists per type diff --git a/src/ModuleSection.h b/src/ModuleSection.h index bbf3bbc..449e53b 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -20,6 +20,13 @@ #include +/** + * The module section represts a single Hook into the Flowee Pay user interface. + * This class fits in the ModuleInfo and ModuleManager framework for modules. + * + * A module can fit into one or more placs in the UI and the user can enable those + * separately in the modules overview. + */ class ModuleSection : public QObject { Q_OBJECT @@ -65,6 +72,9 @@ public: } bool enabled() const; + /** + * This is set by the user or restored from the save file upon restart. + */ void setEnabled(bool on); signals: -- 2.54.0 From 8d4b4ea7ca11e431a279d6cbebbdb4bfe665e803 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 13:02:38 +0200 Subject: [PATCH 0580/1428] Persist user choice of which module is enabled Code the load/save methods for storing module enabled-ness --- src/ModuleManager.cpp | 164 ++++++++++++++++++++++++++++++++++++++---- src/ModuleManager.h | 10 ++- src/ModuleSection.h | 3 +- 3 files changed, 159 insertions(+), 18 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index bed2c79..bdcf879 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -18,16 +18,29 @@ #include "ModuleManager.h" #include -#include - #include #include #include #include -#include +#include +#include +#include +#include +#include +#include + +#include +#include #include +enum ModuleConfigSaveTags { + ModuleId = 1, + ModuleSectionId, + ModuleSectionEnabled +}; + + ModuleManager::ModuleManager(QObject *parent) : QObject(parent) { @@ -39,26 +52,42 @@ ModuleManager::ModuleManager(QObject *parent) m_configFile = path + '/' + "modules.conf"; } +#ifdef TARGET_OS_Android + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) + save(); + }); +#endif + extern void load_all_modules(ModuleManager*); // the load_all_modules method is generated by cmake in the modules builddir. // it essentially makes each module call 'load' on this class. load_all_modules(this); + load(); // restore user settings. - QFile config(m_configFile); - if (config.open(QIODevice::ReadOnly)) { - // TODO - - } - // for now we just assume all are enabled ;-) - for (auto *m : m_modules) { + // connect to signals of the sections so we tell the QML to get the new + // filtered list on the user enabling / disabling something. + for (const auto *m : m_modules) { for (auto *s : m->sections()) { - if (s->type() == ModuleSection::SendMethod) { - m_sendMenuSections.append(s); - } + connect (s, &ModuleSection::enabledChanged, this, [=]() { + switch (s->type()) { + case ModuleSection::SendMethod: + emit sendMenuSectionsChanged(); + default: + break; + } + }); } } } +ModuleManager::~ModuleManager() +{ + save(); +} + void ModuleManager::load(const char *translationUnit, const std::function &function) { if (translationUnit) { @@ -83,12 +112,117 @@ void ModuleManager::load(const char *translationUnit, const std::function ModuleManager::sendMenuSections() const +void ModuleManager::load() { - return m_sendMenuSections; + // enforce default off, unless the user explicitly asks. + for (const auto *m : m_modules) { + for (auto *s : m->sections()) { + s->setEnabled(false); + } + } + + std::ifstream in(m_configFile.toStdString()); + if (!in.is_open()) + return; + + auto dataSize = boost::filesystem::file_size(m_configFile.toStdString()); + Streaming::BufferPool pool(dataSize); + in.read(pool.data(), dataSize); + Streaming::MessageParser parser(pool.commit(dataSize)); + const ModuleInfo *info = nullptr; + int type = -1; + while (parser.next() == Streaming::FoundTag) { + if (parser.tag() == ModuleId) { + info = nullptr; + type = -1; + for (const auto *module : m_modules) { + const QString wantedId = QString::fromUtf8(parser.stringData()); + if (module->id() == wantedId) { + info = module; + break; + } + } + } + else if (parser.tag() == ModuleSectionId) { + type = parser.intData(); + } + else if (parser.tag() == ModuleSectionEnabled) { + for (auto *s : info->sections()) { + if (s->type() == type) { + s->setEnabled(parser.boolData()); + break; + } + } + } + } +} + +void ModuleManager::save() const +{ + int saveFileSize = 100; + for (const auto *m : m_modules) { + saveFileSize += m->id().size() * 3; + saveFileSize += m->sections().size() * 5; + } + + Streaming::BufferPool pool(saveFileSize); + Streaming::MessageBuilder builder(pool); + for (const auto *m : m_modules) { + builder.add(ModuleId, m->id().toStdString()); + for (const auto *s : m->sections()) { + builder.add(ModuleSectionId, s->type()); + if (s->enabled()) + builder.add(ModuleSectionEnabled, s->enabled()); + } + } + + auto data = builder.buffer(); + + // hash the new file and check if its different lest we can skip saving + QFile origFile(m_configFile); + if (origFile.open(QIODevice::ReadOnly)) { + CRIPEMD160 fileHasher; + auto origContent = origFile.readAll(); + fileHasher.write(origContent.data(), origContent.size()); + char fileHash[CRIPEMD160::OUTPUT_SIZE]; + fileHasher.finalize(fileHash); + + CRIPEMD160 memHasher; + memHasher.write(data.begin(), data.size()); + char memHash[CRIPEMD160::OUTPUT_SIZE]; + memHasher.finalize(memHash); + if (memcmp(fileHash, memHash, CRIPEMD160::OUTPUT_SIZE) == 0) { + // no changes, so don't write. + return; + } + } + try { + auto configFile = m_configFile.toStdString(); + auto configFileNew = configFile + "~"; + boost::filesystem::remove(configFileNew); + std::ofstream outFile(configFileNew); + outFile.write(data.begin(), data.size()); + outFile.flush(); + outFile.close(); + boost::filesystem::rename(configFileNew, configFile); + } catch (const std::exception &e) { + logFatal() << "Failed to save the wallet.dat. Reason:" << e; + } } QList ModuleManager::registeredModules() const { return m_modules; } + +QList ModuleManager::sendMenuSections() const +{ + QList answer; + for (const auto *m : m_modules) { + for (auto *s : m->sections()) { + if (s->enabled() && s->type() == ModuleSection::SendMethod) + answer.append(s); + } + } + return answer; +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 0d209c9..d86177b 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -35,9 +35,10 @@ class ModuleManager : public QObject * This property holds all the module-sections which are enabled and have the type 'send-menu'. * \see ModduleSection::SectionType */ - Q_PROPERTY(QList sendMenuItems READ sendMenuSections CONSTANT) + Q_PROPERTY(QList sendMenuItems READ sendMenuSections NOTIFY sendMenuSectionsChanged) public: explicit ModuleManager(QObject *parent = nullptr); + ~ModuleManager(); /// This is automatically called by a module at statup to register itself. void load(const char *translationUnit, const std::function &function); @@ -47,9 +48,14 @@ public: // lists per type QList sendMenuSections() const; +signals: + void sendMenuSectionsChanged(); + private: + void load(); + void save() const; + QList m_modules; - QList m_sendMenuSections; QString m_configFile; }; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 449e53b..d72b651 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -37,7 +37,8 @@ class ModuleSection : public QObject public: /// The placement in the main app of this section. enum SectionType { - SendMethod, //< A specific way to send coin, shown in list of send-methods. + SendMethod, //< A specific way to send coin, shown in list of send-methods. + // BuildTxComponent, //< Inside the 'build-transactions' this adds a new buildingblock. }; explicit ModuleSection(SectionType type, QObject *parent = nullptr); -- 2.54.0 From 89ff36c311b0a15c8601ea581e1d3d1262e59841 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 15:48:16 +0200 Subject: [PATCH 0581/1428] Make the ExploreModules QML prettier This includes a self-drawn SVG of a ribbon, configurable text on top and just a simple checkbox to recognize that at this time there is only one category-type. KISS. --- guis/mobile.qrc | 1 + guis/mobile/ExploreModules.qml | 209 ++++++++++++++++++++------------- guis/mobile/images/ribbon.svg | 62 ++++++++++ 3 files changed, 189 insertions(+), 83 deletions(-) create mode 100644 guis/mobile/images/ribbon.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 96ad731..b6e25b4 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -34,6 +34,7 @@ mobile/images/tx-move-light.svg mobile/images/edit-clipboard.svg mobile/images/edit-clipboard-light.svg + mobile/images/ribbon.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 9f6b2d6..e6840c8 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -23,102 +23,145 @@ Page { id: root headerText: qsTr("Explore") - Flickable { + Item { anchors.fill: parent - anchors.topMargin: 10 - anchors.bottomMargin: 10 - clip: true - contentWidth: width - contentHeight: content.height + // clip: true - Column { - id: content - width: parent.width - spacing: 15 + Flickable { + anchors.fill: parent + anchors.topMargin: 10 + anchors.bottomMargin: 10 + contentWidth: width + contentHeight: content.height - Repeater { - model: ModuleManager.registeredModules - Rectangle { - width: root.width - 30 - height: 35 + titleLabel.height + statusField.height + Math.min(120, descriptionLabel.implicitHeight) - radius: 20 - color: palette.alternateBase - border.width: 1 - border.color: palette.midlight + Column { + id: content + width: parent.width + spacing: 15 - Flowee.Label { - id: titleLabel - y: 15 - text: modelData.title - font.bold: true - anchors.horizontalCenter: parent.horizontalCenter - } - Image { - x: 10 - width: 42 - anchors.top: descriptionFrame.top - source: modelData.iconSource - visible: modelData.iconSource !== "" - smooth: true - fillMode: Image.PreserveAspectFit - } + Repeater { + model: ModuleManager.registeredModules + Rectangle { + width: root.width - 30 + height: 35 + titleLabel.height + statusField.height + Math.min(120, descriptionLabel.implicitHeight) + radius: 20 + color: palette.alternateBase + border.width: 1 + border.color: palette.midlight - Item { - id: descriptionFrame - anchors.top: titleLabel.bottom - anchors.topMargin: 10 - width: parent.width - (modelData.iconSource === "" ? 0 : 52) - clip: true - anchors.bottom: statusField.top - anchors.bottomMargin: 10 - anchors.right: parent.right - Rectangle { - color: palette.alternateBase - anchors.fill: descriptionLabel - } Flowee.Label { - id: descriptionLabel - width: parent.width - 20 - x: 10 - text: modelData.description - horizontalAlignment: Text.AlignJustify - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - MouseArea { - anchors.fill: parent - onClicked: descriptionFrame.clip = !descriptionFrame.clip - } + id: titleLabel + y: 15 + text: modelData.title + font.bold: true + anchors.horizontalCenter: parent.horizontalCenter } - } - - Item { - id: statusField - width: parent.width - height: 40 - anchors.bottom: parent.bottom - Flowee.CheckBox { - anchors.verticalCenter: parent.verticalCenter + Image { x: 10 - checked: modelData.enabled - enabled: false + width: 42 + anchors.top: descriptionFrame.top + source: modelData.iconSource + visible: modelData.iconSource !== "" + smooth: true + fillMode: Image.PreserveAspectFit } - Flowee.ImageButton { - source: "qrc:/sending" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - iconSize: 24 + Item { + id: descriptionFrame + anchors.top: titleLabel.bottom + anchors.topMargin: 10 + width: parent.width - (modelData.iconSource === "" ? 0 : 52) + clip: true + anchors.bottom: statusField.top + anchors.bottomMargin: 10 anchors.right: parent.right - anchors.rightMargin: 15 - anchors.verticalCenter: parent.verticalCenter - visible: section != null - opacity: (visible && section.enabled) ? 1 : 0.3 - property QtObject section: { - for (let s of modelData.sections) { - if (s.isSendMethod) - return s; - } - return null; + Rectangle { + color: palette.alternateBase + anchors.fill: descriptionLabel } + Flowee.Label { + id: descriptionLabel + width: parent.width - 20 + x: 10 + text: modelData.description + horizontalAlignment: Text.AlignJustify + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + MouseArea { + anchors.fill: parent + onClicked: descriptionFrame.clip = !descriptionFrame.clip + } + } + } - onClicked: if (section != null) section.enabled = !section.enabled + Image { + id: enabledIndicator + source: "qrc:/ribbon.svg" + smooth: true + anchors.right: parent.right + anchors.rightMargin: -8 + y: -9 + width: 90 + height: 90 + opacity: modelData.enabled ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { NumberAnimation { } } + + Item { + rotation: 45 + width: 80 + height: 22 + x: 16 + y: 22 + clip: true + + Text { + color: "white" + text: qsTr("ON", "Enabled. SHORT TEXT!"); + font.bold: true + anchors.centerIn: parent + } + } + } + + Item { + id: statusField + width: parent.width + height: 40 + anchors.bottom: parent.bottom + + Row { + spacing: 10 + anchors.fill: parent + anchors.leftMargin: 10 + + Flowee.CheckBox { + anchors.verticalCenter: parent.verticalCenter + checked: modelData.enabled + onClicked: { + for (let s of modelData.sections) { + s.enabled = checked + } + } + } + + // one icon per section-type + Image { + source: "qrc:/sending" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 16 + height: width + anchors.verticalCenter: parent.verticalCenter + visible: section != null + opacity: (visible && section.enabled) ? 0.8 : 0.3 + property QtObject section: { + // find the section our icon represents. + for (let s of modelData.sections) { + if (s.isSendMethod) + return s; + } + return null; // module doesn't have such a section. + } + } + } } } } diff --git a/guis/mobile/images/ribbon.svg b/guis/mobile/images/ribbon.svg new file mode 100644 index 0000000..e066e02 --- /dev/null +++ b/guis/mobile/images/ribbon.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.54.0 From 030380dd4415f227c4e0117d934f2257990d559c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 19:00:08 +0200 Subject: [PATCH 0582/1428] KISS --- guis/mobile/SendTransactionsTab.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 40434a6..3446ec9 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -31,7 +31,7 @@ FocusScope { } TextButton { - text: qsTr("Scan a QR code") + text: qsTr("Start Payment") showPageIcon: true onClicked: thePile.push("PayWithQR.qml") } -- 2.54.0 From 2f51d08bde258ca99e48868262a6159bb0c5b01b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 20:50:29 +0200 Subject: [PATCH 0583/1428] Improve payment-from-clipboard If no full address is found in the clipboard, use the chain prefix to validate the address and use that instead. --- src/CameraController.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 5a5f497..17dd71a 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -18,6 +18,7 @@ */ #include "CameraController.h" #include "QRScanner.h" +#include "FloweePay.h" #include "qclipboard.h" #include @@ -539,17 +540,39 @@ bool CameraController::importScanFromClipboard() if (d->scanRequest->scanType() != QRScanner::PaymentDetails) return false; + QString result; + const QString prefix = QString::fromStdString(chainPrefix()) + ":"; auto text = QGuiApplication::clipboard()->text(); - auto index = text.indexOf("bitcoincash:"); + auto index = text.indexOf(prefix); if (index >= 0) { auto end = text.indexOf(' ', index + 10); - d->scanRequest->setScanResult(text.mid(index, end), QRScanner::Clipboard); + result = text.mid(index, end); + } + else { + // find the address if it doesn't have the prefix. + for (auto word : text.split(' ', Qt::SkipEmptyParts)) { + if (word.length() > 40 && word.length() < 50) { + auto id = FloweePay::instance()->identifyString(prefix + word); + if (id == WalletEnums::CashPKH || id == WalletEnums::CashSH) { + result = prefix + word; + break; + } + } + } + } + + if (!result.isEmpty()) { + logInfo() << "Processing clipboard segment for payment:" << result; + d->scanRequest->setScanResult(result, QRScanner::Clipboard); // stop camera d->cameraStarted = false; emit cameraActiveChanged(); - return true; + if (d->m_scanningThread == nullptr) { + // then the above would have no effect; + qrScanFinished(); + } } - return false; + return !result.isEmpty(); } void CameraController::setCamera(QObject *object) -- 2.54.0 From 5c1952c2676bda5e3ff02d699f36b5531c656f1a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Jun 2023 21:30:38 +0200 Subject: [PATCH 0584/1428] Update translation units --- .gitignore | 2 + CMakeLists.txt | 5 +- translations/module-build-transaction.ts | 154 ----------------------- 3 files changed, 6 insertions(+), 155 deletions(-) delete mode 100644 translations/module-build-transaction.ts diff --git a/.gitignore b/.gitignore index b79dc62..ff42d23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ floweepay-common.ts floweepay-desktop.ts floweepay-mobile.ts +module-build-transaction.ts +module-example.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 25fd49b..895532d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,9 +99,12 @@ if(NOT ANDROID) COMMAND lupdate src guis/widgets.qrc -ts translations/floweepay-common.ts COMMAND lupdate guis/mobile.qrc -ts translations/floweepay-mobile.ts - COMMAND lupdate modules/build-transaction/data.qrc + COMMAND lupdate modules/build-transaction/build-transactions-data.qrc modules/build-transaction/BuildTransactionModuleInfo.cpp -ts translations/module-build-transaction.ts + COMMAND lupdate modules/example/example-data.qrc + modules/example/ExampleModuleInfo.cpp + -ts translations/module-example.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/translations/module-build-transaction.ts b/translations/module-build-transaction.ts deleted file mode 100644 index f46af6f..0000000 --- a/translations/module-build-transaction.ts +++ /dev/null @@ -1,154 +0,0 @@ - - - - - BuildTransactionModuleInfo - - - This is a nice text - - - - - PayToOthers - - - Build Transaction - - - - - Building Error - error during build - - - - - Add Payment Detail - page title - - - - - - Add Destination - - - - - an address to send money to - - - - - Confirm Sending - confirm we want to send the transaction - - - - - TXID - - - - - Copy transaction-ID - - - - - Fee - - - - - Transaction size - - - - - %1 bytes - - - - - Fee per byte - - - - - %1 sat/byte - fee - - - - - Destination - - - - - unset - indication of empty - - - - - invalid - address is not correct - - - - - - Copy Address - - - - - Edit Destination - - - - - Send All - all money in wallet - - - - - Bitcoin Cash Address - - - - - Warning - - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - - - - - I am certain - - - - - Drag to Edit - - - - - Drag to Delete - - - - - Prepare Payment... - - - - -- 2.54.0 From 92b3b260c3a6a5f24266ebca5c47622ce7e2a475 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 16:20:42 +0200 Subject: [PATCH 0585/1428] Disable clearing if there is nothing to clear. --- guis/mobile/ReceiveTab.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 51bea36..abb0b8d 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -178,6 +178,7 @@ FocusScope { Flowee.Button { anchors.right: parent.right text: qsTr("Clear") + enabled: description.text != "" || priceInput.paymentBackend.paymentAmount > 0; onClicked: { request.clear(); request.account = portfolio.current; -- 2.54.0 From da864543c0239aef91177d56f1d79e992350424a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 16:58:36 +0200 Subject: [PATCH 0586/1428] UX; pressing 'continue' on receive goes home now --- guis/mobile/ReceiveTab.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index abb0b8d..6d1e362 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -398,6 +398,7 @@ FocusScope { priceInput.paymentBackend.paymentAmount = 0; request.account = portfolio.current; request.start(); + switchToTab(0); // go to the 'main' tab. } } } -- 2.54.0 From 98e193f4cab4dd3d56f4ffc6483c66604df22bf6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 17:12:03 +0200 Subject: [PATCH 0587/1428] Copy the userComment here for user-editing When the payment protocol provides a comment, lets copy it for the user to see and maybe edit. --- guis/Flowee/BroadcastFeedback.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index b010197..b3feecf 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -172,6 +172,7 @@ QQC2.Control { onTextChanged: payment.userComment = text placeholderText: qsTr("Add a personal note") placeholderTextColor: "#3e3e3e" + text: payment.userComment } RowLayout { -- 2.54.0 From 9dcd608e69115e8961b3a8f92e049f9b03d36698 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 17:49:03 +0200 Subject: [PATCH 0588/1428] Style the Flowee button. The buttons on Android are somehow atrocious, lets style it ourselves for consistency and actually being able to see the disabled button. --- guis/Flowee/Button.qml | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/guis/Flowee/Button.qml b/guis/Flowee/Button.qml index e92d791..3419c72 100644 --- a/guis/Flowee/Button.qml +++ b/guis/Flowee/Button.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,19 +23,33 @@ import "../ControlColors.js" as ControlColors // between enabled and disabled buttons. QQC2.Button { id: button - onEnabledChanged: updateColors(); - Connections { - target: Pay - function onUseDarkSkinChanged() { updateColors(); } + + contentItem: Text { + text: button.text + font: button.font + color: { + if (!button.enabled) + return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 1.8) : Qt.lighter(palette.buttonText, 2); + return palette.buttonText; + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight } - function updateColors() { - ControlColors.applySkin(this); - if (!enabled) { - palette.buttonText = Pay.useDarkSkin - ? Qt.darker(palette.buttonText, 1.8) - : Qt.lighter(palette.buttonText, 2) - palette.button = Qt.darker(palette.button, 1.2) + background: Rectangle { + implicitWidth: 90 + implicitHeight: contentItem.implicitHeight + 10 + opacity: enabled ? 1 : 0.8 + border.color: Pay.useDarkSkin ? Qt.lighter(palette.button) + : Qt.darker(palette.button); + color: { + if (button.down || button.checked) + return Pay.useDarkSkin ? Qt.darker(palette.button) + : Qt.lighter(palette.button); + return palette.button } + border.width: 1.5 + radius: 2 } } -- 2.54.0 From d30381eb7d55899929c7ffb63417da72c0d07d84 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 19:11:12 +0200 Subject: [PATCH 0589/1428] Copy the payment-request-comment to wallet-tx When the user typed a request message, make sure that the message is attached to the transaction that comes in which pays the request. --- src/PaymentRequest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 41f15d6..0938a0b 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -138,6 +138,12 @@ void PaymentRequest::setAccount(QObject *account) if (tx.state != WalletKeyView::UTXORejected) { seen += tx.amount; } + // copy the user typed payment request to the wallet + // for paying transactions. + if (!m_message.isEmpty()) { + Wallet::OutputRef ref(tx.ref); + m_account->wallet()->setTransactionComment(ref.txIndex(), m_message); + } } if (seen != m_amountSeen) { -- 2.54.0 From 44379f2650faf1a337dd6f7937b1f1971b0f16df Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 21:57:49 +0200 Subject: [PATCH 0590/1428] Defensive programming. Don't assume the module exist on loading its config. --- src/ModuleManager.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index bdcf879..f4d2b64 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -147,10 +147,12 @@ void ModuleManager::load() type = parser.intData(); } else if (parser.tag() == ModuleSectionEnabled) { - for (auto *s : info->sections()) { - if (s->type() == type) { - s->setEnabled(parser.boolData()); - break; + if (info) { + for (auto *s : info->sections()) { + if (s->type() == type) { + s->setEnabled(parser.boolData()); + break; + } } } } -- 2.54.0 From 7be3f7bd2605150cd6b8da4d91d6f4645289a107 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Jun 2023 23:05:15 +0200 Subject: [PATCH 0591/1428] Remove stray rotate property. --- guis/mobile/QRScannerOverlay.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 3ea4d09..2ea0ced 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -191,7 +191,6 @@ FocusScope { VideoOutput { id: videoOutput fillMode: VideoOutput.PreserveAspectCrop - orientation: 90 width: parent.width height: parent.height } -- 2.54.0 From d94f1faae2cdfe8bf84e282826277d19494db507 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 14:53:22 +0200 Subject: [PATCH 0592/1428] Work on QR scanning This removes the dependency on the QrEncode lib and uses the fact that our scanning lib also is capable of making QRs. We now allow the QR to get its color inverted for dark mode users, making it less bright to show a QR. This introduces a new QR-creation mode which allows spaces and other chars to be in there directly without being URL-encoded. This improves the QR showing of the user's seed-phrase (in the backup screen). This adds the ability to scan a seed phrase with your camera by adding a button to the wallet import page. This also adds support for the QR format that the bitcoin com wallet uses in its QR for seed phrases. --- CMakeLists.txt | 10 ++--- android/docker/Dockerfile | 2 - android/docker/scripts/buildQrEncode.sh | 25 ------------ guis/Flowee/QRWidget.qml | 4 ++ guis/mobile/AccountPageListItem.qml | 1 + guis/mobile/ImportWalletPage.qml | 14 +++++++ src/CameraController.cpp | 28 +++++++++++-- src/QRCreator.cpp | 53 +++++++++++++++---------- src/QRCreator.h | 11 ++++- src/main.cpp | 3 +- 10 files changed, 90 insertions(+), 61 deletions(-) delete mode 100755 android/docker/scripts/buildQrEncode.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 895532d..98a0dfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,10 +47,9 @@ if (ANDROID) endif() else () find_package(Qt6 COMPONENTS DBus LinguistTools) - find_package(ZXing) + find_package(ZXing REQUIRED) endif() find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) -find_package(QREncode REQUIRED) function(download_file url path) if (NOT EXISTS "${path}") @@ -145,14 +144,14 @@ if (BUILD_DESKTOP_PAY) ) add_executable(pay ${SOURCES_PAY}) set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") - target_link_libraries(pay PRIVATE pay_lib Qt6::Svg) + target_link_libraries(pay PRIVATE pay_lib ZXing::ZXing Qt6::Svg) install(TARGETS pay DESTINATION bin) endif() ###### Pay-mobile executable option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ON) -if (NOT "${Qt6Multimedia_FOUND}" OR NOT "${ZXing_FOUND}") +if (NOT "${Qt6Multimedia_FOUND}") set (BUILD_MOBILE_PAY OFF) endif () @@ -326,9 +325,6 @@ endif() if (NOT ${Qt6Multimedia_FOUND}) message("ww Missing QtMultimedia libs, not building Pay for mobile ") endif () -if (NOT ${ZXing_FOUND}) - message("ww Missing ZXing lib, not building Pay for mobile ") -endif () if (${BUILD_MOBILE_PAY}) message ("-> Building Pay for mobile") if (ANDROID AND DEFINED MOBILE_PAY_I18N_QRC) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 8825b52..44016ce 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -56,8 +56,6 @@ RUN mkdir -p /usr/local/cache \ && rm -rf ~builduser/* \ && /usr/local/bin/buildBoost.sh \ && rm -rf ~builduser/* \ - && /usr/local/bin/buildQrEncode.sh \ - && rm -rf ~builduser/* \ && /usr/local/bin/buildZXing.sh \ && rm -rf ~builduser/* \ && rm -rf /usr/local/cache diff --git a/android/docker/scripts/buildQrEncode.sh b/android/docker/scripts/buildQrEncode.sh deleted file mode 100755 index 454e283..0000000 --- a/android/docker/scripts/buildQrEncode.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -VERSION=4.1.1 - -echo "Based on qrencode version $VERSION" >> /etc/versions -source /etc/profile - -cd /usr/local/cache -if ! test -f qrencode-${VERSION}.tar.bz2; then - curl -O https://fukuchi.org/works/qrencode/qrencode-${VERSION}.tar.bz2 -fi - -cd ~builduser -tar xf /usr/local/cache/qrencode-${VERSION}.tar.bz2 - -cd qrencode-$VERSION -mkdir build -cd build -cmake -DCMAKE_TOOLCHAIN_FILE=/opt/android-qt6/lib/cmake/Qt6/qt.toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX=/opt/android-qrencode \ - -DWITHOUT_PNG=ON \ - -G Ninja \ - .. - -ninja install diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 4c99323..51417c0 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -24,6 +24,8 @@ Item { property alias textVisible: addressLine.visible property string qrText: "" + property bool useRawString: false + implicitWidth: qrImage.width implicitHeight: qrImage.height + (addressLine.visible ? addressLine.height : 0) @@ -39,6 +41,8 @@ Item { var text = root.qrText; if (text === "") return ""; + if (useRawString) + return "image://qr-raw/" + text; return "image://qr/" + text; } smooth: false diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 8249b0a..301cb60 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -106,6 +106,7 @@ QQC2.Control { id: seedQr qrSize: 250 textVisible: false + useRawString: true } } } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index cfbfe7a..020aa46 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -58,7 +58,21 @@ Page { text: importAccount.finished ? "✔" : " " color: Pay.useDarkSkin ? "#37be2d" : "green" font.pixelSize: 24 + anchors.right: startScan.left + } + Flowee.ImageButton { + id: startScan + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + onClicked: scanner.start(); + iconSize: 25 + anchors.verticalCenter: secrets.verticalCenter anchors.right: parent.right + + QRScanner { + id: scanner + scanType: QRScanner.Seed + onFinished: if (scanResult !== "") secrets.text = scanResult; + } } } } diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 17dd71a..fd5f023 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -171,7 +171,7 @@ void CameraControllerPrivate::initCamera() if (preferredIsCheap && !formatIsCheap) continue; // avoid going for the biggest feed, but not too small either. - if (oldSize.width() < 600 || (size.width() < oldSize.width() && size.width() >= 600)) { + if (oldSize.width() < 800 || (size.width() < oldSize.width() && size.width() >= 800)) { preferred = format; logInfo() << "picked"; } @@ -268,9 +268,28 @@ void QRScanningThread::run() switch (m_scanType) { case QRScanner::Seed: { - // We can't be certain something is a seed here, we can just check the basics - // only normal characters and spaces + /* + * The Seed QR would obviously just use the 12 or 24 words sentence in a QR. + * Simple. + * + * But a pretty big wallet has instead put a bit more in the QR which ends up having + * the following content: + * + * 1|this holds twelve words|livenet|m/44'/0'/0'|false + * + * No clue what the leading 1 and the trailing false are about. All wallets I tried + * have those :shrug: + * + * Lets try to recognize those two types of QR. + * ------ + * While seeds can be verified with their checksum, here we just check if the general + * pattern fits. + * Only normal characters and spaces are allowed in the basic seed, and 12 words or 24 words. + */ QString possibleSeed = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + if (possibleSeed.startsWith("1|") && possibleSeed.endsWith("|livenet|m/44'/0'/0'|false")) + possibleSeed = possibleSeed.mid(2, possibleSeed.length() - 28); + int wordCount = 0; bool seenSpace = false; bool failedChecks = false; @@ -290,9 +309,12 @@ void QRScanningThread::run() if (failedChecks) break; } + if (seenSpace == false && wordCount > 0) + ++wordCount; // one more word not registered due to lack of space after. if (!failedChecks && (wordCount == 12 || wordCount == 24)) { text = possibleSeed; return; // makes the 'QThread::finished()' signal get emitted. + } break; } diff --git a/src/QRCreator.cpp b/src/QRCreator.cpp index 1191dd6..b4f5d61 100644 --- a/src/QRCreator.cpp +++ b/src/QRCreator.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2018-2022 Tom Zander + * Copyright (C) 2018-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,13 +16,17 @@ * along with this program. If not, see . */ #include "QRCreator.h" +#include "FloweePay.h" #include -// cmake requires the presence of the lib. -#include +// cmake ensures the presence of the ZXing lib. +#include +#include +#include -QRCreator::QRCreator() - : QQuickImageProvider(QQmlImageProviderBase::Image) +QRCreator::QRCreator(QRType type) + : QQuickImageProvider(QQmlImageProviderBase::Image), + m_type(type) { } @@ -30,22 +34,29 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ { Q_UNUSED(size); Q_UNUSED(requestedSize); - QUrl url(id); // go via URL to encode spaces and special chars - QRcode *code = QRcode_encodeString(url.toEncoded().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); - if (code == nullptr) { // failed to encode. - QImage blank = QImage(37, 37, QImage::Format_RGB32); - blank.fill(0x232629); // gray - return blank; + std::string data; // assumed utf8 by zxing + if (m_type == URLEncoded) { + QUrl url(id); // go via URL to encode spaces and special chars + data = url.toEncoded(); + } else if (m_type == RawString) { + data = id.toUtf8(); } - QImage result = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); - result.fill(0xffffff); - unsigned char *p = code->data; - for (int y = 0; y < code->width; y++) { - for (int x = 0; x < code->width; x++) { - result.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); - ++p; - } - } - QRcode_free(code); + + auto writer = ZXing::MultiFormatWriter(ZXing::BarcodeFormat::QRCode).setMargin(16); + ZXing::BitMatrix matrix = writer.encode(data, 250, 250); + + QImage result = QImage(matrix.height(), matrix.width(), QImage::Format_RGB32); + constexpr uint Black = 0xFF000000; + constexpr uint White = 0xFFFFFFFF; + uint black = Black; + uint white = White; + // inverted QRs are perfectly legal, lets make it nice for our users. + if (FloweePay::instance()->darkSkin()) + qSwap(black, white); + + for (int y = 0; y < matrix.height(); ++y) + for (int x = 0; x < matrix.width(); ++x) + result.setPixel(x, y, matrix.get(x, y) ? black : white); + return result; } diff --git a/src/QRCreator.h b/src/QRCreator.h index 817f956..5e5590a 100644 --- a/src/QRCreator.h +++ b/src/QRCreator.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2018-2021 Tom Zander + * Copyright (C) 2018-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,9 +34,16 @@ class QRCreator : public QQuickImageProvider { public: - QRCreator(); + enum QRType { + URLEncoded, + RawString + }; + QRCreator(QRType type); QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + +private: + QRType m_type; }; #endif diff --git a/src/main.cpp b/src/main.cpp index bb27e66..8057d00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,7 +142,8 @@ int main(int argc, char *argv[]) #endif auto app = FloweePay::instance(); - engine.addImageProvider(QLatin1String("qr"), new QRCreator()); + engine.addImageProvider(QLatin1String("qr"), new QRCreator(QRCreator::URLEncoded)); + engine.addImageProvider(QLatin1String("qr-raw"), new QRCreator(QRCreator::RawString)); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel; -- 2.54.0 From bdad39848bd7d7882c4895abc9f93d6ae0931ed9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 15:58:51 +0200 Subject: [PATCH 0593/1428] Make MultilineTextField usable for code setting text This stops the widget being confused with regards to the placeholder text when the main text is set by code instead of by user input. --- guis/Flowee/MultilineTextField.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index f3de683..c2bcf4f 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -49,7 +49,10 @@ QQC2.Control { } Connections { - function onTextChanged() { textEdit.text = text; } + function onTextChanged() { + textEdit.showingPlaceholder = false + textEdit.text = text; + } function onPlaceholderTextChanged() { if (textEdit.text === "") { textEdit.showingPlaceholder = true @@ -84,11 +87,11 @@ QQC2.Control { property bool showingPlaceholder: false onActiveFocusChanged: { - if (activeFocus && showingPlaceholder) { + if (activeFocus && showingPlaceholder && text == root.placeholderText) { showingPlaceholder = false; text = "" } - else if (!activeFocus && !showingPlaceholder && text === "") { + else if (!activeFocus && !showingPlaceholder && text === "" && root.placeholderText !== "") { showingPlaceholder = true; text = root.placeholderText } -- 2.54.0 From 1377c015318440ab5a94591a6b6c3ffc66d83f94 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 15:59:07 +0200 Subject: [PATCH 0594/1428] Slight alignment fixlet. --- guis/mobile/ImportWalletPage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 020aa46..34be553 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -65,7 +65,7 @@ Page { source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); onClicked: scanner.start(); iconSize: 25 - anchors.verticalCenter: secrets.verticalCenter + anchors.verticalCenter: feedback.verticalCenter anchors.right: parent.right QRScanner { -- 2.54.0 From 174c5f127f49342f672f141591a4c2455d248dc3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 17:48:47 +0200 Subject: [PATCH 0595/1428] Deal a little better with crappy camera firmware. --- src/CameraController.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index fd5f023..8cabebe 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -171,7 +171,8 @@ void CameraControllerPrivate::initCamera() if (preferredIsCheap && !formatIsCheap) continue; // avoid going for the biggest feed, but not too small either. - if (oldSize.width() < 800 || (size.width() < oldSize.width() && size.width() >= 800)) { + if (oldSize.width() < 800 || (size.width() < oldSize.width() && size.width() >= 800) + || (size.width() == oldSize.width() && size.height() > oldSize.height() && size.height() < 1000)) { preferred = format; logInfo() << "picked"; } @@ -210,8 +211,30 @@ void CameraControllerPrivate::checkState() logFatal() << "CameraController found cam error:" << cam->errorString(); cam->setCameraFormat(preferredFormat); - cam->setFocusMode(QCamera::FocusModeAutoNear); // macro focus mode. - cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); // avoid flash + // best too least-acceptbale focus mode. + constexpr QCamera::FocusMode f_modes[] = { QCamera::FocusModeAutoNear, QCamera::FocusModeAuto, + QCamera::FocusModeHyperfocal, QCamera::FocusModeInfinity }; + for (auto m : f_modes) { + if (cam->isFocusModeSupported(m)) { + cam->setFocusMode(m); + break; + } + } + constexpr QCamera::WhiteBalanceMode w_modess[] = { QCamera::WhiteBalanceShade, QCamera::WhiteBalanceAuto }; + for (auto m : w_modess) { + if (cam->isWhiteBalanceModeSupported(m)) { + cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); + break; + } + } + constexpr QCamera::ExposureMode e_modes[] = { QCamera::ExposureBarcode, + QCamera::ExposurePortrait, QCamera::ExposureAuto }; + for (auto m : e_modes) { + if (cam->isExposureModeSupported(m)) { + cam->setExposureMode(m); + break; + } + } cameraStarted = true; QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { currentFrame = frame; -- 2.54.0 From e033c49f0ca3f01ee9c2186df05bbf525dfbab93 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 17:59:54 +0200 Subject: [PATCH 0596/1428] Move ZXing dep from all using apps to just the lib. --- CMakeLists.txt | 6 +++--- src/CMakeLists.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98a0dfd..c776481 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,7 +144,7 @@ if (BUILD_DESKTOP_PAY) ) add_executable(pay ${SOURCES_PAY}) set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP") - target_link_libraries(pay PRIVATE pay_lib ZXing::ZXing Qt6::Svg) + target_link_libraries(pay PRIVATE pay_lib Qt6::Svg) install(TARGETS pay DESTINATION bin) endif() @@ -224,7 +224,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) LIST (APPEND PAY_MOBILE_LIBS Qt6::CorePrivate) message(STATUS "including private QtCore APIs for Android support") endif () - target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) + target_link_libraries(pay_mobile PRIVATE pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" @@ -258,7 +258,7 @@ if(NOT ANDROID AND BUILD_MOBILE_PAY) COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - target_link_libraries(pay_mobile PRIVATE pay_lib ZXing::ZXing Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) + target_link_libraries(pay_mobile PRIVATE pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) install(TARGETS pay_mobile DESTINATION bin) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e46bfc..5eeeff5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,4 +77,4 @@ target_link_libraries(pay_lib flowee_p2p ${OPENSSL_LIBRARIES} ${Boost_LIBRARIES} - Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ${QREncode_LIBRARIES}) + Qt6::Core Qt6::Quick ${Qt6DBus_LIBRARIES} ${PayLib_PRIVATE_LIBS} ZXing::ZXing ) -- 2.54.0 From a1e56528f95f8889096898a55e542f9044ca8304 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 19:15:02 +0200 Subject: [PATCH 0597/1428] new feature: turn on torch on QR scan We now show a button to turn on the from the QR scanning page. --- guis/mobile.qrc | 2 ++ guis/mobile/QRScannerOverlay.qml | 23 ++++++++++++++- guis/mobile/images/flash-light.svg | 4 +++ guis/mobile/images/flash.svg | 4 +++ src/CameraController.cpp | 47 +++++++++++++++++++++++++++++- src/CameraController.h | 7 +++-- 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 guis/mobile/images/flash-light.svg create mode 100644 guis/mobile/images/flash.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index b6e25b4..6149760 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -35,6 +35,8 @@ mobile/images/edit-clipboard.svg mobile/images/edit-clipboard-light.svg mobile/images/ribbon.svg + mobile/images/flash.svg + mobile/images/flash-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 2ea0ced..47a8731 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -121,10 +121,12 @@ FocusScope { Rectangle { + id: pasteFrame x: 50 anchors.bottom: parent.bottom anchors.bottomMargin: 50 visible: CameraController.supportsPaste + radius: 6 width: pasteButton.width height: pasteButton.height color: palette.base @@ -159,6 +161,24 @@ FocusScope { } } } + Rectangle { + id: flashFrame + anchors.top: pasteFrame.top + anchors.right: parent.right + anchors.rightMargin: 50 + radius: 6 + visible: false + + width: flashButton.width + height: flashButton.height + color: palette.base + Flowee.ImageButton { + id: flashButton + source: "qrc:/flash" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + opacity: CameraController.torchEnabled ? 0.3 : 1 + onClicked: CameraController.torchEnabled = !CameraController.torchEnabled + } + } Component { id: videoFeedPanel @@ -172,6 +192,7 @@ FocusScope { function onCameraActiveChanged() { if (CameraController.cameraActive) { camera.start(); + flashFrame.visible = camera.isTorchModeSupported(Camera.TorchOn) } else if (Qt.platform.os !== "linux") { // at least on Linux stopping a camera and turning it on again fails with // "Camera is in use" diff --git a/guis/mobile/images/flash-light.svg b/guis/mobile/images/flash-light.svg new file mode 100644 index 0000000..f39c129 --- /dev/null +++ b/guis/mobile/images/flash-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/flash.svg b/guis/mobile/images/flash.svg new file mode 100644 index 0000000..f4e68af --- /dev/null +++ b/guis/mobile/images/flash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 8cabebe..6365291 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * Copyright (C) 2020 Axel Waggershauser * * This program is free software: you can redistribute it and/or modify @@ -70,6 +70,7 @@ public: bool cameraLoaded = false; bool cameraStarted = false; bool visible = false; + bool torchEnabled = false; int streamWidth = -1; int streamHeight = -1; @@ -578,6 +579,46 @@ bool CameraController::supportsPaste() const return d->scanRequest->scanType() == QRScanner::PaymentDetails; } +bool CameraController::torchEnabled() const +{ + return d->torchEnabled; +} + +void CameraController::setTorchEnabled(bool on) +{ + if (d->torchEnabled == on) + return; + if (!d->cameraStarted) { + assert(d->torchEnabled == false); + return; + } + QCamera *cam = qobject_cast(d->camera); + if (cam == nullptr) + return; + if (cam->isTorchModeSupported(on ? QCamera::TorchOn : QCamera::TorchOff) == false) { + logWarning() << "Trying to toggle torch, but the camera does not support that"; + return; + } + d->torchEnabled = on; + cam->setTorchMode(on ? QCamera::TorchOn : QCamera::TorchOff); + logFatal() << "toggling the torch"; + + if (on) { + if (cam->isWhiteBalanceModeSupported(QCamera::WhiteBalanceFlash)) + cam->setWhiteBalanceMode(QCamera::WhiteBalanceFlash); + } else if (cam->whiteBalanceMode() == QCamera::WhiteBalanceFlash) { + // we should not just turn it off but also set it to the most appropriate normal mode. + constexpr QCamera::WhiteBalanceMode w_modess[] = { QCamera::WhiteBalanceShade, QCamera::WhiteBalanceAuto }; + for (auto m : w_modess) { + if (cam->isWhiteBalanceModeSupported(m)) { + cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto); + break; + } + } + } + emit torchEnabledChanged(); +} + bool CameraController::importScanFromClipboard() { if (d->scanRequest == nullptr) @@ -689,6 +730,10 @@ void CameraController::qrScanFinished() QCamera *cam = qobject_cast(d->camera); if (cam) cam->setTorchMode(QCamera::TorchOff); + if (d->torchEnabled) { + d->torchEnabled = false; + emit torchEnabledChanged(); + } } void CameraController::checkState() diff --git a/src/CameraController.h b/src/CameraController.h index 91a8bbc..425655e 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,6 +46,7 @@ class CameraController : public QObject Q_PROPERTY(bool supportsPaste READ supportsPaste NOTIFY visibleChanged) Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) + Q_PROPERTY(bool torchEnabled READ torchEnabled WRITE setTorchEnabled NOTIFY torchEnabledChanged) Q_PROPERTY(QObject* camera READ camera WRITE setCamera NOTIFY cameraChanged) Q_PROPERTY(QObject* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) public: @@ -70,8 +71,9 @@ public: bool loadCamera() const; bool cameraActive() const; bool visible() const; - bool supportsPaste() const; + bool torchEnabled() const; + void setTorchEnabled(bool on); signals: void cameraChanged(); @@ -80,6 +82,7 @@ signals: void loadCameraChanged(); void cameraActiveChanged(); void visibleChanged(); + void torchEnabledChanged(); // \internal (used to move thread) void startCheckState(); -- 2.54.0 From a9b2d3a9758bcbb6f10584fd7a43eb2bbc938fe7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 19:23:48 +0200 Subject: [PATCH 0598/1428] new release version 6.1 --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b31246e..087985a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="12" android:versionName="2023.06.1"> diff --git a/src/main.cpp b/src/main.cpp index 8057d00..5cb143c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.06.0"); + qapp.setApplicationVersion("2023.06.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 779c245f5b0f3845ea7a8d58bfe356b74b2908f3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 20:02:35 +0200 Subject: [PATCH 0599/1428] Import translations from crowdin --- CMakeLists.txt | 4 +- translations/floweepay-common_nl.ts | 37 +- translations/floweepay-mobile-nl.ts | 1102 +++++++++++++++++++ translations/mobile-i18n.qrc | 1 + translations/module-build-transaction_en.ts | 152 +-- translations/module-build-transaction_nl.ts | 164 +++ 6 files changed, 1290 insertions(+), 170 deletions(-) create mode 100644 translations/floweepay-mobile-nl.ts create mode 100644 translations/module-build-transaction_nl.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index c776481..0585846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ if(NOT ANDROID) translations/floweepay-mobile_pl.ts translations/module-build-transaction_en.ts + translations/module-build-transaction_nl.ts ) qt6_add_translation(qmFiles ${TS_FILES}) @@ -101,9 +102,6 @@ if(NOT ANDROID) COMMAND lupdate modules/build-transaction/build-transactions-data.qrc modules/build-transaction/BuildTransactionModuleInfo.cpp -ts translations/module-build-transaction.ts - COMMAND lupdate modules/example/example-data.qrc - modules/example/ExampleModuleInfo.cpp - -ts translations/module-example.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index c7976ef..4488988 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -99,17 +99,17 @@ Voeg een persoonlijke notitie toe - + Copied TXID to clipboard Gekopieerd TXID naar klipbord - + Opening Website Open Website - + Close Sluiten @@ -125,30 +125,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -157,13 +157,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -184,21 +184,26 @@ MenuModel + Explore + Ontdek + + + Settings Instellingen - + Network Details Netwerk Details - + About Over Ons - + Wallets Portemonnees @@ -274,7 +279,7 @@ QRWidget - + Copied to clipboard Naar klembord gekopieerd diff --git a/translations/floweepay-mobile-nl.ts b/translations/floweepay-mobile-nl.ts new file mode 100644 index 0000000..cfbce8f --- /dev/null +++ b/translations/floweepay-mobile-nl.ts @@ -0,0 +1,1102 @@ + + + + + About + + + About + Over Ons + + + + Help translate this app + Help deze app te vertalen + + + + License + Licentie + + + + + Credits + Met dank aan + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander en bijdragers + + + + Project Home + Startpagina project + + + + With git repository and issues tracker + Met git data en takenlijst + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Start + + + + Pay + Betaal + + + + Receive + Ontvang + + + + Miner Reward + Mijnwerker Beloning + + + + Cash Fusion + Cash Fusion + + + + Received + Ontvangen + + + + Moved + Zelf-betaling + + + + Sent + Verzonden + + + + Sending + Verzenden + + + + Seen + Gezien + + + + Rejected + Geweigerd + + + + AccountPageListItem + + + Sync Status + Synchronisatie status + + + + Backup information + Back-up Informatie + + + + Backup Details + Back-up gegevens + + + + Wallet seed-phrase + Herstelzin opslaan + + + + Derivation Path + Derivatie pad + + + + Starting Height + height refers to block-height + Beginhoogte + + + + Name + Naam + + + + Archived wallets do not check for activities. Balance may be out of date + Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd + + + + xpub + xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! + + + + Wallet keys + Sleutels van portemonnee + + + + Show Index + toggle to show numbers + Toon Index + + + + Change Addresses + Wisselgeldadres + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. + + + + Used Addresses + Gebruikte adressen + + + + Switches between still in use addresses and formerly used, new empty, addresses + Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen + + + + Addresses and keys + Adressen en sleutels + + + + Hide in private mode + Verbergen in privémodus + + + + Unarchive Wallet + Portemonnee De-archiveren + + + + Archive Wallet + Portemonnee Archiveren + + + + AccountSelectorPopup + + + Your Wallets + Uw portemonnees + + + + last active + Laatst actief + + + + AccountSyncState + + + Status: Offline + Status: offline + + + + AccountsList + + + Wallet + Portemonnee + + + + Wallets + Portemonnees + + + + Add Wallet + Portemonnee toevoegen + + + + Default Wallet + Standaard portemonnee + + + + %1 is used on startup + %1 wordt gebruikt bij start + + + + Exit Private Mode + Verlaat privémodus + + + + Enter Private Mode + Start privémodus + + + + Reveals wallets marked private + Onthult portefeuilles gemarkeerd als privé + + + + Hides wallets marked private + Verbergt portemonnees gemarkeerd als privé + + + + CurrencySelector + + + Select Currency + Selecteer valuta + + + + ExploreModules + + + Explore + Ontdek + + + + ON + Enabled. SHORT TEXT! + AAN + + + + GuiSettings + + + Display Settings + Scherminstellingen + + + + Font sizing + Lettertypegrootte + + + + Unit + Eenheid + + + + Change Currency (%1) + Verander valuta (%1) + + + + Main View + Hoofd overzicht + + + + Show Bitcoin Cash value + Toon Bitcoin Cash waarde + + + + ImportWalletPage + + + Import Wallet + Portemonnee importeren + + + + Create + Creëer + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. + + + + Secret + The seed-phrase or private key + Geheim + + + + Private key + description of type + Privé-sleutel + + + + BIP 39 seed-phrase + description of type + BIP 39 herstelzin + + + + Unrecognized word + Word from the seed-phrases lexicon + Niet herkend woord + + + + Name + Naam + + + + Force Single Address + Forceer één adres + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. +Wisselgeld gaat weer naar de geïmporteerde sleutel. + + + + Alternate phrase + Alternatieve zin + + + + Oldest Transaction + Oudste transactie + + + + Derivation + Derivatie + + + + InstaPayConfigButton + + + Enable Instant Pay + Direct Betalen inschakelen + + + + Configure Instant Pay + Configureer Direct Betalen + + + + Fast payments for low amounts + Direct betalen bij lage bedragen + + + + Not configured + Niet geconfigureerd + + + + Limit set to: %1 + Limiet ingesteld op: %1 + + + + InstaPayConfigPage + + + Instant Pay + Direct Betalen + + + + Requests for payment can be approved automatically using Instant Pay. + Betalingsverzoeken worden automatisch goedgekeurd met behulp van Direct Betalen. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Beveiligde portemonnees kunnen niet worden gebruikt voor Direct Betalen ze een PIN vereisen voor gebruik + + + + Enable Instant Pay for this wallet + Actief maken voor portemonnee + + + + Maximum Amount + Maximum Bedrag + + + + MenuOverlay + + + Add Wallet + Portemonnee toevoegen + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adres + + + + Start-height: %1 + Beginhoogte: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + verbinding initialiseren + + + + Verifying peer + Peer verifiëren + + + + Peer for wallet: %1 + Peer voor portemonnee: %1 + + + + Peer for wallet + Peer voor portemonnee + + + + NewAccount + + + New Bitcoin Cash Wallet + Nieuwe Bitcoin Cash Portemonnee + + + + Create a New Wallet + Maak een nieuwe portemonnee + + + + Basic + Eenvoudig + + + + Private keys based + Property of a wallet + Privé sleutels gebaseerd + + + + Difficult to backup + Context: wallet type + Back-up moeilijk + + + + Great for brief usage + Context: wallet type + Ideaal voor kort gebruik + + + + HD wallet + HD portemonnee + + + + Seed-phrase based + Context: wallet type + Herstelzin gebaseerd + + + + Easy to backup + Context: wallet type + Back-upt makkelijk + + + + Most compatible + The most compatible wallet type + Meest compatibel + + + + Import Existing Wallet + Importeer bestaande portemonnee + + + + Import + Importeer + + + + Imports seed-phrase + Importeert herstelzin + + + + Imports private key + Importeert Privésleutel + + + + New Wallet + Nieuwe Portemonnee + + + + + Create + Creëer + + + + This creates a new empty wallet with simple multi-address capability + Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden + + + + + Name + Naam + + + + Force Single Address + Forceer één adres + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. +Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + + + + This creates a new wallet that can be backed up with a seed-phrase + Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin + + + + Derivation + Derivatie + + + + New HD-Wallet + Nieuwe Portemonnee + + + + PayWithQR + + + Approve Payment + Betaling goedkeuren + + + + Send All + all money in wallet + Alles verzenden + + + + Show Address + to show a bitcoincash address + Toon adres + + + + Edit Amount + Edit amount of money to send + Bedrag aanpassen + + + + Invalid QR code + Ongeldige QR-code + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Gelezen tekst: <pre>%1</pre> + + + + Payment description + Omschrijving betaling + + + + Destination Address + Bestemmingsadres + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + PriceInputWidget + + + All Currencies + Alle valuta's + + + + QRScannerOverlay + + + Paste + Plak + + + + Failed + Mislukt! + + + + ReceiveTab + + + Receive + Ontvangen + + + + Share this QR to receive + Betaler moet deze QR lezen + + + + Transaction high risk + Transactie met hoog risico + + + + + Payment Seen + Betaling gezien + + + + Encrypted Wallet + Versleutelde portemonnee + + + + Import Running... + Bezig met importeren... + + + + Amount + requested amount of coin + Bedrag + + + + Address + Bitcoin Cash address + Adres + + + + Checking... + Controleren... + + + + Partially Paid + Deels betaald + + + + Payment Accepted + Betaling geaccepteerd + + + + Payment Settled + Betaling Afgewikkeld + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) + + + + Continue + Doorgaan + + + + + Description + Omschrijving + + + + Clear + Wissen + + + + SelectDefaultAccountPage + + + Select Wallet + Selecteer portemonnee + + + + Pick which wallet will be selected on starting Flowee Pay + Kies welke portefeuille zal worden geselecteerd bij het starten van Flowee Pay + + + + No InstaPay limit set + Geen Direct betalen limiet ingesteld + + + + InstaPay till %1 + Direct Betalen tot %1 + + + + InstaPay is turned off + Direct Betalen staat uit + + + + SendTransactionsTab + + + Send + Versturen + + + + Start Payment + Betaling starten + + + + SlideToApprove + + + SLIDE TO SEND + SWIPE VOOR VERZENDEN + + + + StartupScreen + + + Welcome! + Welkom! + + + + Continue + Doorgaan + + + + Moving the world towards a Bitcoin Cash economy + We lopen het pad naar een Bitcoin Cash economie + + + + Scan me to send funds to your HD wallet + Scan mij om geld naar uw HD-portemonnee te sturen + + + + Add a different wallet + Een andere portemonnee toevoegen + + + + OR + OF + + + + TransactionDetails + + + Transaction Details + Transactiedetails + + + + Transaction Hash + Transactie hash + + + + Rejected + Afgewezen + + + + Unconfirmed + Onbevestigd + + + + Mined at + Gedolven in + + + + %1 blocks ago + + Meest recente blok + %1 blokken terug + + + + + Transaction comment + Transactie omschrijving + + + + Size: %1 bytes + Grootte: %1 bytes + + + + Is a CashFusion transaction. + CashFusion transactie. + + + + Fees paid + Betaalde kosten + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Mijn gefuseerde adressen + + + + Sent from my addresses + Verzonden vanaf mijn adressen + + + + Sent from addresses + Verzonden vanaf adressen + + + + + Copy Address + Kopieer adres + + + + Fused into my addresses + Gefuseerd naar mijn adressen + + + + Received at addresses + Ontvangen op adressen + + + + Received at my addresses + Ontvangen op mijn adressen + + + + Coinbase + Coinbase + + + + TxInfoSmall + + + Transaction is rejected + Transactie geweigerd + + + + Processing + In behandeling + + + + Mined + Gedolven + + + + %1 blocks ago + Confirmations + + %1 blok terug + %1 blokken terug + + + + + Miner Reward + Mijnwerker Beloning + + + + Cash Fusion + Cash Fusion + + + + Received + Ontvangen + + + + Sent + Verzonden + + + + Value then + Waarde toen + + + + Value now + Waarde nu + + + + Payment to self + Betaling aan uzelf + + + + Holds a token + Heeft een token + + + + Sent to + Verstuurd naar + + + + Transaction Details + Transactiedetails + + + diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index 9f8f820..b8b9387 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -7,5 +7,6 @@ floweepay-mobile_nl.qm floweepay-mobile_pl.qm module-build-transaction_en.qm + module-build-transaction_nl.qm diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index 534054a..eb9b75f 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -1,154 +1,4 @@ - - - BuildTransactionModuleInfo - - - This is a nice text - bla - - - - PayToOthers - - - Build Transaction - - - - - Building Error - error during build - - - - - Add Payment Detail - page title - - - - - - Add Destination - - - - - an address to send money to - - - - - Confirm Sending - confirm we want to send the transaction - - - - - TXID - - - - - Copy transaction-ID - - - - - Fee - - - - - Transaction size - - - - - %1 bytes - - - - - Fee per byte - - - - - %1 sat/byte - fee - - - - - Destination - - - - - unset - indication of empty - - - - - invalid - address is not correct - - - - - - Copy Address - - - - - Edit Destination - - - - - Send All - all money in wallet - - - - - Bitcoin Cash Address - - - - - Warning - - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - - - - - I am certain - - - - - Drag to Edit - - - - - Drag to Delete - - - - - Prepare Payment... - - - + diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts new file mode 100644 index 0000000..bc500bf --- /dev/null +++ b/translations/module-build-transaction_nl.ts @@ -0,0 +1,164 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Transacties creëren + + + + This module allows building more powerful transactions in one simple user interface. + Deze module maakt het mogelijk om krachtigere transacties te bouwen in één eenvoudige gebruikersinterface. + + + + Build Transaction + Bouw transactie + + + + PayToOthers + + + Build Transaction + Bouw transactie + + + + Building Error + error during build + Fout bij bouwen + + + + Add Payment Detail + page title + Voeg betalingsgegevens toe + + + + + Add Destination + Voeg bestemming toe + + + + an address to send money to + een adres om geld naar te sturen + + + + Confirm Sending + confirm we want to send the transaction + Verzenden bevestigen + + + + TXID + TXID + + + + Copy transaction-ID + Transactie-ID kopiëren + + + + Fee + Transactiekosten + + + + Transaction size + Transactie grootte + + + + %1 bytes + %1 byte + + + + Fee per byte + Transactiekosten per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Destination + Bestemming + + + + unset + indication of empty + niets + + + + invalid + address is not correct + ongeldig + + + + + Copy Address + Kopieer adres + + + + Edit Destination + Bewerk Bestemming + + + + Send All + all money in wallet + Betaal alles + + + + Bitcoin Cash Address + Bitcoin Cash-adres + + + + Warning + Waarschuwing + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? + + + + I am certain + Ik ben zeker + + + + Drag to Edit + Sleep om te bewerken + + + + Drag to Delete + Sleep om te verwijderen + + + + Prepare Payment... + Betaling voorbereiden... + + + -- 2.54.0 From c4e64a1114d1a5b878debd1063173b11dfc3c70b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 15 Jun 2023 20:28:23 +0200 Subject: [PATCH 0600/1428] remove unused class --- guis/mobile.qrc | 1 - guis/mobile/IconButton.qml | 49 -------------------------------------- 2 files changed, 50 deletions(-) delete mode 100644 guis/mobile/IconButton.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6149760..dbf4c7c 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -49,7 +49,6 @@ mobile/MainView.qml mobile/AccountHistory.qml mobile/Loading.qml - mobile/IconButton.qml mobile/TextButton.qml mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml diff --git a/guis/mobile/IconButton.qml b/guis/mobile/IconButton.qml deleted file mode 100644 index 7cfe107..0000000 --- a/guis/mobile/IconButton.qml +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import QtQuick -import "../Flowee" as Flowee - -Item { - id: root - height: 60 + label.height - width: height - signal clicked; - property alias text: label.text - - Rectangle { - x: (parent.width - width) / 2 - width: 60 - height: 60 - radius: 30 - color: "#00000000" - border.color: "yellow" - border.width: 1 - } - Flowee.Label { - id: label - wrapMode: Text.WordWrap - width: parent.width - anchors.bottom: parent.bottom - horizontalAlignment: Text.AlignHCenter - } - - MouseArea { - anchors.fill: parent - onClicked: root.clicked() - } -} -- 2.54.0 From a6bf542108150496aea690e7365028b075a220ce Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 16 Jun 2023 23:56:55 +0200 Subject: [PATCH 0601/1428] Revive the QR-only image after it got updated Not all usages of the old 'QR' image were about scanning, so the added photo frame ended up causing a UX regression for those that were used to indicate showing a QR. Instead we now have 2 icons (at 4kb ascii text each, thats literally not a big deal) to acoid this confusion. --- guis/mobile.qrc | 2 ++ guis/mobile/AccountHistory.qml | 2 +- guis/mobile/ImportWalletPage.qml | 2 +- guis/mobile/images/qr-code-light.svg | 24 +++++++++-------------- guis/mobile/images/qr-code-scan-light.svg | 17 ++++++++++++++++ guis/mobile/images/qr-code-scan.svg | 16 +++++++++++++++ guis/mobile/images/qr-code.svg | 23 +++++++++------------- 7 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 guis/mobile/images/qr-code-scan-light.svg create mode 100644 guis/mobile/images/qr-code-scan.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index dbf4c7c..68733ee 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -18,6 +18,8 @@ mobile/images/receive.svg mobile/images/qr-code.svg mobile/images/qr-code-light.svg + mobile/images/qr-code-scan.svg + mobile/images/qr-code-scan-light.svg mobile/images/backspace.svg mobile/images/backspace-light.svg mobile/images/settingsIcon-light.svg diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index dae4754..ee7ed2e 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -147,7 +147,7 @@ ListView { spacing: 25 Flowee.ImageButton { id: startScan - source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); onClicked: thePile.push("PayWithQR.qml") iconSize: 70 text: qsTr("Pay") diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 34be553..ef72b33 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -62,7 +62,7 @@ Page { } Flowee.ImageButton { id: startScan - source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); onClicked: scanner.start(); iconSize: 25 anchors.verticalCenter: feedback.verticalCenter diff --git a/guis/mobile/images/qr-code-light.svg b/guis/mobile/images/qr-code-light.svg index 88be44a..4445f99 100644 --- a/guis/mobile/images/qr-code-light.svg +++ b/guis/mobile/images/qr-code-light.svg @@ -1,17 +1,11 @@ - - - - - - - - - - + + + + + + + + + - diff --git a/guis/mobile/images/qr-code-scan-light.svg b/guis/mobile/images/qr-code-scan-light.svg new file mode 100644 index 0000000..88be44a --- /dev/null +++ b/guis/mobile/images/qr-code-scan-light.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/guis/mobile/images/qr-code-scan.svg b/guis/mobile/images/qr-code-scan.svg new file mode 100644 index 0000000..4783779 --- /dev/null +++ b/guis/mobile/images/qr-code-scan.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/guis/mobile/images/qr-code.svg b/guis/mobile/images/qr-code.svg index 4783779..01b7dc7 100644 --- a/guis/mobile/images/qr-code.svg +++ b/guis/mobile/images/qr-code.svg @@ -1,16 +1,11 @@ - - - - - - - - - - + + + + + + + + + -- 2.54.0 From 573f316f51eb033d0737a06efb0a547158f67a3e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 18 Jun 2023 17:42:31 +0200 Subject: [PATCH 0602/1428] Revert the inverting of the QR Seems that some phones are not happy with this and simply fail to scan the QR after this. Functionality wins over pretty-ness. --- src/QRCreator.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/QRCreator.cpp b/src/QRCreator.cpp index b4f5d61..1e5d9ba 100644 --- a/src/QRCreator.cpp +++ b/src/QRCreator.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "QRCreator.h" -#include "FloweePay.h" #include // cmake ensures the presence of the ZXing lib. @@ -46,14 +45,8 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ ZXing::BitMatrix matrix = writer.encode(data, 250, 250); QImage result = QImage(matrix.height(), matrix.width(), QImage::Format_RGB32); - constexpr uint Black = 0xFF000000; - constexpr uint White = 0xFFFFFFFF; - uint black = Black; - uint white = White; - // inverted QRs are perfectly legal, lets make it nice for our users. - if (FloweePay::instance()->darkSkin()) - qSwap(black, white); - + constexpr uint black = 0xFF000000; + constexpr uint white = 0xFFFFFFFF; for (int y = 0; y < matrix.height(); ++y) for (int x = 0; x < matrix.width(); ++x) result.setPixel(x, y, matrix.get(x, y) ? black : white); -- 2.54.0 From fd70f18c06f5fded4d8b7e1251b509b1527d68e4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 18 Jun 2023 15:41:57 +0200 Subject: [PATCH 0603/1428] Port saving code to std lib The various places we save files used different libraries to do that work. This commit lets all use the same std lib methods consistently. --- src/FloweePay.cpp | 29 ++++++++++++----------------- src/ModuleManager.cpp | 3 +-- src/Wallet.cpp | 26 ++++++++++++-------------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 841a4aa..b8db96b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -44,6 +44,10 @@ #include #include +#include +#include +#include + constexpr const char *UNIT_TYPE = "unit"; constexpr const char *CREATE_START_WALLET = "create-start-wallet"; constexpr const char *WINDOW_WIDTH = "window/width"; @@ -461,24 +465,15 @@ void FloweePay::saveData() } } - QFile out(filebase + "~"); - out.remove(); // avoid overwrite issues. - if (out.open(QIODevice::WriteOnly)) { - auto rc = out.write(buf.begin(), buf.size()); - if (rc == -1) { - logFatal() << "Failed to write. Disk full?"; - // TODO have an app-wide error - return; - } + try { + std::string filebaseStr(filebase.toStdString()); + std::ofstream out(filebaseStr + "~"); + out.write(buf.begin(), buf.size()); + out.flush(); out.close(); - QFile::remove(filebase); - if (!out.rename(filebase)) { - logFatal() << "Failed to rename to" << filebase; - // TODO have an app-wide error - } - } else { - logFatal() << "Failed to create data file. Permissions issue?"; - // TODO have an app-wide error + std::filesystem::rename(filebaseStr + "~", filebaseStr); + } catch (const std::exception &e) { + logFatal() << "Failed to create data file. Permissions issue?" << e; } } diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index f4d2b64..f642927 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -201,12 +201,11 @@ void ModuleManager::save() const try { auto configFile = m_configFile.toStdString(); auto configFileNew = configFile + "~"; - boost::filesystem::remove(configFileNew); std::ofstream outFile(configFileNew); outFile.write(data.begin(), data.size()); outFile.flush(); outFile.close(); - boost::filesystem::rename(configFileNew, configFile); + std::filesystem::rename(configFileNew, configFile); } catch (const std::exception &e) { logFatal() << "Failed to save the wallet.dat. Reason:" << e; } diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 0c14222..c75dd72 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1743,8 +1743,7 @@ void Wallet::saveSecrets() auto data = builder.buffer(); try { - boost::filesystem::create_directories(m_basedir); - boost::filesystem::remove(m_basedir / "secrets.dat~"); + std::filesystem::create_directories(m_basedir.string()); if (m_encryptionLevel == FullyEncrypted) { auto &pool = Streaming::pool(data.size() + AES_BLOCKSIZE); @@ -1752,11 +1751,12 @@ void Wallet::saveSecrets() int size = crypto.encrypt(data.begin(), data.size(), pool.data()); data = pool.commit(size); } - std::ofstream outFile((m_basedir / "secrets.dat~").string()); + const auto basefile = (m_basedir / "secrets.dat").string(); + std::ofstream outFile(basefile + "~"); outFile.write(data.begin(), data.size()); outFile.flush(); outFile.close(); - boost::filesystem::rename(m_basedir / "secrets.dat~", m_basedir / "secrets.dat"); + std::filesystem::rename(basefile + "~", basefile); } catch (const std::exception &e) { logFatal(LOG_WALLET) << "Failed to save the database. Reason:" << e.what(); } @@ -2038,15 +2038,14 @@ void Wallet::saveWallet() QMutexLocker locker(&m_lock); if (m_walletNameChanged) { try { - boost::filesystem::create_directories(m_basedir); - boost::filesystem::remove(m_basedir / "name~"); - std::ofstream outFile((m_basedir / "name~").string()); + std::filesystem::create_directories(m_basedir.string()); + const auto basefile = (m_basedir / "name").string(); + std::ofstream outFile(basefile + "~"); const auto nameBytes = m_name.toUtf8(); outFile.write(nameBytes.constBegin(), nameBytes.size()); outFile.flush(); outFile.close(); - boost::filesystem::remove(m_basedir / "name"); - boost::filesystem::rename(m_basedir / "name~", m_basedir / "name"); + std::filesystem::rename(basefile + "~", basefile); m_walletNameChanged = false; } catch (const std::exception &e) { logFatal(LOG_WALLET) << "Failed to save the wallet-name. Reason:" << e; @@ -2111,9 +2110,7 @@ void Wallet::saveWallet() auto data = builder.buffer(); try { - boost::filesystem::create_directories(m_basedir); - boost::filesystem::remove(m_basedir / "wallet.dat~"); - + std::filesystem::create_directories(m_basedir.string()); if (m_encryptionLevel == FullyEncrypted) { AES256CBCEncrypt crypto(&m_encryptionKey[0], &m_encryptionIR[0], true); pool.reserve(data.size() + AES_BLOCKSIZE); @@ -2121,11 +2118,12 @@ void Wallet::saveWallet() data = pool.commit(size); } - std::ofstream outFile((m_basedir / "wallet.dat~").string()); + const auto basefile = (m_basedir / "wallet.dat").string(); + std::ofstream outFile(basefile + "~"); outFile.write(data.begin(), data.size()); outFile.flush(); outFile.close(); - boost::filesystem::rename(m_basedir / "wallet.dat~", m_basedir / "wallet.dat"); + std::filesystem::rename(basefile + "~", basefile); } catch (const std::exception &e) { logFatal(LOG_WALLET) << "Failed to save the wallet.dat. Reason:" << e; } -- 2.54.0 From 8f18df35b0ec4daa58c3397ec377cf911f32d9c5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Jun 2023 13:34:01 +0200 Subject: [PATCH 0604/1428] Add comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0585846..1fcc4d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,7 +228,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) qt6_android_generate_deployment_settings(pay_mobile) - qt6_android_add_apk_target(pay_mobile) + qt6_android_add_apk_target(pay_mobile) #notice; this call is deprecated since 6.5 endif () if(NOT ANDROID AND BUILD_MOBILE_PAY) -- 2.54.0 From 901b3b5a893cb0a8d3671968dc11d5489026a6ca Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Jun 2023 19:51:23 +0200 Subject: [PATCH 0605/1428] Add flickable around the import page. Reports show that the button at the bottom may fall out the screen for some people's settings. --- guis/mobile/ImportWalletPage.qml | 311 ++++++++++++++++--------------- 1 file changed, 159 insertions(+), 152 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index ef72b33..b87e1e4 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -30,167 +30,174 @@ Page { property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; property bool isPrivateKey: typedData === Wallet.PrivateKey - ColumnLayout { - width: parent.width - spacing: 10 - y: 10 - Flowee.Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.fillWidth: true - wrapMode: Text.Wrap - } + Flickable { + anchors.fill: parent + anchors.topMargin: 10 + anchors.bottomMargin: 10 + contentWidth: parent.width + contentHeight: column.height + + ColumnLayout { + id: column + width: parent.width + spacing: 10 + Flowee.Label { + text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + PageTitledBox { + title: qsTr("Secret", "The seed-phrase or private key") + Item { + width: parent.width + height: secrets.height + 10 + Flowee.MultilineTextField { + id: secrets + focus: true + nextFocusTarget: accountName + clip: true + anchors.left: parent.left + anchors.right: feedback.left + } + Flowee.Label { + id: feedback + text: importAccount.finished ? "✔" : " " + color: Pay.useDarkSkin ? "#37be2d" : "green" + font.pixelSize: 24 + anchors.right: startScan.left + } + Flowee.ImageButton { + id: startScan + source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + onClicked: scanner.start(); + iconSize: 25 + anchors.verticalCenter: feedback.verticalCenter + anchors.right: parent.right + + QRScanner { + id: scanner + scanType: QRScanner.Seed + onFinished: if (scanResult !== "") secrets.text = scanResult; + } + } + } + } + + Flowee.Label { + id: detectedType + color: typedData === Wallet.PartialMnemonicWithTypo ? mainWindow.errorRed : feedback.color + text: { + var typedData = importAccount.typedData + if (typedData === Wallet.PrivateKey) + return qsTr("Private key", "description of type") // TODO print address to go with it + if (typedData === Wallet.CorrectMnemonic) + return qsTr("BIP 39 seed-phrase", "description of type") + if (typedData === Wallet.PartialMnemonicWithTypo) + return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") + if (typedData === Wallet.MissingLexicon) + return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this + return "" + } + } + + PageTitledBox { + title: qsTr("Name") + Flowee.TextField { + id: accountName + width: parent.width + } + } + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + visible: importAccount.isPrivateKey + } + PageTitledBox { + title: qsTr("Oldest Transaction") + Flow { + width: parent.width + spacing: 10 + QQC2.ComboBox { + id: month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; + } + } + QQC2.ComboBox { + id: year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 9; + } + } + } + + PageTitledBox { + title: qsTr("Derivation") + visible: !importAccount.isPrivateKey + Flowee.TextField { + id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); + width: parent.width + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" + } + } + PageTitledBox { + title: qsTr("Alternate phrase") + visible: !importAccount.isPrivateKey + Flowee.TextField { + // according to the BIP39 spec this is the 'password', but from a UX + // perspective that is confusing and no actual wallet uses it + id: passwordField + width: parent.width + } + } - PageTitledBox { - title: qsTr("Secret", "The seed-phrase or private key") Item { width: parent.width - height: secrets.height + 10 - Flowee.MultilineTextField { - id: secrets - focus: true - nextFocusTarget: accountName - clip: true - anchors.left: parent.left - anchors.right: feedback.left - } - Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - anchors.right: startScan.left - } - Flowee.ImageButton { - id: startScan - source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - onClicked: scanner.start(); - iconSize: 25 - anchors.verticalCenter: feedback.verticalCenter + height: createButton.implicitHeight + Flowee.Button { + id: createButton + enabled: finished anchors.right: parent.right + text: qsTr("Create") + onClicked: { + var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); + if (importAccount.isMnemonic) + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + else + options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - QRScanner { - id: scanner - scanType: QRScanner.Seed - onFinished: if (scanResult !== "") secrets.text = scanResult; - } - } - } - } + options.forceSingleAddress = singleAddress.checked; - Flowee.Label { - id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? mainWindow.errorRed : feedback.color - text: { - var typedData = importAccount.typedData - if (typedData === Wallet.PrivateKey) - return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) - return qsTr("BIP 39 seed-phrase", "description of type") - if (typedData === Wallet.PartialMnemonicWithTypo) - return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Wallet.MissingLexicon) - return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this - return "" - } - } - - PageTitledBox { - title: qsTr("Name") - Flowee.TextField { - id: accountName - width: parent.width - } - } - - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") - checked: true - visible: importAccount.isPrivateKey - } - PageTitledBox { - title: qsTr("Oldest Transaction") - Flow { - width: parent.width - spacing: 10 - QQC2.ComboBox { - id: month - model: { - let locale = Qt.locale(); - var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { - list.push(locale.monthName(i)); + var accounts = portfolio.accounts; + for (var i = 0; i < accounts.length; ++i) { + var a = accounts[i]; + if (a.name === options.name) { + portfolio.current = a; + break; + } } - return list; + thePile.pop(); + thePile.pop(); } } - QQC2.ComboBox { - id: year - model: { - var list = []; - let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { - list.push(i); - } - return list; - } - currentIndex: 9; - } - } - } - - PageTitledBox { - title: qsTr("Derivation") - visible: !importAccount.isPrivateKey - Flowee.TextField { - id: derivationPath - property bool derivationOk: Pay.checkDerivation(text); - width: parent.width - text: "m/44'/0'/0'" // What most BCH wallets are created with - color: derivationOk ? palette.text : "red" - } - } - PageTitledBox { - title: qsTr("Alternate phrase") - visible: !importAccount.isPrivateKey - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - width: parent.width - } - } - - - Item { - width: parent.width - height: createButton.implicitHeight - Flowee.Button { - id: createButton - enabled: finished - anchors.right: parent.right - text: qsTr("Create") - onClicked: { - var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - - options.forceSingleAddress = singleAddress.checked; - - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); - } } } } -- 2.54.0 From cf59f2f06a68b80bb13a541f1ebe03aa68cfc376 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Jun 2023 22:47:15 +0200 Subject: [PATCH 0606/1428] Make CHF as a currency work for input As the name is so much wider the widget didn't work well, this makes the name not overlap. Additionally, added a space beteween the currency name and the numbers. --- guis/mobile/PriceInputWidget.qml | 13 +++++++++---- src/PriceDataProvider.cpp | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index a110a2f..7c12c80 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -108,12 +108,17 @@ FocusScope { color: palette.highlight opacity: 0.3 radius: 6 - width: 35 + width: { + if (root.fiatFollowsSats) + return 35; + return currencyNameShort.width + 10 + } height: parent.height - 4 y: 2 x: root.fiatFollowsSats ? 5 : 45 Behavior on x { NumberAnimation { } } + Behavior on width { NumberAnimation { } } } Row { @@ -137,10 +142,10 @@ FocusScope { Item { width: 8; height: 1 } // spacer Flowee.Label { - text: (Fiat.currencySymbolPost + Fiat.currencySymbolPrefix).trim() - width: 24 + id: currencyNameShort + text: (Fiat.currencySymbolPost + Fiat.currencySymbolPrefix).trim(); + width: Math.max(24, implicitWidth) font.pixelSize: 32 - horizontalAlignment: Text.AlignRight anchors.baseline: logo.bottom MouseArea { diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 6fe2ea4..0752fd0 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -76,6 +76,10 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) m_displayCents = !(m_currency == QLatin1String("JPY") || m_currency == QLatin1String("NOK")); + if (m_currency == QLatin1String("CHF")) { + m_currencySymbolPrefix += QLatin1String(" "); + } + emit currencySymbolChanged(); if (!m_basedir.isEmpty()) { assert(m_priceHistory.get()); -- 2.54.0 From 7398dd21ac7aa93bb45ad9c1ed1b43b63d98b0d7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Jun 2023 22:56:33 +0200 Subject: [PATCH 0607/1428] Update to the latest android --- android/docker/Dockerfile | 2 ++ android/docker/scripts/aurs.sh | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 44016ce..11de942 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -39,6 +39,8 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ libb2 \ md4c \ wget \ + libc++ \ + libxcrypt-compat \ jdk11-openjdk \ && pacman -Sc --noconfirm \ && rm -rf /var/cache/pacman/pkg/* \ diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index 402daa7..a2e5c3b 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -26,9 +26,8 @@ function makeAur( makeAur android-ndk -# For now we hardcode the 31.0.3 release of the sdk, seeing as Qt hardcodes gradle 7.4.2, which doesn't like a newer sdk. -makeAur android-sdk-platform-tools 89f6840df092ea2f1fc3d446c41be78cf9b66339 -makeAur android-sdk-build-tools 87aea36e5aef112d7af0c2ae5db154e96ab633c3 +makeAur android-sdk-platform-tools +makeAur android-sdk-build-tools makeAur android-sdk-cmdline-tools-latest makeAur android-platform -- 2.54.0 From b78ecfb86fc5a17784da3b47d3135a7464773a02 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 19 Jun 2023 23:39:04 +0200 Subject: [PATCH 0608/1428] Avoid crashing on missing data --- src/TransactionInfo.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 3958441..98f9b4f 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -33,10 +33,21 @@ double TransactionInfo::fees() const qint64 fees = 0; if (m_createdByUs) { for (const auto i : m_inputs) { - fees += i->value(); + /* + * We may assume that if a transaction is created by us that this + * means we have all inputs. An assumption that makes sense. + * It does hit the snag that if a user started an import at a later date + * than the input was created that we don't actually have it. We should, but + * we don't. + * + * So, make sure to check our pointers here. + */ + if (i) + fees += i->value(); } for (const auto o : m_outputs) { - fees -= o->value(); + if (o) + fees -= o->value(); } } return static_cast(fees); -- 2.54.0 From b5b9bac043bd6ce5cc03aa42243e0ed752c1a163 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Jun 2023 15:19:51 +0200 Subject: [PATCH 0609/1428] Rename some variables to make linter happy Avoid using the same variable name as one already defined in a wider scope, this improves readability of code. --- src/Payment.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 0c590cb..7300ff4 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -314,15 +314,15 @@ void Payment::prepare() #endif if (m_allowInstaPay) { - PaymentDetailOutput *out = nullptr; + PaymentDetailOutput *output = nullptr; if (m_paymentDetails.length() == 1) { // insta-pay is used to fund a single output, anything more complex than that // should be presented to the user for approval, no exceptions. - out = m_paymentDetails.first()->toOutput(); + output = m_paymentDetails.first()->toOutput(); } - if (!out) + if (!output) return; - if (out->maxSelected()) + if (output->maxSelected()) return; auto *fp = FloweePay::instance(); @@ -336,8 +336,8 @@ void Payment::prepare() return; } auto limit = conf.fiatInstaPayLimit(currency); - logInfo() << "Payment-prepare. Insta-pay for" << currency << "set to" << limit << "payment is" << out->paymentAmountFiat(); - if (out->paymentAmountFiat() > limit) + logInfo() << "Payment-prepare. Insta-pay for" << currency << "set to" << limit << "payment is" << output->paymentAmountFiat(); + if (output->paymentAmountFiat() > limit) return; // schedule broadcast in a different event in order to @@ -589,9 +589,9 @@ void Payment::remove(PaymentDetail *detail) if (detail->isOutput()) { bool seenOne = false; for (auto iter = m_paymentDetails.rbegin(); iter != m_paymentDetails.rend(); ++iter) { - auto *detail = *iter; - if (detail->isOutput()) { - detail->toOutput()->setMaxAllowed(!seenOne); + auto *otherDetail = *iter; + if (otherDetail->isOutput()) { + otherDetail->toOutput()->setMaxAllowed(!seenOne); seenOne = true; } } @@ -713,10 +713,10 @@ int Payment::paymentAmountFiat() const totalBch = inputSelector->selectedValue(); } else { // then the total amount is actually trivial, it is all that is available in the wallet. - auto wallet = m_wallet; // use the one we prepare()d from if available - if (wallet == nullptr) - wallet = m_account->wallet(); - totalBch = wallet->balanceConfirmed() + wallet->balanceUnconfirmed(); + auto bestWallet = m_wallet; // use the one we prepare()d from if available + if (bestWallet == nullptr) + bestWallet = m_account->wallet(); + totalBch = bestWallet->balanceConfirmed() + bestWallet->balanceUnconfirmed(); } return (totalBch * m_fiatPrice / 10000000 + 5) / 10; @@ -741,10 +741,10 @@ double Payment::paymentAmount() const if (inputSelector) return inputSelector->selectedValue(); // then the total amount is actually trivial, it is all that is available in the wallet. - auto wallet = m_wallet; // use the one we prepare()d from if available - if (wallet == nullptr) - wallet = m_account->wallet(); - return wallet->balanceConfirmed() + wallet->balanceUnconfirmed(); + auto bestWallet = m_wallet; // use the one we prepare()d from if available + if (bestWallet == nullptr) + bestWallet = m_account->wallet(); + return bestWallet->balanceConfirmed() + bestWallet->balanceUnconfirmed(); } amount += out->paymentAmount(); } -- 2.54.0 From 7e375f50b58f5c98ebf67228b919b777cfe7306d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Jun 2023 17:46:17 +0200 Subject: [PATCH 0610/1428] Bugfix how we handle numbers from user input This is a long overdue cleanup around the ideas of entering numbers in Flowee Pay. The core dataclass BitcoinValue now keeps track where the number came from, either user input or some calculation. This allows us to have the Fiat and the Coin price stay in sync without weird problems. The one we type uses a string, the price field that we are not typing in is then a slave and we follow the auto-generated number as the source. This solves a host of known issues: * Editing of value objects is much more consistnnt and predictable now. * Switching to a different fiat type now properly re-calculates the values that are slaved. So if the primary is a BCH value then the fiat value gets the new exhange rate instantly applied. * Switching to a different fiat type properly applies having a separator So if you go from euro to Japanese yen, we now remove the separator and the numbers behind it. * Changing the app setting from BCH to mBCH now properly updates all amounts. Notice that the user-typed string wins, if you typed 2 and then change to mBCH we assume you wanted 2, not 2000. * Paste now works more logcally. * Cursor is no longer sometimes invisible, requiring backspace to make it show up. And last we now protect against too large numbers. It is seen as an error to type a number above 21 million BCH. Fixes #19 --- guis/Flowee/MoneyValueField.qml | 1 + guis/mobile/NumericKeyboardWidget.qml | 141 +++++++++++++++++--------- guis/mobile/ReceiveTab.qml | 18 ++++ src/BitcoinValue.cpp | 139 ++++++++++++++----------- src/BitcoinValue.h | 21 ++-- src/Payment.cpp | 14 +++ src/Payment.h | 3 +- src/PaymentBackend.cpp | 15 +-- src/PaymentBackend.h | 4 +- src/PaymentDetailOutput.cpp | 11 +- src/PaymentDetailOutput_p.h | 4 +- src/PriceDataProvider.cpp | 17 ++-- src/PriceDataProvider.h | 7 +- testing/value/TestValue.cpp | 36 ++++++- 14 files changed, 283 insertions(+), 148 deletions(-) diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index 5ffe489..e5f976c 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -31,6 +31,7 @@ FocusScope { // this allows us to wrap methods property Item money: Item { property alias enteredString: privValue.enteredString + property alias value: privValue.value function insertNumber(character) { let rc = privValue.insertNumber(character); if (rc) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index a4245ac..04c6107 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -18,59 +18,100 @@ import QtQuick import QtQuick.Controls as QQC2 -Flow { +Item { id: root - Repeater { - model: 12 - delegate: Item { - width: root.width / 3 - height: 70 - QQC2.Label { - id: textLabel - anchors.centerIn: parent - font.pixelSize: 28 - text: { - if (index < 9) - return index + 1; - if (index === 9) - return Qt.locale().decimalPoint - if (index === 10) - return "0" - return ""; // index === 11, the backspace. - } - // make dim when not enabled. - color: { - if (!enabled) - return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 2) : Qt.lighter(palette.buttonText, 2); - return palette.windowText; - } - } - Image { - visible: index === 11 - anchors.centerIn: parent - source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" - width: 27 - height: 17 - opacity: enabled ? 1 : 0.4 - } + implicitHeight: flowLayout.implicitHeight + Rectangle { + // when the typed items are not allowd + // by the back-end, we flash this red area for a very brief time + // to let the user know. + // notice we also 'shake' the actual typed text, but if the user is + // focused on this widget they might miss that. + // Useful since some currencies don't allow dots/commas, which should + // be clearly communicated to the user! + id: errorFeedback + anchors.fill: parent + radius: 20 + color: mainWindow.errorRedBg + opacity: 0 - MouseArea { - anchors.fill: parent - function doSomething() { - let editor = priceInput.editor; - if (index < 9) // these are digits - var shake = !editor.insertNumber("" + (index + 1)); - else if (index === 9) - shake = !editor.addSeparator() - else if (index === 10) - shake = !editor.insertNumber("0"); - else - shake = !editor.backspacePressed(); - if (shake) - priceInput.shake(); + Timer { + interval: 100 + running: errorFeedback.opacity > 0 + repeat: true + onTriggered: { + anim.duration = 400 + errorFeedback.opacity = 0 + } + } + + Behavior on opacity { + NumberAnimation { + id: anim + duration: 50 + easing.type: Easing.InOutQuad + } + } + } + + Flow { + id: flowLayout + width: parent.width + Repeater { + model: 12 + delegate: Item { + width: root.width / 3 + height: 70 + QQC2.Label { + id: textLabel + anchors.centerIn: parent + font.pixelSize: 28 + text: { + if (index < 9) + return index + 1; + if (index === 9) + return Qt.locale().decimalPoint + if (index === 10) + return "0" + return ""; // index === 11, the backspace. + } + // make dim when not enabled. + color: { + if (!enabled) + return Pay.useDarkSkin ? Qt.darker(palette.buttonText, 2) : Qt.lighter(palette.buttonText, 2); + return palette.windowText; + } + } + Image { + visible: index === 11 + anchors.centerIn: parent + source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" + width: 27 + height: 17 + opacity: enabled ? 1 : 0.4 + } + + MouseArea { + anchors.fill: parent + function doSomething() { + let editor = priceInput.editor; + if (index < 9) // these are digits + var fail = !editor.insertNumber("" + (index + 1)); + else if (index === 9) + fail = !editor.addSeparator() + else if (index === 10) + fail = !editor.insertNumber("0"); + else + fail = !editor.backspacePressed(); + if (fail) { + anim.duration = 40 + errorFeedback.opacity = 0.7 + priceInput.shake(); + } + } + onClicked: doSomething(); + onDoubleClicked: doSomething(); } - onClicked: doSomething(); - onDoubleClicked: doSomething(); } } } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 6d1e362..b005287 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -231,6 +231,22 @@ FocusScope { fiatFollowsSats: false width: parent.width onActiveFocusChanged: if (activeFocus) editBox.editingTextField = false + + Connections { + target: Fiat + /* + * The price may change simply because the market moved and we re-cheked the server, + * but more important is the case where the user changed which currency they use. + */ + function onPriceChanged() { + // set the value again to trigger an updated exchange-rate calculation to happen. + var price = priceInput.editor.value; + if (priceInput.fiatFollowsSats) + priceInput.paymentBackend.paymentAmount = price; + else + priceInput.paymentBackend.paymentAmountFiat = price; + } + } } } @@ -267,6 +283,8 @@ FocusScope { } } opacity: request.state === PaymentRequest.Unpaid ? 0: 1 + enabled: opacity > 0 + visible: opacity > 0 // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) diff --git a/src/BitcoinValue.cpp b/src/BitcoinValue.cpp index e52ec4c..802e6b3 100644 --- a/src/BitcoinValue.cpp +++ b/src/BitcoinValue.cpp @@ -20,14 +20,21 @@ #include #include +#include BitcoinValue::BitcoinValue(QObject *parent) : QObject(parent), m_value(0) { connect (FloweePay::instance(), &FloweePay::unitChanged, this, [this]() { - // we follow what the user actually typed. - setStringValue(m_typedNumber); + if (m_maxFractionalDigits == -1) { + if (m_valueSource == UserInput) { // m_typedNumber is leading + // we follow what the user actually typed. For crypto values anyway. + setStringValue(m_typedNumber); + } else { + QTimer::singleShot(10, this, SLOT(processNumberValue())); + } + } }); } @@ -36,29 +43,16 @@ qint64 BitcoinValue::value() const return m_value; } -void BitcoinValue::setValue(qint64 value) +void BitcoinValue::setValue(qint64 value, ValueSource source) { if (m_value == value) return; m_value = value; + m_valueSource = source; emit valueChanged(); - if (value && m_typedNumber.isEmpty()) { - const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; - m_typedNumber = QString::number(m_value / pow(10, unitConfigDecimals), 'f', unitConfigDecimals); - if (unitConfigDecimals > 0) { // there is a comma separator in the string - // remove trailing zeros - bool done = false; - while (!done) { - if (m_typedNumber.endsWith('.')) - done = true; - if (done || m_typedNumber.endsWith('0')) - m_typedNumber = m_typedNumber.left(m_typedNumber.size() - 1); - else - done = true; - } - } - setCursorPos(m_typedNumber.size()); - } + + if (source == FromNumber) + QTimer::singleShot(10, this, SLOT(processNumberValue())); } void BitcoinValue::moveLeft() @@ -77,26 +71,31 @@ bool BitcoinValue::insertNumber(QChar number) { if (!number.isNumber()) throw std::runtime_error("Only numbers can be inserted in insertNumber"); + auto backup(m_typedNumber); int pos = m_typedNumber.indexOf('.'); const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; if (pos > -1 && m_cursorPos > pos && m_typedNumber.size() - pos - unitConfigDecimals > 0) return false; - int cursorPos = m_cursorPos; - m_typedNumber.insert(cursorPos, number); + int cursorPosNow = m_cursorPos; + m_typedNumber.insert(cursorPosNow, number); while (((pos < 0 && m_typedNumber.size() > 1) || pos > 1) && m_typedNumber.startsWith('0')) { - --cursorPos; + --cursorPosNow; m_typedNumber = m_typedNumber.mid(1); } - setStringValue(m_typedNumber); - setCursorPos(cursorPos + 1); + if (!setStringValue(m_typedNumber)) { + // revert due to error + m_typedNumber = backup; + return false; + } + setCursorPos(cursorPosNow + 1); emit enteredStringChanged(); return true; } bool BitcoinValue::addSeparator() { - if (m_typedNumber.indexOf('.') != -1) + if (m_typedNumber.indexOf('.') != -1 || m_maxFractionalDigits == 0) return false; m_typedNumber.insert(m_cursorPos, '.'); int movedPlaces = 1; @@ -114,7 +113,11 @@ void BitcoinValue::paste() { QClipboard *clipboard = QGuiApplication::clipboard(); assert(clipboard); - setEnteredString(clipboard->text().trimmed()); + + auto newValue = clipboard->text().trimmed().replace(',', '.'); + setStringValue(newValue); + m_valueSource = FromNumber; + QTimer::singleShot(10, this, SLOT(processNumberValue())); } bool BitcoinValue::backspacePressed() @@ -144,29 +147,7 @@ void BitcoinValue::reset() emit valueChanged(); } -void BitcoinValue::setEnteredString(const QString &value) -{ - bool started = false; - setCursorPos(0); - m_typedNumber.clear(); - for (int i = 0; i < value.size(); ++i) { - auto k = value.at(i); - if (k.isDigit()) { - started = true; - insertNumber(k); - } - else if ((started || (value.size() > i + 1 && value.at(i+1).isDigit())) - && (k.unicode() == ',' || k.unicode() == '.')) { - addSeparator(); - } - else if (started) - return; - } - if (!m_cursorPos) - setValue(0); -} - -void BitcoinValue::setStringValue(const QString &value) +bool BitcoinValue::setStringValue(const QString &value) { int separator = value.indexOf('.'); QStringView before; @@ -187,17 +168,17 @@ void BitcoinValue::setStringValue(const QString &value) after += '0'; newVal += QStringView(after).left(unitConfigDecimals).toInt(); - setValue(newVal); + + constexpr int64_t Coin = 100000000; // num satoshis in a single coin + constexpr int64_t MaxMoney = 21000000 * Coin; // number of maxim monies ever to be created + if (newVal > MaxMoney) + return false; + setValue(newVal, UserInput); + return true; } QString BitcoinValue::enteredString() const { - const char decimalPoint = QLocale::system().decimalPoint().at(0).unicode(); - if (decimalPoint != '.') { // make the user-visible one always use the locale-aware decimalpoint. - QString answer(m_typedNumber); - answer.replace('.', decimalPoint); - return answer; - } return m_typedNumber; } @@ -213,11 +194,17 @@ void BitcoinValue::setMaxFractionalDigits(int newMaxFractionalDigits) m_maxFractionalDigits = newMaxFractionalDigits; emit maxFractionalDigitsChanged(); - // the behavior here assumes that this is a property most won't set and those that do - // only set it once - if (m_value) { - m_typedNumber = QString::number(m_value / pow(10, m_maxFractionalDigits), 'f', m_maxFractionalDigits); - m_cursorPos = m_typedNumber.size(); + // recalc the integer baesd one from the user-typed string. + if (m_valueSource == UserInput) { + if (newMaxFractionalDigits == 0) { + // special case this. Drop the separator if the new currency has none. + int index = m_typedNumber.indexOf('.'); + if (index > 0) { + m_typedNumber = m_typedNumber.left(index); + setCursorPos(std::min(index, m_cursorPos)); + } + } + setStringValue(m_typedNumber); } } @@ -238,3 +225,31 @@ void BitcoinValue::setCursorPos(int cp) m_cursorPos = cp; emit cursorPosChanged(); } + +/* + * Use m_value as the 'user input' and update + * the cursor pos and the m_typedNumber variables in a heuristically pleasing manner. + */ +void BitcoinValue::processNumberValue() +{ + if (m_valueSource == UserInput) + return; + + const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; + m_typedNumber = QString::number(m_value / pow(10, unitConfigDecimals), 'f', unitConfigDecimals); + if (unitConfigDecimals > 0) { // there is a comma separator in the string + int length; + if (m_typedNumber.startsWith("0.")) + length = -1; + else + length = m_typedNumber.indexOf('.') - 1; + for (int i = length + 1; i < m_typedNumber.size(); ++i) { + if (m_typedNumber.at(i).isDigit() && m_typedNumber.at(i).unicode() != '0') { + length = i; + } + } + m_typedNumber = m_typedNumber.left(length + 1); + } + setCursorPos(m_typedNumber.size()); + emit enteredStringChanged(); +} diff --git a/src/BitcoinValue.h b/src/BitcoinValue.h index a8a3987..427551e 100644 --- a/src/BitcoinValue.h +++ b/src/BitcoinValue.h @@ -25,20 +25,25 @@ class BitcoinValue : public QObject { Q_OBJECT Q_PROPERTY(double value READ realValue WRITE setRealValue NOTIFY valueChanged) - Q_PROPERTY(QString enteredString READ enteredString WRITE setEnteredString NOTIFY enteredStringChanged) + Q_PROPERTY(QString enteredString READ enteredString NOTIFY enteredStringChanged) Q_PROPERTY(int maxFractionalDigits READ maxFractionalDigits WRITE setMaxFractionalDigits RESET resetMaxFractionalDigits NOTIFY maxFractionalDigitsChanged) Q_PROPERTY(int cursorPos READ cursorPos WRITE setCursorPos NOTIFY cursorPosChanged) public: explicit BitcoinValue(QObject *parent = nullptr); + enum ValueSource { + UserInput, // the result of things like insertNumnber() or paste() + FromNumber // setValue() with a number was called. + }; + qint64 value() const; - void setValue(qint64 value); + void setValue(qint64 value, ValueSource source); inline double realValue() { return m_value; } inline void setRealValue(double val) { - setValue(val); + setValue(val, FromNumber); } Q_INVOKABLE void moveLeft(); @@ -50,7 +55,6 @@ public: Q_INVOKABLE void reset(); QString enteredString() const; - void setEnteredString(const QString &s); /* * For fiat prices we want to limit the number of digits after @@ -71,14 +75,19 @@ signals: void maxFractionalDigitsChanged(); void cursorPosChanged(); -private: +protected slots: + void processNumberValue(); + +protected: // sets integer form of m_value from the \a value - void setStringValue(const QString &value); + // returns false if the string is not a proper number (or out of bounds) + bool setStringValue(const QString &value); qint64 m_value; QString m_typedNumber; int m_cursorPos = 0; int m_maxFractionalDigits = -1; + ValueSource m_valueSource = UserInput; }; #endif diff --git a/src/Payment.cpp b/src/Payment.cpp index 7300ff4..79af304 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -387,6 +387,7 @@ void Payment::reset() out->setCollapsable(false); // the first one is special since its used to emulate a dumb payment API connect (out, SIGNAL(paymentAmountChanged()), this, SIGNAL(amountChanged())); + connect (out, SIGNAL(paymentAmountFiatChanged()), this, SIGNAL(amountFiatChanged())); connect (out, SIGNAL(addressChanged()), this, SIGNAL(targetAddressChanged())); addDetail(out); @@ -504,6 +505,19 @@ void Payment::setFiatPrice(int pricePerCoin) return; m_fiatPrice = pricePerCoin; emit fiatPriceChanged(); + + for (auto *detail : m_paymentDetails) { + if (detail->isOutput()) { + auto out = detail->toOutput(); + assert(out); + // ensure that the UI fetches the price based on the new exchange rate. + if (out->fiatFollows()) + emit out->paymentAmountFiatChanged(); + else + emit out->paymentAmountChanged(); + } + } + doAutoPrepare(); } diff --git a/src/Payment.h b/src/Payment.h index e483fff..0981c39 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -39,7 +39,7 @@ class Payment : public QObject /// The payment-wide amount of funds being sent by this Payment. Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) /// The payment-wide amount of funds being sent by this Payment. - Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) + Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountFiatChanged) /// The single-output address we will send to Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged) // cleaned up and re-formatted @@ -222,6 +222,7 @@ private slots: signals: void feePerByteChanged(); void amountChanged(); + void amountFiatChanged(); void targetAddressChanged(); void txPreparedChanged(); void preferSchnorrChanged(); diff --git a/src/PaymentBackend.cpp b/src/PaymentBackend.cpp index 105dfc0..572e903 100644 --- a/src/PaymentBackend.cpp +++ b/src/PaymentBackend.cpp @@ -32,13 +32,13 @@ double PaymentBackend::paymentAmount() const void PaymentBackend::setPaymentAmount(double amount) { - if (qFuzzyCompare(m_paymentAmount, amount)) - return; if (amount < 0) return; + auto priceInFiat = std::round(FloweePay::instance()->prices()->price() * amount / 1E8); + if (qFuzzyCompare(m_paymentAmount, amount) && m_paymentAmountFiat == priceInFiat) + return; m_paymentAmount = amount; - auto priceInFiat = FloweePay::instance()->prices()->price() * amount / 1E8; - m_paymentAmountFiat = std::round(priceInFiat); + m_paymentAmountFiat = priceInFiat; emit amountChanged(); } @@ -49,11 +49,12 @@ int PaymentBackend::paymentAmountFiat() const void PaymentBackend::setPaymentAmountFiat(int amount) { - if (m_paymentAmountFiat == amount) - return; if (amount < 0) return; + auto satsAmount = amount * 1E8 / FloweePay::instance()->prices()->price(); + if (m_paymentAmountFiat == amount && m_paymentAmount == satsAmount) + return; m_paymentAmountFiat = amount; - m_paymentAmount = amount * 1E8 / FloweePay::instance()->prices()->price(); + m_paymentAmount = satsAmount; emit amountChanged(); } diff --git a/src/PaymentBackend.h b/src/PaymentBackend.h index 34d035e..d3d1682 100644 --- a/src/PaymentBackend.h +++ b/src/PaymentBackend.h @@ -31,10 +31,10 @@ public: explicit PaymentBackend(QObject *parent = nullptr); double paymentAmount() const; - void setPaymentAmount(double newPaymentAmount); + void setPaymentAmount(double amount); int paymentAmountFiat() const; - void setPaymentAmountFiat(int newPaymentAmountFiat); + void setPaymentAmountFiat(int amount); signals: void amountChanged(); diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index ad5f55e..57f0491 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -87,6 +87,7 @@ void PaymentDetailOutput::setPaymentAmount(double amount_) setFiatFollows(true); setMaxSelected(false); emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); checkValid(); } @@ -220,6 +221,7 @@ void PaymentDetailOutput::setWallet(Wallet *) { if (m_maxAllowed && m_maxSelected) { emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); } } @@ -237,6 +239,7 @@ void PaymentDetailOutput::setMaxSelected(bool on) setFiatFollows(on); emit maxSelectedChanged(); emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); checkValid(); } @@ -244,6 +247,7 @@ void PaymentDetailOutput::recalcMax() { if (m_maxSelected && m_maxAllowed) { emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); } } @@ -299,6 +303,7 @@ void PaymentDetailOutput::setPaymentAmountFiat(int amount) setFiatFollows(false); setMaxSelected(false); emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); checkValid(); } @@ -328,8 +333,10 @@ void PaymentDetailOutput::setMaxAllowed(bool max) m_maxAllowed = max; emit maxAllowedChanged(); - if (max == false && m_paymentAmount == -1) + if (max == false && m_paymentAmount == -1) { setPaymentAmount(0); - else + } else { emit paymentAmountChanged(); + emit paymentAmountFiatChanged(); + } } diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 0ad841e..3a6706a 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -30,7 +30,7 @@ class PaymentDetailOutput : public PaymentDetail */ Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY paymentAmountChanged) - Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountChanged) + Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountFiatChanged) // cleaned up and re-formatted, empty if invalid. Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY correctAddressChanged) // same as formatted, but without prefix. @@ -84,9 +84,9 @@ public: signals: void paymentAmountChanged(); + void paymentAmountFiatChanged(); void addressChanged(); void correctAddressChanged(); - void fiatIsMainChanged(); void fiatFollowsChanged(); void maxSelectedChanged(); void forceLegacyOkChanged(); diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 0752fd0..4a5c678 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -190,15 +190,14 @@ int PriceDataProvider::historicalPriceAccurate(int days) const QString PriceDataProvider::priceToStringSimple(int cents) const { auto value = QString::number(cents); - if (m_displayCents) { - const QChar comma = QLocale::system().decimalPoint().at(0); - if (cents < 10) - return "0" % comma % "0" % value; - if (cents < 100) - return"0" % comma % value; - return value.left(value.size() - 2) % comma % value.right(2); - } - return value.left(value.size() - 2); + if (!m_displayCents) + return value; + const QChar comma = QLocale::system().decimalPoint().at(0); + if (cents < 10) + return "0" % comma % "0" % value; + if (cents < 100) + return "0" % comma % value; + return value.left(value.size() - 2) % comma % value.right(2); } void PriceDataProvider::fetch() diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 25cfcd2..cda3653 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -78,10 +78,13 @@ public: Q_INVOKABLE int historicalPriceAccurate(int days) const; /// return a string with the given price and needed decimal separator. - /// Please note that the currency indicators are not included, unlike in formattedPrice() + /// Notes: 1. the currency indicators are not included, unlike in formattedPrice() + /// 2. the current fiat properties are used and we expect the 'price' in the + /// smallest unit. Cents for things like Euro, whole coins for currencies + /// where they hae no separator. /// \see currencySymbolPrefix() /// \see currencySymbolPost() - Q_INVOKABLE QString priceToStringSimple(int cents) const; + Q_INVOKABLE QString priceToStringSimple(int price) const; /** * Returns if the locale defined currency wants to display cents. diff --git a/testing/value/TestValue.cpp b/testing/value/TestValue.cpp index c37b40e..c9c7905 100644 --- a/testing/value/TestValue.cpp +++ b/testing/value/TestValue.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,32 @@ #include #include +class MockBitcoinValue : public BitcoinValue +{ +public: + void setEnteredString(const QString &string) + { + bool started = false; + setCursorPos(0); + m_typedNumber.clear(); + for (int i = 0; i < string.size(); ++i) { + auto k = string.at(i); + if (k.isDigit()) { + started = true; + insertNumber(k); + } + else if ((started || (string.size() > i + 1 && string.at(i+1).isDigit())) + && (k.unicode() == ',' || k.unicode() == '.')) { + addSeparator(); + } + else if (started) + return; + } + if (!m_cursorPos) + setValue(0, UserInput); + } +}; + void TestValue::init() { FloweePay::instance()->setUnit(FloweePay::BCH); @@ -26,7 +52,7 @@ void TestValue::init() void TestValue::basics() { - BitcoinValue testObject; + MockBitcoinValue testObject; testObject.setRealValue(8790.9); // We only use the whole QCOMPARE(testObject.value(), 8790); @@ -112,7 +138,7 @@ void TestValue::basics() void TestValue::insertAtCursor() { - BitcoinValue testObject; + MockBitcoinValue testObject; testObject.setEnteredString("156.1"); QCOMPARE(testObject.value(), 15610000000); QCOMPARE(testObject.cursorPos(), 5); @@ -168,7 +194,7 @@ void TestValue::insertAtCursor() void TestValue::fiatValues() { - BitcoinValue testObject; + MockBitcoinValue testObject; testObject.setMaxFractionalDigits(2); // this sets it to a fiat mode, like the Euro @@ -192,7 +218,7 @@ void TestValue::fiatValues() void TestValue::setText() { - BitcoinValue testObject; + MockBitcoinValue testObject; testObject.setEnteredString("1.23456789"); QCOMPARE(testObject.value(), 123456789); FloweePay::instance()->setUnit(FloweePay::BCH); -- 2.54.0 From 01ccb87686faf68a65efe56a4601e03a3740b4c9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Jun 2023 19:40:15 +0200 Subject: [PATCH 0611/1428] Properly display much larger prices. Handle fiat prices as a long --- src/PriceDataProvider.cpp | 12 +++++------- src/PriceDataProvider.h | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 4a5c678..67df132 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -97,24 +97,22 @@ void PriceDataProvider::setCountry(const QString &countrycode) setCurrency(QLocale(countrycode)); } -QString PriceDataProvider::formattedPrice(double amountSats, int price) const +QString PriceDataProvider::formattedPrice(double amountSats, int64_t price) const { if (price == 0) return QString(); return formattedPrice(priceFor(amountSats, price)); } -int PriceDataProvider::priceFor(double amountSats, int price) const +int64_t PriceDataProvider::priceFor(double amountSats, int64_t price) const { if (std::isnan(amountSats)) return 0; qint64 fiatValue = amountSats * price; - fiatValue = (fiatValue + (amountSats > 0 ? 50000000: -50000000)) / qint64(100000000); - assert(fiatValue < INT_MAX); - return static_cast(fiatValue); + return (fiatValue + (amountSats > 0 ? 50000000: -50000000)) / qint64(100000000); } -QString PriceDataProvider::formattedPrice(int fiatValue) const +QString PriceDataProvider::formattedPrice(int64_t fiatValue) const { // convert cheaply (low number of mallocs) to a price. // since our fiat is in cents, we assume we may add up to two leading zeros. @@ -187,7 +185,7 @@ int PriceDataProvider::historicalPriceAccurate(int days) const PriceHistoryDataProvider::Accurate); } -QString PriceDataProvider::priceToStringSimple(int cents) const +QString PriceDataProvider::priceToStringSimple(int64_t cents) const { auto value = QString::number(cents); if (!m_displayCents) diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index cda3653..6ffc58f 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -53,14 +53,14 @@ public: * This takes the BCH amount (in sats) and renders it into a price as the current locale defines, * based on \a price. */ - Q_INVOKABLE QString formattedPrice(double amountSats, int price) const; + Q_INVOKABLE QString formattedPrice(double amountSats, int64_t price) const; /// Return the price as int (in cents) for the number of sats and the given price. - Q_INVOKABLE int priceFor(double amountSats, int price) const; + Q_INVOKABLE int64_t priceFor(double amountSats, int64_t price) const; /** * Return a formatted string with the locale-defined price of a fiat price from \a cents. */ - Q_INVOKABLE QString formattedPrice(int cents) const; + Q_INVOKABLE QString formattedPrice(int64_t cents) const; /** * Return the price at a certain time in the past. @@ -84,7 +84,7 @@ public: /// where they hae no separator. /// \see currencySymbolPrefix() /// \see currencySymbolPost() - Q_INVOKABLE QString priceToStringSimple(int price) const; + Q_INVOKABLE QString priceToStringSimple(int64_t price) const; /** * Returns if the locale defined currency wants to display cents. -- 2.54.0 From 13e63fa86b9371ad8c6df4cfe312e692bc3f35a5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 Jun 2023 20:28:02 +0200 Subject: [PATCH 0612/1428] Make the QR not move upon opening the popup menu --- guis/mobile/ReceiveTab.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index b005287..0fce567 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -66,7 +66,7 @@ FocusScope { if (a) // disable the other one verticalTabs.children[(index + 1) % 2].active = false // update page-scope state variables - if (index == 1) + if (index === 1) a = !a; qrViewActive = a; editViewActive = !a; @@ -116,7 +116,7 @@ FocusScope { id: qr width: parent.width y: qrViewActive ? 40 : editBox.height - x: priceInput.activeFocus ? 0 - width : 0 + x: qrViewActive || editBox.editingTextField ? 0 : 0 - width qrSize: qrViewActive ? 256 : 160 textVisible: false qrText: request.qr @@ -222,8 +222,9 @@ FocusScope { id: description width: parent.width onTextChanged: if (!inputMethodComposing) request.message = text - onActiveFocusOnPressChanged: editBox.editingTextField = true + onActiveFocusChanged: if (activeFocus) editBox.editingTextField = true } + Behavior on opacity { OpacityAnimator { } } PriceInputWidget { @@ -253,7 +254,7 @@ FocusScope { NumericKeyboardWidget { id: numericKeyboard width: parent.width - x: priceInput.activeFocus ? 0 : width + 10 + x: qrViewActive || editBox.editingTextField ? width + 10 : 0 anchors.bottom: parent.bottom anchors.bottomMargin: 15 } -- 2.54.0 From ce60a3695d9c40d5a91cea6741e4e7c83b519775 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 23 Jun 2023 10:39:32 +0200 Subject: [PATCH 0613/1428] Make comment in line with new reality Last week I changed the SPV action to never stop, this comment was still refering to the reality before that. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b8db96b..795c92b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -167,7 +167,7 @@ FloweePay::FloweePay() * not active, they may all have been removed or timed out already. * * What we'll do is to start the actions again which will check up on our connections - * and create new ones or download blocks if we need to. + * and create new ones if we need to. */ if (!m_offline && m_loadingCompleted) p2pNet()->start(); -- 2.54.0 From a0fc2d144838780d449d0e0db6aee4cd37fe9112 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 23 Jun 2023 10:42:06 +0200 Subject: [PATCH 0614/1428] Remove unused code The Qt.platform property has been good enough. --- src/FloweePay.cpp | 11 ----------- src/FloweePay.h | 4 ---- 2 files changed, 15 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 795c92b..30b83fb 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -489,17 +489,6 @@ QString FloweePay::basedir() const return m_basedir; } -QString FloweePay::platform() const -{ -#ifdef TARGET_OS_Android - return "Android"; -#endif -#ifdef TARGET_OS_Linux - return "Linux"; -#endif - return "unknown"; // TODO for other platforms check which names CMake uses. -} - void FloweePay::startP2PInit() { // start creation of downloadmanager and loading of data in a different thread diff --git a/src/FloweePay.h b/src/FloweePay.h index 2211941..6b60101 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -47,7 +47,6 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_OBJECT Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString libsVersion READ libsVersion CONSTANT) - Q_PROPERTY(QString platform READ platform CONSTANT) // p2p net Q_PROPERTY(int headerChainHeight READ headerChainHeight NOTIFY headerChainHeightChanged) Q_PROPERTY(int expectedChainHeight READ expectedChainHeight NOTIFY expectedChainHeightChanged) @@ -109,9 +108,6 @@ public: /// return the app data location QString basedir() const; - /// returns platform name, Linux / Android / etc - QString platform() const; - /** * Load p2p layer in a worker-thread. * The signal loadComplete() will be triggered when that is done, after which it is safe to call -- 2.54.0 From 5634f2ba2bc5f7015520294cb9c133d7ea8e56f9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 23 Jun 2023 12:48:22 +0200 Subject: [PATCH 0615/1428] Move modules status to the end Make the module messages appear below the "configuration results" section by moving the messages to the end of the CMakeLists file --- CMakeLists.txt | 17 +++++++++++++++-- modules/CMakeLists.txt | 5 ----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fcc4d0..c9d7d6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,8 +303,8 @@ endif() message("") message("Configuration results:") message("----------------------") -message(STATUS "Target OS: ${CMAKE_SYSTEM_NAME}") -message(STATUS "Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}") +message("Target OS: ${CMAKE_SYSTEM_NAME}") +message("Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}") if (${local_qml}) message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") endif () @@ -333,5 +333,18 @@ if (${BUILD_PAY_TOOLS}) message ("-> Building Pay tools") endif() +file(GLOB sub_directories ${CMAKE_SOURCE_DIR}/modules/*) +foreach (child ${sub_directories}) + if (IS_DIRECTORY ${child}) + file(GLOB subModule ${child}/*ModuleInfo.h) + if (EXISTS ${subModule}) + file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) + message("-> Including module '${module}'") + get_filename_component(className ${subModule} NAME_WE) + message(" ${className}") + endif() + endif() +endforeach() + message("") message("") diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 99bba7e..acbc427 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -18,9 +18,6 @@ #add_subdirectory(build-transaction) # Find all modules to auto-invoke and write them to a file. -message("") -message("Modules") -message("=======") set(INCLUDES_LIST "") set(CLASSES_LIST "") file(GLOB sub_directories ${CMAKE_CURRENT_SOURCE_DIR}/*) @@ -30,9 +27,7 @@ foreach (child ${sub_directories}) if (EXISTS ${subModule}) add_subdirectory(${child}) file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) - message("-- Including module '${module}'") get_filename_component(className ${subModule} NAME_WE) - message(" ${className}") list (APPEND CLASSES_LIST ${className}) file (RELATIVE_PATH include_file ${CMAKE_CURRENT_SOURCE_DIR} ${subModule}) -- 2.54.0 From 7f8ad5aedd8c285dfacfd45679b9989336c66159 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 23 Jun 2023 13:04:02 +0200 Subject: [PATCH 0616/1428] Port to new QPermissions system This now uses the new Qt APIs for requesting camera permissions on supported platforms (Adnroid and iOS). There are still several ifdefs and the older code is still there, lets leave that for some releases, because why not. --- src/CameraController.cpp | 70 ++++++++++++++++++++-------------------- src/CameraController.h | 1 - 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 6365291..11cbf69 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -19,7 +19,7 @@ #include "CameraController.h" #include "QRScanner.h" #include "FloweePay.h" -#include "qclipboard.h" +#include #include @@ -28,14 +28,17 @@ #include #include #include +#include #include #include #include -#ifdef TARGET_OS_Android -#include -#endif -#include +#if TARGET_OS_Android && QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +# include +#endif +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) +#include +#endif enum AskingState { NotAsked, @@ -475,34 +478,11 @@ CameraController::CameraController(QObject *parent) : QObject(parent), d(new CameraControllerPrivate(this)) { - // pre-load the camera for stability sake - // QTimer::singleShot(3000, this, SLOT(initialize())); - // The Android permissions requesting stuff returns results in a different thread, // allow an easy way to move back to the main thread using a connection. QObject::connect(this, SIGNAL(startCheckState()), this, SLOT(checkState()), Qt::QueuedConnection); } -void CameraController::initialize() -{ -#ifdef TARGET_OS_Android - QMutexLocker locker(&d->lock); - if (d->state == NotAsked) { -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); - future.then([=](QtAndroidPrivate::PermissionResult res) { - if (res == QtAndroidPrivate::PermissionResult::Authorized) { - // the camera had been authorized before, load the camera components in the UI. - d->cameraLoaded = true; - emit loadCameraChanged(); - } - }); -#endif - } -#endif -} - - void CameraController::startRequest(QRScanner *request) { assert(request); @@ -514,10 +494,30 @@ void CameraController::startRequest(QRScanner *request) } if (d->state == NotAsked) { -#ifdef TARGET_OS_Android -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - // we expect that in Qt65 this API will be changed or removed as the QCoreApplication::requestPermission will then become available. - // for now, use the private APIs (since 6.5 is still 4 months from release) +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) + switch (QCoreApplication::instance()->checkPermission(QCameraPermission {})) { + case Qt::PermissionStatus::Granted: d->state = Authorized; break; + case Qt::PermissionStatus::Denied: d->state = Denied; break; + case Qt::PermissionStatus::Undetermined: + d->state = Asking; + QCoreApplication::instance()->requestPermission(QCameraPermission {}, + this, [=](const QPermission &permission) { + + if (permission.status() == Qt::PermissionStatus::Granted) { + d->state = Authorized; + // move the actual turning on of the camera to the next event. + // please notice that this reply came in a different thread than the main one. + // We must move back to the main one before doing anything. + emit startCheckState(); + } else { + d->state = Denied; + } + }); + break; + } +#else +# ifdef TARGET_OS_Android + // QCoreApplication::requestPermission was only introduced in Qt6.5 d->state = Asking; auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); future.then([=](QtAndroidPrivate::PermissionResult res) { @@ -542,9 +542,9 @@ void CameraController::startRequest(QRScanner *request) } }); return; -#endif // version check -#else +# else d->state = Authorized; +# endif #endif } d->checkState(); @@ -636,7 +636,7 @@ bool CameraController::importScanFromClipboard() } else { // find the address if it doesn't have the prefix. - for (auto word : text.split(' ', Qt::SkipEmptyParts)) { + for (auto &word : text.split(' ', Qt::SkipEmptyParts)) { if (word.length() > 40 && word.length() < 50) { auto id = FloweePay::instance()->identifyString(prefix + word); if (id == WalletEnums::CashPKH || id == WalletEnums::CashSH) { diff --git a/src/CameraController.h b/src/CameraController.h index 425655e..cd72081 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -88,7 +88,6 @@ signals: void startCheckState(); private slots: - void initialize(); void qrScanFinished(); void checkState(); void initCamera(); -- 2.54.0 From 167c3a9b457764cc126fa387aa8102189bca0083 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 23 Jun 2023 15:52:39 +0200 Subject: [PATCH 0617/1428] Move to Qt 6.5.1 This updates us from the 6.4.3 Qt release to default building on Qt 6.5.1 in our build helper and natrally the docker file. --- android/build-pay.sh | 3 ++- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- android/docker/scripts/aurs.sh | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 87d7ba7..8953602 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="flowee/buildenv-android:v6.4.3" + _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.1" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then @@ -97,6 +97,7 @@ export QT_ANDROID_KEYSTORE_STORE_PASS=longPassword export QT_ANDROID_KEYSTORE_KEY_PASS=longPassword /usr/local/bin/androiddeployqt \ + --release \ --input /home/builds/build/android-pay_mobile-deployment-settings.json \ --output /home/builds/build/android-build \ --sign /home/builds/src/android/selfsigned.keystore floweepay diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 11de942..e85ce8d 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.4.3 +ARG QtVersion=v6.5.1 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index a7f1c84..3fc3faf 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.4.3 +QtVersion=v6.5.1 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index a2e5c3b..402daa7 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -26,8 +26,9 @@ function makeAur( makeAur android-ndk -makeAur android-sdk-platform-tools -makeAur android-sdk-build-tools +# For now we hardcode the 31.0.3 release of the sdk, seeing as Qt hardcodes gradle 7.4.2, which doesn't like a newer sdk. +makeAur android-sdk-platform-tools 89f6840df092ea2f1fc3d446c41be78cf9b66339 +makeAur android-sdk-build-tools 87aea36e5aef112d7af0c2ae5db154e96ab633c3 makeAur android-sdk-cmdline-tools-latest makeAur android-platform -- 2.54.0 From 2c58979232cfc08cbb3a61f75416919396fcec57 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 26 Jun 2023 22:13:09 +0200 Subject: [PATCH 0618/1428] Make the button not the way to stop editing. When the user starts editing the text label, the way out is to use the (virtual) keyboards 'enter' or 'done' button. We mark this button as disabled while editing to avoid weird situation. This also works around the application completely hanging in Qt651 on the phone. Hanging as in: Android suggests it to be killed. --- guis/mobile/EditableLabel.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 937b55b..edb3bbd 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -44,8 +44,15 @@ Item { height: 16 smooth: true source: "qrc:/edit-pen" + (Pay.useDarkSkin ? "-light" : "") + ".svg" - opacity: root.editable ? 1 : 0 + opacity: { + if (root.editable == false) + return 0; + if (editWidget.visible) // button is disabled. + return 0.4 + return 1; // enabled + } visible: opacity > 0 + enabled: editWidget.visible == false MouseArea { anchors.fill: parent anchors.margins: -15 -- 2.54.0 From eb5f95a1a89dbc0816292567ec1b3ff752eadf28 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Jun 2023 13:17:55 +0200 Subject: [PATCH 0619/1428] support the button being disabled. --- guis/Flowee/ImageButton.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index 1040c26..8e889bf 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -33,7 +33,7 @@ QQC2.Control { id: highlight anchors.fill: parent color: palette.button - visible: mouseArea.containsMouse + visible: enabled && mouseArea.containsMouse radius: 6 } Image { @@ -44,6 +44,7 @@ QQC2.Control { anchors.top: parent.top anchors.topMargin: 4 smooth: true + opacity: enabled ? 1 : 0.6 } Label { id: label @@ -52,6 +53,7 @@ QQC2.Control { horizontalAlignment: Text.AlignHCenter anchors.top: imageIcon.bottom anchors.topMargin: 4 + opacity: enabled ? 1 : 0.6 } MouseArea { id: mouseArea -- 2.54.0 From 8db23ba6246dc55c7d8970276d5bbb8f2ef00b5c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Jun 2023 18:57:50 +0200 Subject: [PATCH 0620/1428] Provide a screen to unlock encrypted wallets. This detects that the currently selected wallet is fully encryted and if it is, it shows a password request page on top of the current screen. The default setup aims to have people type a PIN in numbers to unlock the wallet, but we also provide a way to make it use a textual password instead. --- guis/desktop.qrc | 6 - guis/desktop/WalletEncryptionStatus.qml | 2 +- guis/desktop/images/lock-dark.svg | 1 - guis/desktop/images/lock-light.svg | 1 - .../{desktop => }/images/eye-closed-light.png | Bin guis/{desktop => }/images/eye-closed.png | Bin guis/{desktop => }/images/eye-open-light.png | Bin guis/{desktop => }/images/eye-open.png | Bin guis/images/lock-light.svg | 3 + guis/images/lock.svg | 3 + guis/mobile.qrc | 5 + guis/mobile/AccountHistory.qml | 4 - guis/mobile/MainViewBase.qml | 24 +++ guis/mobile/NumericKeyboardWidget.qml | 7 +- guis/mobile/PayWithQR.qml | 1 + guis/mobile/ReceiveTab.qml | 3 +- guis/mobile/UnlockWalletPanel.qml | 204 ++++++++++++++++++ guis/mobile/images/keyboard-light.svg | 4 + guis/mobile/images/keyboard.svg | 4 + guis/mobile/images/num-keyboard-light.svg | 13 ++ guis/mobile/images/num-keyboard.svg | 13 ++ guis/widgets.qrc | 6 + modules/build-transaction/PayToOthers.qml | 1 + 23 files changed, 289 insertions(+), 16 deletions(-) delete mode 100644 guis/desktop/images/lock-dark.svg delete mode 100644 guis/desktop/images/lock-light.svg rename guis/{desktop => }/images/eye-closed-light.png (100%) rename guis/{desktop => }/images/eye-closed.png (100%) rename guis/{desktop => }/images/eye-open-light.png (100%) rename guis/{desktop => }/images/eye-open.png (100%) create mode 100644 guis/images/lock-light.svg create mode 100644 guis/images/lock.svg create mode 100644 guis/mobile/UnlockWalletPanel.qml create mode 100644 guis/mobile/images/keyboard-light.svg create mode 100644 guis/mobile/images/keyboard.svg create mode 100644 guis/mobile/images/num-keyboard-light.svg create mode 100644 guis/mobile/images/num-keyboard.svg diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 018f75d..273b916 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -12,13 +12,7 @@ desktop/images/settingsIcon-light.png desktop/images/activityIcon.png desktop/images/activityIcon-light.png - desktop/images/eye-closed-light.png - desktop/images/eye-closed.png - desktop/images/eye-open-light.png - desktop/images/eye-open.png desktop/images/edit-delete.svg - desktop/images/lock-light.svg - desktop/images/lock-dark.svg desktop/defaults.ini desktop/ConfigItem.qml ControlColors.js diff --git a/guis/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml index 8927521..3dcd6fe 100644 --- a/guis/desktop/WalletEncryptionStatus.qml +++ b/guis/desktop/WalletEncryptionStatus.qml @@ -27,7 +27,7 @@ Item { Image { id: lockIcon - source: Pay.useDarkSkin ? "qrc:/lock-light.svg" : "qrc:/lock-dark.svg" + source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); height: parent.height + 2 width: height opacity: root.account.isDecrypted ? 0.65 : 1 diff --git a/guis/desktop/images/lock-dark.svg b/guis/desktop/images/lock-dark.svg deleted file mode 100644 index 6bf9499..0000000 --- a/guis/desktop/images/lock-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/guis/desktop/images/lock-light.svg b/guis/desktop/images/lock-light.svg deleted file mode 100644 index e3406cd..0000000 --- a/guis/desktop/images/lock-light.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/guis/desktop/images/eye-closed-light.png b/guis/images/eye-closed-light.png similarity index 100% rename from guis/desktop/images/eye-closed-light.png rename to guis/images/eye-closed-light.png diff --git a/guis/desktop/images/eye-closed.png b/guis/images/eye-closed.png similarity index 100% rename from guis/desktop/images/eye-closed.png rename to guis/images/eye-closed.png diff --git a/guis/desktop/images/eye-open-light.png b/guis/images/eye-open-light.png similarity index 100% rename from guis/desktop/images/eye-open-light.png rename to guis/images/eye-open-light.png diff --git a/guis/desktop/images/eye-open.png b/guis/images/eye-open.png similarity index 100% rename from guis/desktop/images/eye-open.png rename to guis/images/eye-open.png diff --git a/guis/images/lock-light.svg b/guis/images/lock-light.svg new file mode 100644 index 0000000..3b37aa1 --- /dev/null +++ b/guis/images/lock-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/guis/images/lock.svg b/guis/images/lock.svg new file mode 100644 index 0000000..873427d --- /dev/null +++ b/guis/images/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 68733ee..8a13c2b 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -39,6 +39,10 @@ mobile/images/ribbon.svg mobile/images/flash.svg mobile/images/flash-light.svg + mobile/images/keyboard.svg + mobile/images/keyboard-light.svg + mobile/images/num-keyboard-light.svg + mobile/images/num-keyboard.svg mobile/main.qml mobile/defaults.ini ControlColors.js @@ -79,5 +83,6 @@ mobile/InstaPayConfigPage.qml mobile/InstaPayConfigButton.qml mobile/ExploreModules.qml + mobile/UnlockWalletPanel.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index ee7ed2e..6b56ac3 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -159,10 +159,6 @@ ListView { text: qsTr("Receive") } } - - /* TODO - Is Encryopted / Decrypt - */ } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 1e71f5f..c037ad6 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -196,4 +196,28 @@ QQC2.Control { } } } + + /* + * Fully encrypted wallet are useless to inspect or use until they are opened. + * This is an overlay that covers the entire screen except for the header to make + * unlocking a "modal" dialogue. + */ + Rectangle { + color: palette.window + anchors.top: header.bottom + anchors.bottom: parent.bottom + width: parent.width + visible: portfolio.current.needsPinToOpen && !portfolio.current.isDecrypted + + // eat all taps / clicks. + MouseArea { anchors.fill: parent } + + // to avoid using resources in the 99% of the time the user is not unlocking his wallet, + // we use a loader for the unlocking screen. + Loader { + id: unlockWalletWidget + anchors.fill: parent + source: parent.visible ? "./UnlockWalletPanel.qml" : "" + } + } } diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 04c6107..bcca4af 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -21,6 +21,9 @@ import QtQuick.Controls as QQC2 Item { id: root implicitHeight: flowLayout.implicitHeight + + required property var dataInput; + Rectangle { // when the typed items are not allowd // by the back-end, we flash this red area for a very brief time @@ -94,7 +97,7 @@ Item { MouseArea { anchors.fill: parent function doSomething() { - let editor = priceInput.editor; + let editor = dataInput.editor; if (index < 9) // these are digits var fail = !editor.insertNumber("" + (index + 1)); else if (index === 9) @@ -106,7 +109,7 @@ Item { if (fail) { anim.duration = 40 errorFeedback.opacity = 0.7 - priceInput.shake(); + dataInput.shake(); } } onClicked: doSomething(); diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 99295eb..e209459 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -237,6 +237,7 @@ Page { width: parent.width enabled: !payment.details[0].maxSelected x: allowEditAmount ? 0 : 0 - width + dataInput: priceInput Behavior on x { NumberAnimation { } } } diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 0fce567..9139b80 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -178,7 +178,7 @@ FocusScope { Flowee.Button { anchors.right: parent.right text: qsTr("Clear") - enabled: description.text != "" || priceInput.paymentBackend.paymentAmount > 0; + enabled: description.text !== "" || priceInput.paymentBackend.paymentAmount > 0; onClicked: { request.clear(); request.account = portfolio.current; @@ -257,6 +257,7 @@ FocusScope { x: qrViewActive || editBox.editingTextField ? width + 10 : 0 anchors.bottom: parent.bottom anchors.bottomMargin: 15 + dataInput: priceInput } // the "payment received" screen. diff --git a/guis/mobile/UnlockWalletPanel.qml b/guis/mobile/UnlockWalletPanel.qml new file mode 100644 index 0000000..b5e179e --- /dev/null +++ b/guis/mobile/UnlockWalletPanel.qml @@ -0,0 +1,204 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import Flowee.org.pay; + +Item { + id: root + anchors.fill: parent + anchors.margins: 10 + + property bool numericInput: true + + Image { + id: lockIcon + source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 80 + height: 80 + anchors.horizontalCenter: parent.horizontalCenter + smooth: true + y: 40 + } + + Image { + width: parent.numericInput ? 40 : 30 + height: width + anchors.right: parent.right + source: "qrc:/" + (parent.numericInput ? "keyboard" : "num-keyboard") + + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + + MouseArea { + anchors.fill: parent + onClicked: { + keyboard.dataInput.editor.reset(); + var newValue = !root.numericInput; + root.numericInput = newValue; + if (newValue === false) + pwdField.forceActiveFocus(); + } + } + } + + Flowee.Label { + id: introText + text: qsTr("Enter your wallet passcode") + anchors.top: lockIcon.bottom + anchors.topMargin: 20 + Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes + } + + // Show the typed pin code, but as bullets as you type. + Row { + anchors.top: introText.bottom + anchors.topMargin: 20 + spacing: 10 + anchors.horizontalCenter: parent.horizontalCenter + visible: parent.numericInput + Repeater { + id: repeater + // take the number typed and turn it into an array of characters. + model: { + var inputString = keyboard.dataInput.editor.enteredString + var answer = []; + for (let i = 0; i < inputString.length; ++i) { + answer.push(inputString.substr(i, 1)); + } + return answer; + } + + Flowee.Label { + text: { + if (index !== keyboard.dataInput.editor.insertedIndex) + return "∙" + return modelData; + } + font.pixelSize: introText.font.pixelSize * 2 + + Timer { + interval: 1000 + running: index === keyboard.dataInput.editor.insertedIndex + onTriggered: text = "∙" + } + } + } + } + + NumericKeyboardWidget { + id: keyboard + x: parent.numericInput ? 0 : 0 - parent.width + y: parent.height - openButton.height - height - 20 + width: parent.width + dataInput: Item { + property QtObject editor: Item { + property string enteredString; + property int insertedIndex: -1 + function insertNumber(character) { + if (enteredString.length >= 10) + return false; + insertedIndex = enteredString.length; + enteredString = enteredString + character; + return true; + } + function addSeparator() { return false; } + function backspacePressed() { + if (enteredString == "") + return false; + insertedIndex = -1; + enteredString = enteredString.substring(0, enteredString.length - 1); + return true; + } + function reset() { + insertedIndex = -1; + enteredString = ""; + } + } + function shake() { shaker.start(); } + } + Behavior on x { NumberAnimation { } } + } + + Flowee.TextField { + id: pwdField + x: parent.numericInput ? parent.width + 10 : 0 + anchors.top: introText.bottom + anchors.topMargin: 20 + width: parent.width + focus: parent.numericInput === false + onEditingFinished: openButton.clicked() + echoMode: TextInput.Password + Behavior on x { NumberAnimation { } } + + Image { + width: 14 + height: 14 + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.verticalCenter: parent.verticalCenter + source: { + var state = (pwdField.echoMode === TextInput.Normal) ? "closed" : "open"; + var skin = Pay.useDarkSkin ? "-light" : "" + return "qrc:/eye-" + state + skin + ".png"; + } + + MouseArea { + width: parent.width + 20 // extend to right physical edge + x: -5 + y: 0 - parent.y - 5 + height: parent.height + 10 + onClicked: { + var old = pwdField.echoMode; + if (old == TextInput.Normal) + pwdField.echoMode = TextInput.Password + else + pwdField.echoMode = TextInput.Normal + } + } + } + } + + Flowee.Button { + id: openButton + anchors.right: parent.right + y: { + if (parent.numericInput) + return parent.height - height + return pwdField.y + pwdField.height + 10; + } + + text: qsTr("Open", "open wallet with PIN") + + onClicked: { + if (root.numericInput) + var pwd = keyboard.dataInput.editor.enteredString; + else + pwd = pwdField.text; + if (pwd !== "") { + portfolio.current.decrypt(pwd); + if (!portfolio.current.isDecrypted) { + keyboard.dataInput.editor.reset(); + shaker.start(); + if (!numericInput) { + pwdField.selectAll(); + pwdField.forceActiveFocus(); + } + } + } + } + } +} diff --git a/guis/mobile/images/keyboard-light.svg b/guis/mobile/images/keyboard-light.svg new file mode 100644 index 0000000..7d83598 --- /dev/null +++ b/guis/mobile/images/keyboard-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/keyboard.svg b/guis/mobile/images/keyboard.svg new file mode 100644 index 0000000..f61c068 --- /dev/null +++ b/guis/mobile/images/keyboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/num-keyboard-light.svg b/guis/mobile/images/num-keyboard-light.svg new file mode 100644 index 0000000..1e33787 --- /dev/null +++ b/guis/mobile/images/num-keyboard-light.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/guis/mobile/images/num-keyboard.svg b/guis/mobile/images/num-keyboard.svg new file mode 100644 index 0000000..f7cdc4a --- /dev/null +++ b/guis/mobile/images/num-keyboard.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 9d62e55..fcf7725 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -1,9 +1,15 @@ + images/eye-closed-light.png + images/eye-closed.png + images/eye-open-light.png + images/eye-open.png images/edit-copy.svg images/edit-copy-light.svg images/internet.svg images/CashTokens.svg + images/lock-light.svg + images/lock.svg images/Flowee-Symbols.otf Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 835ff79..4d89602 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -341,6 +341,7 @@ Page { anchors.bottomMargin: 15 width: parent.width enabled: !paymentDetail.maxSelected + dataInput: priceInput } Rectangle { -- 2.54.0 From 5878f8a6b7597f66f5d5a19f171b9620c99f67c9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Jun 2023 11:19:40 +0200 Subject: [PATCH 0621/1428] Updates to the AccountSelector * Add a total balance label at the bottom if we have more than one wallet. * Add lock icons for encrypted or even partially encrypted wallets * Add label to indicate an encrypted wallet needs opening before it can be used. * Fix spacing in case of larger fonts. --- guis/mobile/AccountSelectorPopup.qml | 69 ++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 32b3e44..c5598cc 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -55,7 +55,14 @@ QQC2.Popup { model: portfolio.accounts delegate: Item { width: columnLayout.width - height: accountName.height + lastActive.height + 6 * 3 + height: { + var h = accountName.height + lastActive.height + 6 * 3; + // if the width is not enough to fit, the bchamount moves + // to its own line. + if (6 + lastActive.width + 6 + bchAmountLabel.width + 6 > width) + h += bchAmountLabel.height + 6 + return h; + } Rectangle { id: selectedItemIndicator visible: modelData === root.selectedAccount @@ -69,10 +76,25 @@ QQC2.Popup { color: palette.highlight visible: selectedItemIndicator.visible } + + Image { + id: lockIcon + x: 6 + width: 12 + height: 12 + anchors.verticalCenter: parent.verticalCenter + visible: modelData.needsPinToPay || modelData.needsPinToOpen + source: { + if (visible) + return "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + return ""; + } + } + Flowee.Label { id: accountName y: 6 - x: 6 + x: lockIcon.visible ? 24: 6 text: modelData.name } Flowee.Label { @@ -81,22 +103,36 @@ QQC2.Popup { anchors.right: parent.right anchors.rightMargin: 6 text: Fiat.formattedPrice(modelData.balanceConfirmed + modelData.balanceUnconfirmed, Fiat.price) + visible: modelData.isDecrypted || !modelData.needsPinToOpen } Flowee.Label { id: lastActive anchors.top: accountName.bottom + anchors.topMargin: 6 anchors.left: accountName.left text: qsTr("last active") + ": " + Pay.formatDate(modelData.lastMinedTransaction) font.pixelSize: root.font.pixelSize * 0.8 - font.bold: false + visible: modelData.isDecrypted || !modelData.needsPinToOpen + } + Flowee.Label { + id: lockStatus + anchors.top: accountName.bottom + anchors.topMargin: 6 + anchors.left: accountName.left + text: qsTr("Needs PIN to open") + font.pixelSize: root.font.pixelSize * 0.8 + visible: !lastActive.visible } Flowee.BitcoinAmountLabel { + id: bchAmountLabel anchors.right: fiat.right - anchors.top: lastActive.top + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 font.pixelSize: lastActive.font.pixelSize showFiat: false value: modelData.balanceConfirmed + modelData.balanceUnconfirmed colorize: false + visible: modelData.isDecrypted || !modelData.needsPinToOpen } MouseArea { anchors.fill: parent @@ -107,5 +143,30 @@ QQC2.Popup { } } } + + Item { + width: parent.width + opacity: 0.8 // less bright || dark text + implicitHeight: 5 + totalLabel.implicitHeight + + (totalLabel.width + 6 + 6 + totalAmount.width + 6 > width + ? totalAmount.implicitHeight : 0) + Flowee.Label { + id: totalLabel + x: 6 + y: 5 + text: qsTr("Balance Total") + ":" + visible: !portfolio.singleAccountSetup + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.BitcoinAmountLabel { + id: totalAmount + value: portfolio.totalBalance + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.bottom: parent.bottom + font.pixelSize: root.font.pixelSize * 0.8 + colorize: false + } + } } } -- 2.54.0 From 983fb28c9f28421e05da663949c3eb8d984f49af Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Jun 2023 11:33:08 +0200 Subject: [PATCH 0622/1428] Add support for the 'countBalance' setting Now the mobile skin can also enable the per-wallet 'count-balance' bool. If set to false it makes the balances of the wallet no longer be visible or indeed counted in the overview. Specifically the AccountSelectorPopup --- guis/mobile/AccountPageListItem.qml | 6 ++++++ guis/mobile/AccountSelectorPopup.qml | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 301cb60..fccec98 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -338,6 +338,12 @@ QQC2.Control { } } } + Flowee.CheckBox { + visible: !singleAccountSetup && !root.account.isArchived + checked: root.account.countBalance === false + text: qsTr("Hide balance in overviews") + onClicked: root.account.countBalance = !checked + } Flowee.CheckBox { visible: !singleAccountSetup && !root.account.isArchived checked: root.account.isPrivate diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index c5598cc..108fdc1 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -103,7 +103,7 @@ QQC2.Popup { anchors.right: parent.right anchors.rightMargin: 6 text: Fiat.formattedPrice(modelData.balanceConfirmed + modelData.balanceUnconfirmed, Fiat.price) - visible: modelData.isDecrypted || !modelData.needsPinToOpen + visible: modelData.countBalance && (modelData.isDecrypted || !modelData.needsPinToOpen) } Flowee.Label { id: lastActive @@ -132,7 +132,7 @@ QQC2.Popup { showFiat: false value: modelData.balanceConfirmed + modelData.balanceUnconfirmed colorize: false - visible: modelData.isDecrypted || !modelData.needsPinToOpen + visible: fiat.visible } MouseArea { anchors.fill: parent -- 2.54.0 From 3974dc4b56b3734ffcb416c5bec32341fb1a8de0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Jun 2023 19:23:47 +0200 Subject: [PATCH 0623/1428] Hide fully encrypted wallets from payment flow When the user is trying to create a transaction we start with the currently selected wallet. The user is able to change to another wallet in such flows, but we stop them from seeing the fully encrypted wallet as the workflow would just become too tedius and confusing. --- guis/mobile/AccountSelectorPopup.qml | 18 ++++++++++++++++-- guis/mobile/AccountSelectorWidget.qml | 4 ++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 108fdc1..bb52c73 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -27,6 +27,8 @@ QQC2.Popup { focus: true // to allow Escape to close it property QtObject selectedAccount: portfolio.current + property bool showTotalBalance: true + property bool showEncryptedAccounts: true Connections { target: menuOverlay @@ -52,7 +54,19 @@ QQC2.Popup { Repeater { // portfolio holds all our accounts width: parent.width - model: portfolio.accounts + model: { + var accounts = portfolio.accounts + if (root.showEncryptedAccounts === false) { + // then we filter them out here. + let copy = accounts; + accounts = []; + for (let account of copy) { + if (account.isDecrypted || !account.needsPinToOpen) + accounts.push(account); + } + } + return accounts; + } delegate: Item { width: columnLayout.width height: { @@ -147,6 +161,7 @@ QQC2.Popup { Item { width: parent.width opacity: 0.8 // less bright || dark text + visible: !portfolio.singleAccountSetup && root.showTotalBalance implicitHeight: 5 + totalLabel.implicitHeight + (totalLabel.width + 6 + 6 + totalAmount.width + 6 > width ? totalAmount.implicitHeight : 0) @@ -155,7 +170,6 @@ QQC2.Popup { x: 6 y: 5 text: qsTr("Balance Total") + ":" - visible: !portfolio.singleAccountSetup font.pixelSize: root.font.pixelSize * 0.8 } Flowee.BitcoinAmountLabel { diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index d0fddf9..077f0b2 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -98,5 +98,9 @@ Rectangle { y: -10 onSelectedAccountChanged: payment.account = selectedAccount selectedAccount: payment.account + + // when showing the selector widget these make little to no sense. + showTotalBalance: false + showEncryptedAccounts: false } } -- 2.54.0 From 049d735a53a4aff97084a8b7c48ee4af211bc193 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Jun 2023 22:46:22 +0200 Subject: [PATCH 0624/1428] Add decrypt of PinToPay wallet in mobile this also changes the API propertes that handle encryption details a little. Making them faster and the meaning follows the logical conclusion of the naming better. Specifically: when needsPinToOpen would return true, now needsPinToPay will also always return true. --- guis/Flowee/AccountTypeLabel.qml | 4 +-- guis/desktop/AccountConfigMenu.qml | 2 +- guis/desktop/WalletEncryption.qml | 10 +++---- guis/mobile/AccountSelectorPopup.qml | 2 +- guis/mobile/InstaPayConfigPage.qml | 2 +- guis/mobile/PayWithQR.qml | 43 ++++++++++++++++++++++++++++ guis/mobile/UnlockWalletPanel.qml | 12 +++++--- src/AccountInfo.cpp | 18 ++++-------- src/AccountInfo.h | 11 +++++++ src/Wallet_encryption.cpp | 1 - 10 files changed, 78 insertions(+), 27 deletions(-) diff --git a/guis/Flowee/AccountTypeLabel.qml b/guis/Flowee/AccountTypeLabel.qml index b76e5b0..594e410 100644 --- a/guis/Flowee/AccountTypeLabel.qml +++ b/guis/Flowee/AccountTypeLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ Label { property QtObject account: null wrapMode: Text.WordWrap - visible: root.account.isDecrypted || root.account.needsPinToPay + visible: root.account.isDecrypted || !root.account.needsPinToOpen font.italic: true text: { if (root.account.isSingleAddressAccount) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 98cb746..46172bc 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -64,7 +64,7 @@ ConfigItem { items.push(detailsAction); var encrypted = root.account.needsPinToOpen; var decrypted = root.account.isDecrypted; - if ((encrypted || root.account.needsPinToPay) && decrypted) + if (root.account.needsPinToPay && decrypted) items.push(closeWalletAction); if (onMainView && encrypted && !decrypted && tabbar.currentIndex != 0) items.push(openWalletAction); diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index e648289..9999aff 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -128,10 +128,10 @@ FocusScope { Label { text: { - if (root.account.needsPinToPay) - return qsTr("Wallet already has pin to pay applied") if (root.account.needsPinToOpen) return qsTr("Wallet already has pin to open applied") + if (root.account.needsPinToPay) + return qsTr("Wallet already has pin to pay applied") return qsTr("Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first.") } wrapMode: Text.WordWrap @@ -148,7 +148,7 @@ FocusScope { width: stack.width } Label { - visible: root.account.needsPinToPay + visible: root.account.needsPinToPay && !root.account.needPinToOpen text: "This wallet already has pin to pay applied, you may upgrade it to pin to open but it will remove pin to pay. The password you provide must be the same as the one for pin to pay"; wrapMode: Text.WordWrap width: stack.width @@ -158,10 +158,10 @@ FocusScope { GridLayout { visible: { - if (root.account.needsPinToPay) - return optionsRow.selectedKey == 1; if (root.account.needsPinToOpen) return false; + if (root.account.needsPinToPay) + return optionsRow.selectedKey == 1; return true; } diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index bb52c73..858315a 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -97,7 +97,7 @@ QQC2.Popup { width: 12 height: 12 anchors.verticalCenter: parent.verticalCenter - visible: modelData.needsPinToPay || modelData.needsPinToOpen + visible: modelData.needsPinToPay source: { if (visible) return "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); diff --git a/guis/mobile/InstaPayConfigPage.qml b/guis/mobile/InstaPayConfigPage.qml index 9bbc1f2..780a1f4 100644 --- a/guis/mobile/InstaPayConfigPage.qml +++ b/guis/mobile/InstaPayConfigPage.qml @@ -41,7 +41,7 @@ Page { width: parent.width radius: 10 height: warning.implicitHeight + 20 - visible: root.account.needsPinToOpen || root.account.needsPinToPay + visible: root.account.needsPinToPay color: "#00000000" border.width: 2 border.color: "blue" diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index e209459..2df631c 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -261,6 +261,49 @@ Page { width: parent.width enabled: payment.isValid && payment.txPrepared onActivated: payment.broadcast() + visible: payment.account.isDecrypted || !payment.account.needsPinToPay + } + + Item { + id: decryptButton + visible: !slideToApprove.visible + anchors.fill: slideToApprove + + Image { + id: lockIcon + source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + x: 10 + y: 5 + width: 50 + height: 50 + smooth: true + } + + Flowee.Button { + height: parent.height - 10 + width: parent.width - 80 + x: 70 + y: 5 + text: qsTr("Unlock Wallet") + onClicked: thePile.push(unlockInPage) + } + + Component { + id: unlockInPage + Page { + headerText: payment.account.name + UnlockWalletPanel { + account: payment.account + Connections { + target: payment.account + function onIsDecryptedChanged() { + if (payment.account.isDecrypted) + thePile.pop() + } + } + } + } + } } Flowee.BroadcastFeedback { diff --git a/guis/mobile/UnlockWalletPanel.qml b/guis/mobile/UnlockWalletPanel.qml index b5e179e..494ee28 100644 --- a/guis/mobile/UnlockWalletPanel.qml +++ b/guis/mobile/UnlockWalletPanel.qml @@ -26,6 +26,8 @@ Item { property bool numericInput: true + property QtObject account: portfolio.current + Image { id: lockIcon source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); @@ -135,7 +137,7 @@ Item { Flowee.TextField { id: pwdField - x: parent.numericInput ? parent.width + 10 : 0 + x: parent.numericInput ? parent.width + 30 : 0 anchors.top: introText.bottom anchors.topMargin: 20 width: parent.width @@ -189,9 +191,11 @@ Item { else pwd = pwdField.text; if (pwd !== "") { - portfolio.current.decrypt(pwd); - if (!portfolio.current.isDecrypted) { - keyboard.dataInput.editor.reset(); + root.account.decrypt(pwd); + keyboard.dataInput.editor.reset(); + if (root.account.isDecrypted) { + pwdField.text = ""; + } else { shaker.start(); if (!numericInput) { pwdField.selectAll(); diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 9d2e900..64aa13c 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -300,7 +300,7 @@ void AccountInfo::setHasFreshTransactions(bool fresh) bool AccountInfo::needsPinToPay() const { - return m_wallet->encryption() == Wallet::SecretsEncrypted; + return m_wallet->encryption() != Wallet::NotEncrypted; } bool AccountInfo::needsPinToOpen() const @@ -310,17 +310,7 @@ bool AccountInfo::needsPinToOpen() const bool AccountInfo::isDecrypted() const { - if (m_wallet->encryption() == Wallet::NotEncrypted) - return true; - - const auto &secrets = m_wallet->walletSecrets(); - if (secrets.empty()) - return false; - for (auto i = secrets.begin(); i != secrets.end(); ++i) { - if (i->second.privKey.isValid()) - return true; - } - return false; + return m_wallet->isDecrypted(); } void AccountInfo::setPrimaryAccount(bool isPrimary) @@ -371,6 +361,10 @@ void AccountInfo::encryptPinToOpen(const QString &password) bool AccountInfo::decrypt(const QString &password) { + if (isDecrypted()) { + logCritical() << "Requested to decrypt an already decrypted wallet"; + return false; + } const bool shouldAutoclose = m_wallet->encryption() == Wallet::SecretsEncrypted; const bool success = m_wallet->decrypt(password); if (shouldAutoclose && success) { diff --git a/src/AccountInfo.h b/src/AccountInfo.h index d95b653..f204f51 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -141,8 +141,19 @@ public: bool hasFreshTransactions() const; void setHasFreshTransactions(bool fresh); + /** + * Returns true if a PIN is needed to start a payment. + * This means that the wallet is either partially or fully encrypted. + */ bool needsPinToPay() const; + /** + * Returns true if a PIN is needed to open the wallet. + * This means that the wallet is fully encrypted. + */ bool needsPinToOpen() const; + /** + * Returs true if the wallet is either not encrypted, or currently decrypted. + */ bool isDecrypted() const; int initialBlockHeight() const; diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 3025971..828150a 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -330,7 +330,6 @@ void Wallet::forgetEncryptedSecrets() m_hdData->walletMnemonicPwd.clear(); m_hdData->masterKey = HDMasterKey(); } - // above method emits encryptionChanged() } else if (m_encryptionLevel == FullyEncrypted) { if (m_segment) -- 2.54.0 From 6f8369e4ff06f606ed5b6355aff1ad0994da2194 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 30 Jun 2023 22:25:19 +0200 Subject: [PATCH 0625/1428] [UX] Take preedit text into account. For instant validation and similar, use the preedit text into account to work around the silly design in Qt where the normal is made difficult. --- guis/Flowee/MultilineTextField.qml | 17 +++++++++++++++++ guis/Flowee/PasswdDialog.qml | 2 +- guis/Flowee/TextField.qml | 16 ++++++++++++++++ guis/mobile/EditableLabel.qml | 4 ++-- guis/mobile/ImportWalletPage.qml | 2 +- guis/mobile/ReceiveTab.qml | 4 ++-- 6 files changed, 39 insertions(+), 6 deletions(-) diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index c2bcf4f..3e84b84 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -31,11 +31,28 @@ QQC2.Control { id: root property string text: "" + property string totalText: { + var t = textEdit.text; + /** + * Qt has a special variable for dealing with input-methods. For us it makes life just + * a little harder as we simply want to be able to get the total in order to validate + * it or update the backend as the user is typing in order to not lose "uncommitted" text. + */ + var pre = textEdit.preeditText; + if (pre !== "") { + let first = t.substring(0, textEdit.cursorPosition) + let last = t.substring(textEdit.cursorPosition) + t = first + pre + last; + } + return t; + } + property string placeholderText: "" property var nextFocusTarget: null property color color: palette.text signal editingFinished; property alias readOnly: textEdit.readOnly + property alias inputMethodHints: textEdit.inputMethodHints implicitHeight: textEdit.implicitHeight + 10 implicitWidth: 100 diff --git a/guis/Flowee/PasswdDialog.qml b/guis/Flowee/PasswdDialog.qml index 3d31302..ae21246 100644 --- a/guis/Flowee/PasswdDialog.qml +++ b/guis/Flowee/PasswdDialog.qml @@ -35,7 +35,7 @@ Dialog { id: textEntryField Flowee.TextField { echoMode: TextInput.Password - onTextChanged: passwdDialog.pwd = text + onTotalTextChanged: passwdDialog.pwd = totalText onAccepted: passwdDialog.accept(); focus: true } diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index c31cc36..54866d5 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -28,6 +28,22 @@ QQC2.TextField { placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#3e3e3e" color: palette.windowText + property string totalText: { + var t = text; + /** + * Qt has a special variable for dealing with input-methods. For us it makes life just + * a little harder as we simply want to be able to get the total in order to validate + * it or update the backend as the user is typing in order to not lose "uncommitted" text. + */ + var pre = preeditText; + if (pre !== "") { + let first = t.substring(0, cursorPosition) + let last = t.substring(cursorPosition) + t = first + pre + last; + } + return t; + } + background: Rectangle { implicitHeight: root.contentHeight + 2 implicitWidth: 140 diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index edb3bbd..26a7a57 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -72,8 +72,8 @@ Item { visible: false focus: visible text: ourLabel.text - onTextChanged: { - ourLabel.text = text; + onTotalTextChanged: { + ourLabel.text = totalText; root.edited(); } onEditingFinished: visible = false diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index b87e1e4..9d970d3 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -25,7 +25,7 @@ Page { id: importAccount headerText: qsTr("Import Wallet") - property var typedData: Pay.identifyString(secrets.text) + property var typedData: Pay.identifyString(secrets.totalText) property bool finished: typedData === Wallet.PrivateKey || (typedData === Wallet.CorrectMnemonic && derivationPath.derivationOk); property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; property bool isPrivateKey: typedData === Wallet.PrivateKey diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 9139b80..c801704 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -178,7 +178,7 @@ FocusScope { Flowee.Button { anchors.right: parent.right text: qsTr("Clear") - enabled: description.text !== "" || priceInput.paymentBackend.paymentAmount > 0; + enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; onClicked: { request.clear(); request.account = portfolio.current; @@ -221,7 +221,7 @@ FocusScope { Flowee.TextField { id: description width: parent.width - onTextChanged: if (!inputMethodComposing) request.message = text + onTotalTextChanged: request.message = totalText onActiveFocusChanged: if (activeFocus) editBox.editingTextField = true } -- 2.54.0 From fe6e1020d25faf8a4186d92934bda54de0cbbd0b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Jul 2023 21:24:16 +0200 Subject: [PATCH 0626/1428] Remove overdesign of menu model The model had much more functionality we'd ever use in the UI, it turns out it was over designed. Remove the unneeded complexity and just have one simple list. --- src/MenuModel.cpp | 30 +++++++++--------------------- src/MenuModel.h | 7 ++----- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index fffa5bd..cf5b335 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -18,20 +18,13 @@ #include "MenuModel.h" MenuModel::MenuModel(QObject *parent) - : QAbstractListModel{parent}, - m_current(&m_root) + : QAbstractListModel{parent} { - m_root.children.append({tr("Explore"), "./ExploreModules.qml", {}}); - m_root.children.append({tr("Settings"), "./GuiSettings.qml", {}}); - m_root.children.append({tr("Network Details"), "./NetView.qml", {}}); - m_root.children.append({tr("About"), "./About.qml", {}}); - m_root.children.append({tr("Wallets"), "AccountsList.qml", {}}); - /* - m_root.children.append({tr("Settings"), "", { - { tr("Security"), "", {} }, - { tr("Settings 2"), "", {} }, - }}); */ - + m_data.append({tr("Explore"), "./ExploreModules.qml"}); + m_data.append({tr("Settings"), "./GuiSettings.qml"}); + m_data.append({tr("Network Details"), "./NetView.qml"}); // TODO move to a module + m_data.append({tr("About"), "./About.qml"}); + m_data.append({tr("Wallets"), "AccountsList.qml"}); } int MenuModel::rowCount(const QModelIndex &parent) const @@ -39,8 +32,7 @@ int MenuModel::rowCount(const QModelIndex &parent) const if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree return 0; - assert(m_current); - return m_current->children.size(); + return m_data.size(); } QVariant MenuModel::data(const QModelIndex &index, int role) const @@ -48,17 +40,14 @@ QVariant MenuModel::data(const QModelIndex &index, int role) const if (!index.isValid()) return QVariant(); assert(index.row() >= 0); - assert(m_current); - assert(m_current->children.size() > index.row()); - const auto &item = m_current->children.at(index.row()); + assert(m_data.size() > index.row()); + const auto &item = m_data.at(index.row()); switch (role) { case Name: return item.name; case Target: return item.target; - case HasChildren: - return !item.children.isEmpty(); } return QVariant(); } @@ -68,6 +57,5 @@ QHash MenuModel::roleNames() const QHash answer; answer[Name] = "name"; answer[Target] = "target"; - answer[HasChildren] = "hasChildren"; return answer; } diff --git a/src/MenuModel.h b/src/MenuModel.h index 7a76b82..97afb25 100644 --- a/src/MenuModel.h +++ b/src/MenuModel.h @@ -33,18 +33,15 @@ public: private: enum Roles { Name, - Target, - HasChildren + Target }; struct MenuItem { QString name; QString target; // the QML component to load - QList children; }; - MenuItem m_root; - MenuItem *m_current; + QList m_data; }; #endif -- 2.54.0 From b2a1b31efb04d56b720afc47f4f6fab643e2e13e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Jul 2023 23:12:01 +0200 Subject: [PATCH 0627/1428] Fixlet in error-message Fix grammar of the exception message. --- src/Payment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 79af304..11fa765 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -173,7 +173,7 @@ void Payment::prepare() m_wallet = m_account->wallet(); if (m_wallet->encryption() > Wallet::NotEncrypted) { if (!m_wallet->isDecrypted()) - throw std::runtime_error("Wallet is needs to be decrypted first"); + throw std::runtime_error("Wallet needs to be decrypted first"); } TransactionBuilder builder; -- 2.54.0 From 3ec9a5cb4d94ebd7f81a85f4d83a560e14985cf2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Jul 2023 12:16:46 +0200 Subject: [PATCH 0628/1428] [UX] Cleanup the NewAccount page sources. Also add a little spacing. --- guis/mobile/NewAccount.qml | 35 ++++------------------------------- guis/mobile/PageTitledBox.qml | 1 + 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 1a8cc10..4862bb4 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -39,10 +39,11 @@ Page { PageTitledBox { title: qsTr("Create a New Wallet") width: parent.width + spacing: 10 Flowee.CardTypeSelector { title: qsTr("HD wallet") - onClicked: root.selectedKey = key + onClicked: thePile.push(newHDWalletScreen); width: parent.width * 0.75 anchors.horizontalCenter: parent.horizontalCenter selected: true @@ -52,11 +53,6 @@ Page { qsTr("Easy to backup", "Context: wallet type"), qsTr("Most compatible", "The most compatible wallet type") ] - - MouseArea { - anchors.fill: parent - onClicked: thePile.push(newHDWalletScreen); - } Rectangle { anchors.fill: parent color: "#00000000" @@ -69,7 +65,7 @@ Page { Flowee.CardTypeSelector { title: qsTr("Basic") width: parent.width * 0.75 - onClicked: root.selectedKey = key + onClicked: thePile.push(newBaseWalletScreen); selected: true anchors.horizontalCenter: parent.horizontalCenter @@ -78,18 +74,6 @@ Page { qsTr("Difficult to backup", "Context: wallet type"), qsTr("Great for brief usage", "Context: wallet type") ] - - MouseArea { - anchors.fill: parent - onClicked: thePile.push(newBaseWalletScreen); - } - Rectangle { - anchors.fill: parent - color: "#00000000" - radius: 10 - border.width: 5 - border.color: palette.mid - } } } @@ -99,7 +83,7 @@ Page { Flowee.CardTypeSelector { title: qsTr("Import") width: parent.width * 0.75 - onClicked: root.selectedKey = key + onClicked: thePile.push("./ImportWalletPage.qml"); anchors.horizontalCenter: parent.horizontalCenter selected: true @@ -107,17 +91,6 @@ Page { qsTr("Imports seed-phrase"), qsTr("Imports private key") ] - MouseArea { - anchors.fill: parent - onClicked: thePile.push("./ImportWalletPage.qml"); - } - Rectangle { - anchors.fill: parent - color: "#00000000" - radius: 10 - border.width: 5 - border.color: palette.mid - } } } } diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index 42dd71b..b7812e8 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -21,6 +21,7 @@ import "../Flowee" as Flowee Item { property alias title: boxTitle.text + property alias spacing: contentColumn.spacing default property alias content: contentColumn.children implicitHeight: { -- 2.54.0 From bff9cd5dc2429b4ebc0eec14a0f655a444e200d4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 30 Jun 2023 22:27:27 +0200 Subject: [PATCH 0629/1428] Introduce app-level password feature and more. We already allowed individual wallets to get PIN-to-Open/PIN-to-Pay but that is too advanced for most users. Not to mention that encrypted private keys means a slower payment process and certain types of features become impossible. Like auto-invoicing (incasso). The gap, for mobile, is a simple not-encrypting password on startup of the app which is likely what 80% of the privacy / security minded people on mibile will be more than happy with. This adds this mode and additionally streamlines the encrypted modes of wallets. --- guis/mobile.qrc | 3 + guis/mobile/LockApplication.qml | 269 ++++++++++++++++++++++ guis/mobile/PayWithQR.qml | 3 + guis/mobile/UnlockApplication.qml | 27 +++ guis/mobile/UnlockWalletPanel.qml | 193 +--------------- guis/mobile/UnlockWidget.qml | 236 +++++++++++++++++++ guis/mobile/main.qml | 56 ++++- modules/build-transaction/PayToOthers.qml | 32 ++- src/FloweePay.cpp | 108 ++++++++- src/FloweePay.h | 38 ++- src/MenuModel.cpp | 1 + src/main.cpp | 1 + 12 files changed, 775 insertions(+), 192 deletions(-) create mode 100644 guis/mobile/LockApplication.qml create mode 100644 guis/mobile/UnlockApplication.qml create mode 100644 guis/mobile/UnlockWidget.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 8a13c2b..9e538f4 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -83,6 +83,9 @@ mobile/InstaPayConfigPage.qml mobile/InstaPayConfigButton.qml mobile/ExploreModules.qml + mobile/UnlockWidget.qml mobile/UnlockWalletPanel.qml + mobile/UnlockApplication.qml + mobile/LockApplication.qml diff --git a/guis/mobile/LockApplication.qml b/guis/mobile/LockApplication.qml new file mode 100644 index 0000000..9698289 --- /dev/null +++ b/guis/mobile/LockApplication.qml @@ -0,0 +1,269 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import QtQuick.Controls as QQC2 +import Flowee.org.pay + +Page { + id: root + headerText: qsTr("Security") + + Column { + id: column + width: parent.width + y: 10 + spacing: 20 + + PageTitledBox { + id: pinOnStartup + title: qsTr("PIN on startup") + width: parent.width + spacing: 10 + + property string newPassword: "" + property bool verified: false + + Flowee.Label { + id: mainLabel + text: Pay.appProtection === FloweePay.AppUnlocked + ? qsTr("Enter current PIN") : qsTr("Enter new PIN"); + width: parent.width + wrapMode: Text.Wrap + } + Item { + width: parent.width + height: outline.height + 12 + Rectangle { + id: outline + y: 6 + color: "#00000000" + border.width: 1 + border.color: palette.button + height: pwd1.height + 10 + width: Math.max(pwd1.implicitWidth, parent.width / 2) + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: { + pinOnStartup.newPassword = ""; + pinOnStartup.verified = false; + inputWidget.open(); + } + } + } + + Flowee.Label { + id: pwd1 + text: pinOnStartup.newPassword === "" ? "" : "∙∙∙∙∙"; + font.pixelSize: mainLabel.font.pixelSize * 2 + anchors.centerIn: outline + } + } + + Flowee.Label { + text: qsTr("Repeat PIN") + width: parent.width + wrapMode: Text.Wrap + visible: Pay.appProtection === FloweePay.NoProtection + } + Item { + width: parent.width + height: outline2.height + 12 + visible: Pay.appProtection === FloweePay.NoProtection + Rectangle { + id: outline2 + y: 6 + color: "#00000000" + border.width: 1 + border.color: palette.button + height: pwd2.height + 10 + width: Math.max(pwd2.implicitWidth, parent.width / 2) + anchors.horizontalCenter: parent.horizontalCenter + MouseArea { + anchors.fill: parent + onClicked: inputWidget.open(); + } + } + + Flowee.Label { + id: pwd2 + text: pinOnStartup.verified ? "∙∙∙∙∙" : ""; + font.pixelSize: mainLabel.font.pixelSize * 2 + anchors.centerIn: outline2 + } + } + + Item { + width: parent.width + height: button.height + Flowee.Button { + id: button + text: Pay.appProtection === FloweePay.AppUnlocked ? qsTr("Remove PIN") : qsTr("Set PIN") + anchors.right: parent.right + enabled: pinOnStartup.verified + onClicked: { + if (Pay.appProtection === FloweePay.AppUnlocked) { + // then we are trying to remove the PIN. We just set it to an empty string + pinOnStartup.newPassword = ""; + var feedback = qsTr("PIN has been removed"); + } + else { + feedback = qsTr("PIN has been set"); + } + Pay.setAppPassword(pinOnStartup.newPassword) + pinOnStartup.newPassword = ""; + pinOnStartup.verified = false; + notificationPopup.show(feedback); + } + } + Behavior on y { NumberAnimation { } } + } + } + + PageTitledBox { + title: qsTr("Encrypt your wallet data") + width: parent.width + + TextButton { + text: qsTr("Require PIN to Pay on wallet") + subtext: qsTr("Secure against spending") + showPageIcon: true + } + TextButton { + text: qsTr("Require PIN to Open wallet") + subtext: qsTr("Secure against all usage") + showPageIcon: true + } + } + } + + Item { + } + + + // the invisible drag handler is separate but on similar to the input widget. + Item { + id: dragHandler + width: parent.width + y: root.height + 10 // outside of viewport + height: 25 + onYChanged: { + // widget moves with us, unless its moved too far + // then we insta close. + var base = inputWidget.baseY; + var diff = y - base; + inputWidget.y = diff > 250 ? root.height + 10 : y - 5 + } + + DragHandler { + xAxis.enabled: false + yAxis.minimum: root.height - inputWidget.height - 15 + acceptedButtons: Qt.LeftButton + margin: 15 + cursorShape: Qt.DragMoveCursor + + onActiveChanged: { + if (!active) { + // either close or reset. + var base = inputWidget.baseY; + var diff = inputWidget.y - base; + dragHandler.y = diff > 130 ? root.height + 10 : base + 5 + } + } + } + } + Rectangle { + id: inputWidget + + function open() { + dragHandler.y = baseY + widget.takeFocus(); + } + function close() { + dragHandler.y = parent.height + 10 // outside of viewport + widget.password = ""; + } + + property int baseY: root.height - height - 20 + color: palette.window + border.width: 1 + border.color: palette.highlight + radius: 20 + width: parent.width + height: Math.min(root.height, 610) + UnlockWidget { + id: widget + y: 10 + x: 10 + width: parent.width - 20 + height: parent.height - 40 + buttonText: qsTr("Ok") + + onPasswordEntered: { + var pwd = password; + if (pwd === "") + return; + + if (pinOnStartup.newPassword === "") { + if (Pay.appProtection === FloweePay.AppUnlocked) { // then the PIN needs to be checked. + if (Pay.checkAppPassword(pwd)) { + pinOnStartup.verified = true; + } + else { + passwordIncorrect(); + return; + } + } + + pinOnStartup.newPassword = pwd; + inputWidget.close(); + widget.acceptedPassword(); + } + else if (pinOnStartup.newPassword === pwd) { + pinOnStartup.verified = true; + inputWidget.close(); + widget.acceptedPassword(); + } else { + // validate failed. Passwords not equal + passwordIncorrect(); + } + } + } + Rectangle { + width: parent.width - 50 + anchors.horizontalCenter: parent.horizontalCenter + y: 5 + height: 3 + color: palette.button + radius: 3 + } + + Behavior on y { NumberAnimation { duration: 120 } } + } + + // android navigation + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Back) { + if (inputWidget.y < root.height) { + inputWidget.close(); + event.accepted = true; + } + } + } +} diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 2df631c..8702f21 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -293,6 +293,9 @@ Page { Page { headerText: payment.account.name UnlockWalletPanel { + anchors.fill: parent + anchors.margins: 10 + account: payment.account Connections { target: payment.account diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml new file mode 100644 index 0000000..7cdfde5 --- /dev/null +++ b/guis/mobile/UnlockApplication.qml @@ -0,0 +1,27 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +UnlockWidget { + onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); + + // called from main.qml, need to be implemented here + // since we dont extend the Page type. + function takeFocus() { + } +} diff --git a/guis/mobile/UnlockWalletPanel.qml b/guis/mobile/UnlockWalletPanel.qml index 494ee28..af1c8c5 100644 --- a/guis/mobile/UnlockWalletPanel.qml +++ b/guis/mobile/UnlockWalletPanel.qml @@ -16,193 +16,16 @@ * along with this program. If not, see . */ import QtQuick -import "../Flowee" as Flowee -import Flowee.org.pay; -Item { +UnlockWidget { id: root - anchors.fill: parent - anchors.margins: 10 + // allows users to override which wallet is to be unlocked. + property QtObject account: portfolio.current; - property bool numericInput: true - - property QtObject account: portfolio.current - - Image { - id: lockIcon - source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - width: 80 - height: 80 - anchors.horizontalCenter: parent.horizontalCenter - smooth: true - y: 40 - } - - Image { - width: parent.numericInput ? 40 : 30 - height: width - anchors.right: parent.right - source: "qrc:/" + (parent.numericInput ? "keyboard" : "num-keyboard") - + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - - MouseArea { - anchors.fill: parent - onClicked: { - keyboard.dataInput.editor.reset(); - var newValue = !root.numericInput; - root.numericInput = newValue; - if (newValue === false) - pwdField.forceActiveFocus(); - } - } - } - - Flowee.Label { - id: introText - text: qsTr("Enter your wallet passcode") - anchors.top: lockIcon.bottom - anchors.topMargin: 20 - Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes - } - - // Show the typed pin code, but as bullets as you type. - Row { - anchors.top: introText.bottom - anchors.topMargin: 20 - spacing: 10 - anchors.horizontalCenter: parent.horizontalCenter - visible: parent.numericInput - Repeater { - id: repeater - // take the number typed and turn it into an array of characters. - model: { - var inputString = keyboard.dataInput.editor.enteredString - var answer = []; - for (let i = 0; i < inputString.length; ++i) { - answer.push(inputString.substr(i, 1)); - } - return answer; - } - - Flowee.Label { - text: { - if (index !== keyboard.dataInput.editor.insertedIndex) - return "∙" - return modelData; - } - font.pixelSize: introText.font.pixelSize * 2 - - Timer { - interval: 1000 - running: index === keyboard.dataInput.editor.insertedIndex - onTriggered: text = "∙" - } - } - } - } - - NumericKeyboardWidget { - id: keyboard - x: parent.numericInput ? 0 : 0 - parent.width - y: parent.height - openButton.height - height - 20 - width: parent.width - dataInput: Item { - property QtObject editor: Item { - property string enteredString; - property int insertedIndex: -1 - function insertNumber(character) { - if (enteredString.length >= 10) - return false; - insertedIndex = enteredString.length; - enteredString = enteredString + character; - return true; - } - function addSeparator() { return false; } - function backspacePressed() { - if (enteredString == "") - return false; - insertedIndex = -1; - enteredString = enteredString.substring(0, enteredString.length - 1); - return true; - } - function reset() { - insertedIndex = -1; - enteredString = ""; - } - } - function shake() { shaker.start(); } - } - Behavior on x { NumberAnimation { } } - } - - Flowee.TextField { - id: pwdField - x: parent.numericInput ? parent.width + 30 : 0 - anchors.top: introText.bottom - anchors.topMargin: 20 - width: parent.width - focus: parent.numericInput === false - onEditingFinished: openButton.clicked() - echoMode: TextInput.Password - Behavior on x { NumberAnimation { } } - - Image { - width: 14 - height: 14 - anchors.right: parent.right - anchors.rightMargin: 5 - anchors.verticalCenter: parent.verticalCenter - source: { - var state = (pwdField.echoMode === TextInput.Normal) ? "closed" : "open"; - var skin = Pay.useDarkSkin ? "-light" : "" - return "qrc:/eye-" + state + skin + ".png"; - } - - MouseArea { - width: parent.width + 20 // extend to right physical edge - x: -5 - y: 0 - parent.y - 5 - height: parent.height + 10 - onClicked: { - var old = pwdField.echoMode; - if (old == TextInput.Normal) - pwdField.echoMode = TextInput.Password - else - pwdField.echoMode = TextInput.Normal - } - } - } - } - - Flowee.Button { - id: openButton - anchors.right: parent.right - y: { - if (parent.numericInput) - return parent.height - height - return pwdField.y + pwdField.height + 10; - } - - text: qsTr("Open", "open wallet with PIN") - - onClicked: { - if (root.numericInput) - var pwd = keyboard.dataInput.editor.enteredString; - else - pwd = pwdField.text; - if (pwd !== "") { - root.account.decrypt(pwd); - keyboard.dataInput.editor.reset(); - if (root.account.isDecrypted) { - pwdField.text = ""; - } else { - shaker.start(); - if (!numericInput) { - pwdField.selectAll(); - pwdField.forceActiveFocus(); - } - } - } - } + onPasswordEntered: { + var pwd = password; + root.account.decrypt(pwd); + if (!root.account.isDecrypted) + passwordIncorrect(); } } diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml new file mode 100644 index 0000000..5c781bb --- /dev/null +++ b/guis/mobile/UnlockWidget.qml @@ -0,0 +1,236 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import Flowee.org.pay; + +Item { + id: root + + implicitWidth: 300 + implicitHeight: 600 + + /// This will hold the password when the user is done typing it. + property string password : ""; + property alias buttonText: openButton.text + + /// Emitted when the user submits the password + signal passwordEntered; + + /// in an onPasswordEntered callback, call this method + /// to signify the password is incorrect. + /// Otherwise just close this widget, or call acceptedPassword() + function passwordIncorrect() { + shaker.start(); + moveFocusTimer.start(); + } + /// clears the password typed, if needed. + function acceptedPassword() { + pwdField.text = ""; + } + + function takeFocus() { + moveFocusTimer.start(); + } + + // we can't move focus from things like the onClicked handler of a button + // therefore a little timer that does it very briefly afterwards is used. + Timer { + id: moveFocusTimer + interval: 50 + onTriggered: { + if (!switchButton.numericInput) { + pwdField.selectAll(); + pwdField.forceActiveFocus(); + } + } + } + + Image { + id: lockIcon + source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 80 + height: 80 + anchors.horizontalCenter: parent.horizontalCenter + smooth: true + y: 40 + } + + Image { + id: switchButton + property bool numericInput: true + + width: numericInput ? 40 : 30 + height: width + anchors.right: parent.right + source: "qrc:/" + (numericInput ? "keyboard" : "num-keyboard") + + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + + MouseArea { + anchors.fill: parent + onClicked: { + keyboard.dataInput.editor.reset(); + var newValue = !switchButton.numericInput; + switchButton.numericInput = newValue; + if (newValue === false) + pwdField.forceActiveFocus(); + } + } + } + + Flowee.Label { + id: introText + text: qsTr("Enter your wallet passcode") + anchors.top: lockIcon.bottom + anchors.topMargin: 20 + Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes + } + + // Show the typed pin code, but as bullets as you type. + Row { + anchors.top: introText.bottom + anchors.topMargin: 20 + spacing: 10 + anchors.horizontalCenter: parent.horizontalCenter + visible: switchButton.numericInput + Repeater { + id: repeater + // take the number typed and turn it into an array of characters. + model: { + var inputString = keyboard.dataInput.editor.enteredString + var answer = []; + for (let i = 0; i < inputString.length; ++i) { + answer.push(inputString.substr(i, 1)); + } + return answer; + } + + Flowee.Label { + text: { + if (index !== keyboard.dataInput.editor.insertedIndex) + return "∙" + return modelData; + } + font.pixelSize: introText.font.pixelSize * 2 + + Timer { + interval: 1000 + running: index === keyboard.dataInput.editor.insertedIndex + onTriggered: text = "∙" + } + } + } + } + + NumericKeyboardWidget { + id: keyboard + + x: switchButton.numericInput ? 0 : 0 - parent.width + y: parent.height - openButton.height - height - 20 + width: parent.width + dataInput: Item { + property QtObject editor: Item { + property string enteredString; + property int insertedIndex: -1 + function insertNumber(character) { + if (enteredString.length >= 10) + return false; + insertedIndex = enteredString.length; + enteredString = enteredString + character; + return true; + } + function addSeparator() { return false; } + function backspacePressed() { + if (enteredString == "") + return false; + insertedIndex = -1; + enteredString = enteredString.substring(0, enteredString.length - 1); + return true; + } + function reset() { + insertedIndex = -1; + enteredString = ""; + } + } + function shake() { shaker.start(); } + } + Behavior on x { NumberAnimation { } } + } + + Flowee.TextField { + id: pwdField + x: switchButton.numericInput ? parent.width + 30 : 0 + visible: !switchButton.numericInput + anchors.top: introText.bottom + anchors.topMargin: 20 + width: parent.width + focus: switchButton.numericInput === false + inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveDataTyped + | (hideText ? Qt.ImhHiddenTextCharacters : Qt.ImhNone) + echoMode: hideText ? TextInput.Password : TextInput.Normal + + property bool hideText: true + onEditingFinished: openButton.clicked() + + Image { + width: 14 + height: 14 + anchors.right: parent.right + anchors.rightMargin: 5 + anchors.verticalCenter: parent.verticalCenter + source: { + var state = (pwdField.hideText) ? "open" : "closed"; + var skin = Pay.useDarkSkin ? "-light" : "" + return "qrc:/eye-" + state + skin + ".png"; + } + + MouseArea { + width: parent.width + 20 // extend to right physical edge + x: -5 + y: 0 - parent.y - 5 + height: parent.height + 10 + onClicked: pwdField.hideText = !pwdField.hideText + } + } + Behavior on x { NumberAnimation { } } + } + + Flowee.Button { + id: openButton + anchors.right: parent.right + y: { + if (switchButton.numericInput) + return parent.height - height + return pwdField.y + pwdField.height + 10; + } + + text: qsTr("Open", "open wallet with PIN") + + onClicked: { + if (switchButton.numericInput) + var pwd = keyboard.dataInput.editor.enteredString; + else + pwd = pwdField.text; + if (pwd !== "") { + root.password = pwd; + keyboard.dataInput.editor.reset(); + root.passwordEntered(); + } + } + } +} diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index aa1a43f..9a03ec7 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -19,6 +19,8 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors +import "../Flowee" as Flowee +import Flowee.org.pay; import QtQuick.Controls.Basic @@ -61,7 +63,7 @@ ApplicationWindow { StackView { id: thePile anchors.fill: parent - initialItem: "./Loading.qml"; + initialItem: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "./Loading.qml"; onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); enabled: !menuOverlay.open @@ -81,4 +83,56 @@ ApplicationWindow { QRScannerOverlay { anchors.fill: parent } + QQC2.Popup { + id: notificationPopup + y: 110 + x: 25 + width: mainWindow.contentItem.width - 50 + height: label.implicitHeight * 3 + dim: true + property alias text: label.text + + function show(message) { + if (message === "") + return; + text = message; + visible = true; + } + Flowee.Label { + id: label + width: parent.width - 12 + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Rectangle { + id: timeoutBar + width: 5 + height: visible ? parent.height - 12 : 1 + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 6 + color: palette.highlight + + Behavior on height { NumberAnimation { duration: notificationPopupTimer.interval } } + } + + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + Overlay.modeless: Rectangle { + color: Pay.useDarkSkin ? "#33000000" : "#33ffffff" + } + + Timer { + id: notificationPopupTimer + running: parent.visible; + interval: 6000 + onTriggered: notificationPopup.visible = false; + + } + } } diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 4d89602..6b261e9 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -389,7 +389,29 @@ Page { } } } - } + /* + * A helper page that allows unlocking an account prior to paying from it. + */ + Component { + id: unlockInPage + Page { + headerText: payment.account.name + UnlockWalletPanel { + anchors.fill: parent + anchors.margins: 10 + + account: payment.account + Connections { + target: payment.account + function onIsDecryptedChanged() { + if (payment.account.isDecrypted) + thePile.pop() + } + } + } + } + } +} // check the comment at loaderForPayments to understand this one function pushToThePile(componentId, detail) { thePile.push(loaderForPayments, @@ -607,10 +629,16 @@ Page { } */ Flowee.Button { - text: qsTr("Prepare Payment...") + property bool walletNeedsDecryptFirst: !payment.account.isDecrypted && payment.account.needsPinToPay; + text: walletNeedsDecryptFirst ? qsTr("Unlock Wallet") : qsTr("Prepare Payment...") enabled: payment.isValid anchors.right: parent.right onClicked: { + if (walletNeedsDecryptFirst) { + thePile.push(unlockInPage); + return; + } + payment.prepare(); if (payment.error !== "") { errorDialog.visible = true; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 30b83fb..8183430 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -22,13 +22,14 @@ #include "PriceDataProvider.h" #include -#include #include #include #include #include #include #include +#include +#include #include #include @@ -41,6 +42,7 @@ #include #include #include +#include #include #include @@ -76,6 +78,10 @@ enum FileTags { WalletSetting_FiatInstaPayEnabled, // bool WalletSetting_FiatInstaPayLimitCurrency, // string, ISO-currency-code WalletSetting_FiatInstaPayLimit, // int, cents. Has to be directly behind the currency. + + // security features + AppProtectionType = 20, // see enum FloweePay::ApplicationProtection + AppProtectionHash, // the hash of the password in case type is AppPassword }; static P2PNet::Chain s_chain = P2PNet::MainChain; @@ -152,6 +158,7 @@ FloweePay::FloweePay() connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { logInfo() << "App no longer active. Start saving data"; + m_sleepStart = QDateTime::currentDateTimeUtc(); saveAll(); p2pNet()->saveData(); saveData(); @@ -167,10 +174,16 @@ FloweePay::FloweePay() * not active, they may all have been removed or timed out already. * * What we'll do is to start the actions again which will check up on our connections - * and create new ones if we need to. + * and create new ones if we need them. */ if (!m_offline && m_loadingCompleted) p2pNet()->start(); + + if (m_appProtection == AppUnlocked + && m_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { + // re-lock the app after 10 minutes of not being in the front. + setAppProtection(AppPassword); + } } }); #endif @@ -237,6 +250,29 @@ FloweePay::FloweePay() } } + // load the AppData file and only fetch the AppProtection tags. + // the rest of the data is used in init() when there is a valid p2pNet loaded. + QFile in(m_basedir + AppdataFilename); + if (in.open(QIODevice::ReadOnly)) { + const auto dataSize = in.size(); + auto &pool = Streaming::pool(dataSize); + in.read(pool.begin(), dataSize); + Streaming::MessageParser parser(pool.commit(dataSize)); + while (parser.next() == Streaming::FoundTag) { + switch (parser.tag()) { + case AppProtectionType: + m_appProtection = static_cast(parser.intData()); + break; + case AppProtectionHash: + m_appProtectionHash = parser.uint256Data(); + break; + default: + break; + } + } + in.close(); + } + // forward signal connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); connect (this, &FloweePay::startSaveData_priv, this, [=]() { @@ -413,7 +449,11 @@ void FloweePay::loadingCompleted() m_prices->start(); } m_loadingCompleted = true; - emit loadComplete(); + if (m_appProtection != AppPassword) { // in that case, wait for password. + assert(!m_loadCompletEmitted); + m_loadCompletEmitted = true; + emit loadComplete(); + } } void FloweePay::saveData() @@ -443,6 +483,16 @@ void FloweePay::saveData() builder.add(WalletSetting_FiatInstaPayLimit, iter.value()); } } + + auto protection = m_appProtection; + if (protection == AppUnlocked) // thats an in-memory only state. + protection = AppPassword; + builder.add(AppProtectionType, protection); + if (protection == AppPassword) { + assert(!m_appProtectionHash.IsNull()); // it would be impossible to unlock if so. + builder.add(AppProtectionHash, m_appProtectionHash); + } + auto buf = builder.buffer(); // hash the new file and check if its different lest we can skip saving @@ -820,6 +870,19 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +FloweePay::ApplicationProtection FloweePay::appProtection() const +{ + return m_appProtection; +} + +void FloweePay::setAppProtection(ApplicationProtection prot) +{ + if (m_appProtection == prot) + return; + m_appProtection = prot; + emit appProtectionChanged(); +} + bool FloweePay::privateMode() const { return m_privateMode; @@ -986,6 +1049,45 @@ QObject *FloweePay::researchAddress(const QString &address, QObject *parent) return info; } +bool FloweePay::checkAppPassword(const QString &password) +{ + if (m_appProtection != AppPassword && m_appProtection != AppUnlocked) + return false; + + // Possible attack vectors here are limited to the hash leaking to the + // world and the users chosen password leaking. + // As we aim for the hash to never leave the device any salting or similar + // is irrelevant. + const auto data = password.toUtf8(); + CSHA256 hasher; + hasher.write(data.constData(), data.size()); + uint256 hash; + hasher.finalize(hash.begin()); + const bool ok = (hash == m_appProtectionHash); + if (ok) { + setAppProtection(AppUnlocked); + if (m_loadingCompleted && !m_loadCompletEmitted) { + m_loadCompletEmitted = true; + emit loadComplete(); + } + } + return ok; +} + +void FloweePay::setAppPassword(const QString &password) +{ + if (password.isEmpty()) { + m_appProtectionHash.SetNull(); + setAppProtection(NoProtection); + return; + } + setAppProtection(AppUnlocked); + const auto data = password.toUtf8(); + CSHA256 hasher; + hasher.write(data.constData(), data.size()); + hasher.finalize(m_appProtectionHash.begin()); +} + NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, const QDateTime &date) { const int height = p2pNet()->blockchain().blockHeightAtTime(date.toSecsSinceEpoch()); diff --git a/src/FloweePay.h b/src/FloweePay.h index 6b60101..b7cee93 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -31,6 +31,7 @@ #include #include #include +#include class Wallet; @@ -47,6 +48,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_OBJECT Q_PROPERTY(QString version READ version CONSTANT) Q_PROPERTY(QString libsVersion READ libsVersion CONSTANT) + Q_PROPERTY(ApplicationProtection appProtection READ appProtection WRITE setAppProtection NOTIFY appProtectionChanged) // p2p net Q_PROPERTY(int headerChainHeight READ headerChainHeight NOTIFY headerChainHeightChanged) Q_PROPERTY(int expectedChainHeight READ expectedChainHeight NOTIFY expectedChainHeightChanged) @@ -75,6 +77,19 @@ public: Bits, Satoshis }; + Q_ENUM(UnitOfBitcoin) + + /** + * The protection the user has selected for his Flowee Pay. + */ + enum ApplicationProtection { + NoProtection, //< No application-wide protection. Notice individual wallets can be protected. + AppPassword, //< A single password for the app. No encryption. + AppUnlocked, //< App-password has been provided. + WalletsPinToPay, //< All wallets are PIN-to-Pay + WalletsPinToOpe //< All wallets are PIN-to-Open + }; + Q_ENUM(ApplicationProtection) FloweePay(); @@ -144,6 +159,13 @@ public: /// Find out about the address and return an instance of AddressInfo if known. Q_INVOKABLE QObject* researchAddress(const QString &address, QObject *parent); + /** + * If property appProtection is set to AppPassword, this method will return true + * only if the provided password is the one set by the user. + */ + Q_INVOKABLE bool checkAppPassword(const QString &password); + Q_INVOKABLE void setAppPassword(const QString &password); + /** * Import a mnemonics based (BIP39) wallet. * Warning; will throw if the mnemonic is invalid @@ -286,6 +308,9 @@ public: bool privateMode() const; void setPrivateMode(bool newPrivateMode); + ApplicationProtection appProtection() const; + void setAppProtection(ApplicationProtection newAppProtection); + signals: void loadComplete(); /// \internal @@ -307,6 +332,8 @@ signals: void totalBalanceConfigChanged(); void privateModeChanged(); + void appProtectionChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -332,8 +359,10 @@ private: mutable Mnemonic m_hdSeedValidator; UnitOfBitcoin m_unit = BCH; - QString m_basedir; + ApplicationProtection m_appProtection = NoProtection; P2PNet::Chain m_chain = P2PNet::MainChain; + uint256 m_appProtectionHash; + QString m_basedir; std::string m_chainPrefix; std::unique_ptr m_downloadManager; std::unique_ptr m_prices; @@ -346,6 +375,8 @@ private: int m_windowHeight = 500; int m_fontScaling = 100; bool m_loadingCompleted = false; // 'init()' completed + bool m_loadCompletEmitted = false; // ensure we only emit this once. + bool m_appUnlocked = false; bool m_darkSkin = true; bool m_createStartWallet = false; bool m_hideBalance = false; @@ -355,6 +386,11 @@ private: bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true +#ifdef TARGET_OS_Android + // when the app is no longer the front app we record the time in order to + // know how much time has passed when we get active again. + QDateTime m_sleepStart; +#endif friend class AccountConfig; }; diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index cf5b335..7f4b31f 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -22,6 +22,7 @@ MenuModel::MenuModel(QObject *parent) { m_data.append({tr("Explore"), "./ExploreModules.qml"}); m_data.append({tr("Settings"), "./GuiSettings.qml"}); + m_data.append({tr("Security"), "./LockApplication.qml"}); m_data.append({tr("Network Details"), "./NetView.qml"}); // TODO move to a module m_data.append({tr("About"), "./About.qml"}); m_data.append({tr("Wallets"), "AccountsList.qml"}); diff --git a/src/main.cpp b/src/main.cpp index 5cb143c..ebd08f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -132,6 +132,7 @@ int main(int argc, char *argv[]) ECC_State crypo_state; // allows the secp256k1 to function. qmlRegisterType("Flowee.org.pay", 1, 0, "TransactionInfo"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); + qmlRegisterUncreatableType("Flowee.org.pay", 1, 0, "FloweePay", ""); QQmlApplicationEngine engine; #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) // quit on error in the QMLs -- 2.54.0 From 0b911143e9869766c2c14688338b20d746e86205 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 20:24:26 +0200 Subject: [PATCH 0630/1428] Remove hack and just use FocusScope items --- guis/mobile/ReceiveTab.qml | 176 ++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index c801704..870d3cd 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -70,6 +70,10 @@ FocusScope { a = !a; qrViewActive = a; editViewActive = !a; + if (a) + feedbackScope.forceActiveFocus(); + else + editScope.forceActiveFocus(); } width: 50 height: 50 @@ -143,109 +147,105 @@ FocusScope { } // feedback-fields - Column { + FocusScope { + id: feedbackScope width: parent.width - spacing: 10 - opacity: qrViewActive ? 1 : 0 - y: instructions.height + qr.height + 30 + height: column.height + Column { + id: column + width: parent.width + spacing: 10 + opacity: qrViewActive ? 1 : 0 + y: instructions.height + qr.height + 30 - PageTitledBox { - width: parent.width - visible: request.message !== "" - title: qsTr("Description") - Flowee.Label { - text: request.message - } - } - PageTitledBox { - width: parent.width - visible: request.amount > 0 - title: qsTr("Amount", "requested amount of coin") - Flowee.BitcoinAmountLabel { - colorize: false - value: request.amount - } - } - PageTitledBox { - width: parent.width - title: qsTr("Address", "Bitcoin Cash address") - Flowee.LabelWithClipboard { + PageTitledBox { width: parent.width - text: request.addressShort - font.pixelSize: instructions.font.pixelSize * 0.9 + visible: request.message !== "" + title: qsTr("Description") + Flowee.Label { + text: request.message + } } - } - Flowee.Button { - anchors.right: parent.right - text: qsTr("Clear") - enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; - onClicked: { - request.clear(); - request.account = portfolio.current; - description.text = ""; - priceInput.paymentBackend.paymentAmount = 0; - request.start(); + PageTitledBox { + width: parent.width + visible: request.amount > 0 + title: qsTr("Amount", "requested amount of coin") + Flowee.BitcoinAmountLabel { + colorize: false + value: request.amount + } } + PageTitledBox { + width: parent.width + title: qsTr("Address", "Bitcoin Cash address") + Flowee.LabelWithClipboard { + width: parent.width + text: request.addressShort + font.pixelSize: instructions.font.pixelSize * 0.9 + } + } + Flowee.Button { + anchors.right: parent.right + text: qsTr("Clear") + enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; + onClicked: { + request.clear(); + request.account = portfolio.current; + description.text = ""; + priceInput.paymentBackend.paymentAmount = 0; + request.start(); + } + } + Behavior on opacity { OpacityAnimator { } } } - Behavior on opacity { OpacityAnimator { } } } // edit fields - PageTitledBox { - id: editBox - width: parent.width - 50 - x: 50 - title: qsTr("Description") - opacity: editViewActive ? 1 : 0 - enabled: editViewActive + FocusScope { + id: editScope + width: parent.width + height: editBox.height + PageTitledBox { + id: editBox + width: parent.width - 50 + x: 50 + title: qsTr("Description") + opacity: editViewActive ? 1 : 0 - // little state-machine to switch between the two - // input options - property bool editingTextField: true + // little state-machine to switch between the two + // input options + property bool editingTextField: true - onEnabledChanged: { - // due to lack of a good statemachine, a bit hackish to - // switch states properly. - if (enabled) { - if (editingTextField) - description.forceActiveFocus() - else - priceInput.forceActiveFocus() + Flowee.TextField { + id: description + width: parent.width + focus: true + onTotalTextChanged: request.message = totalText + onActiveFocusChanged: if (activeFocus) editBox.editingTextField = true } - else { - priceInput.focus = false; - description.focus = false; - } - } - Flowee.TextField { - id: description - width: parent.width - onTotalTextChanged: request.message = totalText - onActiveFocusChanged: if (activeFocus) editBox.editingTextField = true - } + Behavior on opacity { OpacityAnimator { } } - Behavior on opacity { OpacityAnimator { } } + PriceInputWidget { + id: priceInput + fiatFollowsSats: false + width: parent.width + onActiveFocusChanged: if (activeFocus) editBox.editingTextField = false - PriceInputWidget { - id: priceInput - fiatFollowsSats: false - width: parent.width - onActiveFocusChanged: if (activeFocus) editBox.editingTextField = false - - Connections { - target: Fiat - /* - * The price may change simply because the market moved and we re-cheked the server, - * but more important is the case where the user changed which currency they use. - */ - function onPriceChanged() { - // set the value again to trigger an updated exchange-rate calculation to happen. - var price = priceInput.editor.value; - if (priceInput.fiatFollowsSats) - priceInput.paymentBackend.paymentAmount = price; - else - priceInput.paymentBackend.paymentAmountFiat = price; + Connections { + target: Fiat + /* + * The price may change simply because the market moved and we re-cheked the server, + * but more important is the case where the user changed which currency they use. + */ + function onPriceChanged() { + // set the value again to trigger an updated exchange-rate calculation to happen. + var price = priceInput.editor.value; + if (priceInput.fiatFollowsSats) + priceInput.paymentBackend.paymentAmount = price; + else + priceInput.paymentBackend.paymentAmountFiat = price; + } } } } -- 2.54.0 From aaf8ab380f9a8b0d7e51ee46816399cb37a93676 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 20:27:10 +0200 Subject: [PATCH 0631/1428] new release version 7.0 --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 087985a..3777085 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="12" android:versionName="2023.07.0"> diff --git a/src/main.cpp b/src/main.cpp index ebd08f5..6ee00d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.06.1"); + qapp.setApplicationVersion("2023.07.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 85e2b7ebbd8b42715c490d40bf3ffb39adf2e0f9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 20:43:46 +0200 Subject: [PATCH 0632/1428] Fix regression in Qt651, combobox background color. --- guis/Flowee/ComboBox.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/ComboBox.qml b/guis/Flowee/ComboBox.qml index 94f3ddd..d559e75 100644 --- a/guis/Flowee/ComboBox.qml +++ b/guis/Flowee/ComboBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,6 +39,12 @@ QQC2.ComboBox { color: basicButton.highlighted ? root.palette.highlight : root.palette.button } } + background: Rectangle { + color: palette.dark + implicitWidth: 120 + implicitHeight: 40 + radius: 2 + } // Personal preference, the single arrow down makes more sense to me. // We've had that for decades. -- 2.54.0 From dd33a32c9d692d17717c189dc8e2a510f6de6de6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 20:50:39 +0200 Subject: [PATCH 0633/1428] Fix missing property this makes it work again. --- guis/mobile/InstaPayConfigPage.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/InstaPayConfigPage.qml b/guis/mobile/InstaPayConfigPage.qml index 780a1f4..e1c659f 100644 --- a/guis/mobile/InstaPayConfigPage.qml +++ b/guis/mobile/InstaPayConfigPage.qml @@ -101,6 +101,7 @@ Page { anchors.bottomMargin: 15 width: parent.width enabled: mainToggle.checked + dataInput: priceInput } -- 2.54.0 From f68d6a2a1cf80f48aead01adc21b097c530f057f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 21:53:32 +0200 Subject: [PATCH 0634/1428] Add a readme for the modules --- modules/README | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 modules/README diff --git a/modules/README b/modules/README new file mode 100644 index 0000000..e6ae543 --- /dev/null +++ b/modules/README @@ -0,0 +1,62 @@ +Modules are the main way for Flowee Pay to extend the user interface +beyond the basics that all users will want. + +It is currently only supported in the mobile 'skin' due to the simple +fact that the desktop one is unofficially meant for the developers and +its Ok to have all features enabled in there by default. +Additionally it avoids modules needing to support multiple form-factors. + +How to create a new module: + +start by copying the example module. Taking care that the base name of +the new module is defined by the directory. Please use all-lowercase for +your directory name! + +Adjust the CMake file and replace the project and library names by +replacing the 'example' word with your project name. +Notice that this is case sensitive and dashes and underscores are +different characters here. + +Your lib will be [dirname]_module_lib. +So if your dir name is "hello-world" your lib name is "hello-world_module_lib. + + +**Next** you need a ModuleInfo class. Any class ending with that name is Ok, +please use CamelCase for your filename and your class names. + +The class will have a build() method which you need to implement in order +to get your module to be used by Flowee Pay. Exactly one ModuleInfo and +a minimum of one ModuleSection is useful to get anything visual. + +**QML dirs** all QML files are expected to be added in the qrc file because +that will cause them to be included in the finished executable. +Due to the way that cmake works it is required that the qrc file has a +unique name, so please prefix the module name to avoid any conflicts. + +The 'qrc' system is a virtual file system and modules should have their own +unique subdirectory. This subdirectory is defined in your qrc file. +In the QRC file you can find a tag like this, it specifies your qrc subdir. + + +In order to reuse the QML classes from the rest of the project you can +import them like such: + +import "../Flowee" as Flowee +import "../mobile" + +These imports map directly to the guis/mobile and guis/Flowee directories +in the global Flowee Pay project repository. + +Feel free to make some symlinks to help autocomplete in QtCreator, just please +don't commit them. + + +**Buildsystem** the build-system will automatically detect a new module +without any need to alter anything outside of the module directory you are +creating. This is intended to avoid merge conflicts if there are multiple +modules being prepared for inclusion at the same time. + + +Please see the wiki for more details: + +https://codeberg.org/Flowee/pay/wiki/dev/modules -- 2.54.0 From 7f5e4130adc4db637e243caa6acfd716d108eb27 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 21:54:18 +0200 Subject: [PATCH 0635/1428] Move the Netview to a module of its own: Peers-view --- .gitignore | 1 + CMakeLists.txt | 8 ++++ guis/mobile.qrc | 1 - modules/example/ExampleModuleInfo.cpp | 11 ++++- modules/peers-view/CMakeLists.txt | 24 ++++++++++ .../mobile => modules/peers-view}/NetView.qml | 1 + modules/peers-view/PeersViewModuleInfo.cpp | 35 ++++++++++++++ modules/peers-view/PeersViewModuleInfo.h | 31 ++++++++++++ modules/peers-view/peers-page-data.qrc | 5 ++ src/MenuModel.cpp | 48 +++++++++++++++---- src/MenuModel.h | 11 ++++- src/ModuleManager.cpp | 14 ++++++ src/ModuleManager.h | 7 +++ src/ModuleSection.h | 1 + src/main.cpp | 2 +- translations/mobile-i18n.qrc | 4 ++ 16 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 modules/peers-view/CMakeLists.txt rename {guis/mobile => modules/peers-view}/NetView.qml (99%) create mode 100644 modules/peers-view/PeersViewModuleInfo.cpp create mode 100644 modules/peers-view/PeersViewModuleInfo.h create mode 100644 modules/peers-view/peers-page-data.qrc diff --git a/.gitignore b/.gitignore index ff42d23..0192812 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ floweepay-desktop.ts floweepay-mobile.ts module-build-transaction.ts module-example.ts +module-peers-view.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index c9d7d6d..60a050f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,11 @@ if(NOT ANDROID) translations/module-build-transaction_en.ts translations/module-build-transaction_nl.ts + translations/module-build-transaction_pl.ts + + translations/module-peers-view_en.ts + translations/module-peers-view_nl.ts + translations/module-peers-view_pl.ts ) qt6_add_translation(qmFiles ${TS_FILES}) @@ -102,6 +107,9 @@ if(NOT ANDROID) COMMAND lupdate modules/build-transaction/build-transactions-data.qrc modules/build-transaction/BuildTransactionModuleInfo.cpp -ts translations/module-build-transaction.ts + COMMAND lupdate modules/peers-view/peers-page-data.qrc + modules/peers-view/PeersViewModuleInfo.cpp + -ts translations/module-peers-view.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 9e538f4..c9dd4e8 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -49,7 +49,6 @@ mobile/About.qml mobile/MenuOverlay.qml mobile/NewAccount.qml - mobile/NetView.qml mobile/Page.qml mobile/MainViewBase.qml mobile/MainView.qml diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index 1a3d4af..cf17323 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -26,12 +26,21 @@ ModuleInfo * ExampleModuleInfo::build() "and what it can do in Flowee Pay. This text is shown in the module explorer UI.")); info->setIconSource("qrc:/example/example.svg"); + // A section is a definition on where in the application this module is able + // to be plugged-in. auto sendButtonExample = new ModuleSection(ModuleSection::SendMethod, info); sendButtonExample->setText(tr("Example Module")); sendButtonExample->setSubtext(tr("This is some helptext")); - // notice that the directory it is in is registered in the data.qrc header + // notice that the directory it is in, is registered in the example-data.qrc file sendButtonExample->setStartQMLFile("qrc:/example/ExamplePage.qml"); info->addSection(sendButtonExample); + auto menuExample = new ModuleSection(ModuleSection::MainMenuItem, info); + menuExample->setText(tr("Example Module")); + menuExample->setSubtext(tr("This is some helptext")); + // notice that the directory it is in, is registered in the example-data.qrc file + menuExample->setStartQMLFile("qrc:/example/ExamplePage.qml"); + info->addSection(menuExample); + return info; } diff --git a/modules/peers-view/CMakeLists.txt b/modules/peers-view/CMakeLists.txt new file mode 100644 index 0000000..953fdcc --- /dev/null +++ b/modules/peers-view/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2023 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(peers-view) + +set (SOURCES + PeersViewModuleInfo.cpp +) +add_library (peers-view_module_lib STATIC ${SOURCES}) +target_link_libraries(peers-view_module_lib pay_lib) + diff --git a/guis/mobile/NetView.qml b/modules/peers-view/NetView.qml similarity index 99% rename from guis/mobile/NetView.qml rename to modules/peers-view/NetView.qml index 62429e0..b191823 100644 --- a/guis/mobile/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee +import "../mobile"; Page { headerText: qsTr("Peers") diff --git a/modules/peers-view/PeersViewModuleInfo.cpp b/modules/peers-view/PeersViewModuleInfo.cpp new file mode 100644 index 0000000..5b538c1 --- /dev/null +++ b/modules/peers-view/PeersViewModuleInfo.cpp @@ -0,0 +1,35 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "PeersViewModuleInfo.h" + +ModuleInfo * PeersViewModuleInfo::build() +{ + ModuleInfo *info = new ModuleInfo(); + info->setId("peersViewModule"); + info->setTitle(tr("Peers View")); + info->setDescription(tr("This module provides a view of network servers we connect to " + "often called 'peers'.")); + // info->setIconSource("qrc:/example/example.svg"); + + auto menuExample = new ModuleSection(ModuleSection::MainMenuItem, info); + menuExample->setText(tr("Network Details")); + menuExample->setStartQMLFile("qrc:/peers-view/NetView.qml"); + info->addSection(menuExample); + + return info; +} diff --git a/modules/peers-view/PeersViewModuleInfo.h b/modules/peers-view/PeersViewModuleInfo.h new file mode 100644 index 0000000..19291e0 --- /dev/null +++ b/modules/peers-view/PeersViewModuleInfo.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class PeersViewModuleInfo : public QObject +{ + Q_OBJECT +public: + static ModuleInfo *build(); + + static const char *translationUnit() { + return "module-peers-view"; + } +}; diff --git a/modules/peers-view/peers-page-data.qrc b/modules/peers-view/peers-page-data.qrc new file mode 100644 index 0000000..6b47bd1 --- /dev/null +++ b/modules/peers-view/peers-page-data.qrc @@ -0,0 +1,5 @@ + + + NetView.qml + + diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 7f4b31f..9c16c57 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -16,16 +16,24 @@ * along with this program. If not, see . */ #include "MenuModel.h" +#include "ModuleManager.h" +#include "ModuleSection.h" -MenuModel::MenuModel(QObject *parent) - : QAbstractListModel{parent} +MenuModel::MenuModel(ModuleManager *mm) + : QAbstractListModel{mm}, + m_moduleManager(mm) { - m_data.append({tr("Explore"), "./ExploreModules.qml"}); - m_data.append({tr("Settings"), "./GuiSettings.qml"}); - m_data.append({tr("Security"), "./LockApplication.qml"}); - m_data.append({tr("Network Details"), "./NetView.qml"}); // TODO move to a module - m_data.append({tr("About"), "./About.qml"}); - m_data.append({tr("Wallets"), "AccountsList.qml"}); + assert(mm); + m_baseItems.append({tr("Explore"), "./ExploreModules.qml"}); + m_baseItems.append({tr("Settings"), "./GuiSettings.qml"}); + m_baseItems.append({tr("Security"), "./LockApplication.qml"}); + m_baseItems.append({tr("About"), "./About.qml"}); + m_baseItems.append({tr("Wallets"), "AccountsList.qml"}); + initData(); + + connect (m_moduleManager, &ModuleManager::mainMenuSectionsChanged, this, [=]() { + initData(); + }); } int MenuModel::rowCount(const QModelIndex &parent) const @@ -60,3 +68,27 @@ QHash MenuModel::roleNames() const answer[Target] = "target"; return answer; } + +void MenuModel::initData() +{ + if (!m_data.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_data.size() - 1); + m_data.clear(); + endRemoveRows(); + } + m_data.append(m_baseItems.at(0)); // explore + m_data.append(m_baseItems.at(1)); // settings + m_data.append(m_baseItems.at(2)); // security + + // from plugins + for (auto module : m_moduleManager->maindMenuSections()) { + m_data.append( { module->text(), module->startQMLFile() } ); + } + + // and the rest. + for (int i = 3; i < m_baseItems.size(); ++i) + m_data.append(m_baseItems.at(i)); + + beginInsertRows(QModelIndex(), 0, m_data.size() - 1); + endInsertRows(); +} diff --git a/src/MenuModel.h b/src/MenuModel.h index 97afb25..8644337 100644 --- a/src/MenuModel.h +++ b/src/MenuModel.h @@ -19,18 +19,22 @@ #define MENUMODEL_H #include +class ModuleManager; class MenuModel : public QAbstractListModel { Q_OBJECT public: - explicit MenuModel(QObject *parent = nullptr); + explicit MenuModel(ModuleManager *parent); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; + private: + void initData(); + enum Roles { Name, Target @@ -41,7 +45,12 @@ private: QString target; // the QML component to load }; + QList m_baseItems; + + // the view we currently present: QList m_data; + + const ModuleManager * const m_moduleManager; }; #endif diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index f642927..6b8ad9a 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -75,6 +75,8 @@ ModuleManager::ModuleManager(QObject *parent) switch (s->type()) { case ModuleSection::SendMethod: emit sendMenuSectionsChanged(); + case ModuleSection::MainMenuItem: + emit mainMenuSectionsChanged(); default: break; } @@ -227,3 +229,15 @@ QList ModuleManager::sendMenuSections() const } return answer; } + +QList ModuleManager::maindMenuSections() const +{ + QList answer; + for (const auto *m : m_modules) { + for (auto *s : m->sections()) { + if (s->enabled() && s->type() == ModuleSection::MainMenuItem) + answer.append(s); + } + } + return answer; +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h index d86177b..6126d07 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -36,6 +36,11 @@ class ModuleManager : public QObject * \see ModduleSection::SectionType */ Q_PROPERTY(QList sendMenuItems READ sendMenuSections NOTIFY sendMenuSectionsChanged) + /** + * This property holds all the module-sections which are enabled and have the type 'main-menu'. + * \see ModduleSection::SectionType + */ + Q_PROPERTY(QList mainMenuItems READ sendMenuSections NOTIFY mainMenuSectionsChanged) public: explicit ModuleManager(QObject *parent = nullptr); ~ModuleManager(); @@ -47,9 +52,11 @@ public: // lists per type QList sendMenuSections() const; + QList maindMenuSections() const; signals: void sendMenuSectionsChanged(); + void mainMenuSectionsChanged(); private: void load(); diff --git a/src/ModuleSection.h b/src/ModuleSection.h index d72b651..d41325b 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -39,6 +39,7 @@ public: enum SectionType { SendMethod, //< A specific way to send coin, shown in list of send-methods. // BuildTxComponent, //< Inside the 'build-transactions' this adds a new buildingblock. + MainMenuItem, //< A text-button in the main menu }; explicit ModuleSection(SectionType type, QObject *parent = nullptr); diff --git a/src/main.cpp b/src/main.cpp index 6ee00d0..251da8f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) engine.addImageProvider(QLatin1String("qr-raw"), new QRCreator(QRCreator::RawString)); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); - MenuModel menuModel; + MenuModel menuModel(&modules); engine.rootContext()->setContextProperty("MenuModel", &menuModel); engine.rootContext()->setContextProperty("ModuleManager", &modules); diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index b8b9387..cab2b97 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -8,5 +8,9 @@ floweepay-mobile_pl.qm module-build-transaction_en.qm module-build-transaction_nl.qm + module-build-transaction_pl.qm + module-peers-view_en.qm + module-peers-view_nl.qm + module-peers-view_pl.qm -- 2.54.0 From bc2fa0132feb724cfc74b2fac943466cbf8fb24b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 22:22:08 +0200 Subject: [PATCH 0636/1428] Import translations from Crowdin. This includes new Polish translations from Karol! --- translations/floweepay-common_en.ts | 129 +- translations/floweepay-common_nl.ts | 32 +- translations/floweepay-common_pl.ts | 380 +++++- translations/floweepay-desktop_en.ts | 276 +++-- translations/floweepay-desktop_pl.ts | 847 ++++++++------ translations/floweepay-mobile_en.ts | 832 +++++++++---- translations/floweepay-mobile_nl.ts | 696 ++++++----- translations/floweepay-mobile_pl.ts | 1172 ++++++++++++++++++- translations/module-build-transaction_en.ts | 165 +++ translations/module-build-transaction_nl.ts | 19 +- translations/module-build-transaction_pl.ts | 169 +++ translations/module-peers-view_en.ts | 66 ++ translations/module-peers-view_nl.ts | 66 ++ translations/module-peers-view_pl.ts | 66 ++ 14 files changed, 3759 insertions(+), 1156 deletions(-) create mode 100644 translations/module-build-transaction_pl.ts create mode 100644 translations/module-peers-view_en.ts create mode 100644 translations/module-peers-view_nl.ts create mode 100644 translations/module-peers-view_pl.ts diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index 34b8558..06275be 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -4,12 +4,17 @@ AccountInfo - + + Offline + Offline + + + Wallet: Up to date Wallet: Up to date - + Behind: %1 weeks, %2 days counter on weeks @@ -18,7 +23,7 @@ - + Behind: %1 days Behind: %1 day @@ -26,17 +31,17 @@ - + Up to date Up to date - + Updating Updating - + Still %1 hours behind Still an hour behind @@ -62,6 +67,15 @@ This wallet is a simple multiple-address wallet. + + AddressInfoWidget + + + self + payment to self + self + + BroadcastFeedback @@ -85,17 +99,17 @@ Add a personal note - + Copied TXID to clipboard Copied TXID to clipboard - + Opening Website Opening Website - + Close Close @@ -111,30 +125,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -143,13 +157,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -169,25 +183,30 @@ MenuModel - + + Explore + Explore + + + Settings Settings - - Wallet Information - Wallet Information + + Security + Security - - Network Details - Network Details - - - + About About + + + Wallets + Wallets + NotificationManager @@ -237,22 +256,22 @@ Payment - + Invalid PIN Invalid PIN - + Not enough funds selected for fees Not enough funds selected for fees - + Not enough funds in wallet to make payment! Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. @@ -260,7 +279,7 @@ QRWidget - + Copied to clipboard Copied to clipboard @@ -268,11 +287,17 @@ Wallet - - + + Change #%1 Change #%1 + + + + Main #%1 + Main #%1 + WalletCoinsModel @@ -326,27 +351,27 @@ WalletHistoryModel - + Today Today - + Yesterday Yesterday - + Earlier this week Earlier this week - + This week This week - + Earlier this month Earlier this month @@ -354,23 +379,37 @@ WalletSecretsView - - + + Explanation + Explanation + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Coins a / b + a) active coin-count. + b) historical coin-count. + + + + Copy Address Copy Address - + Copy Private Key Copy Private Key - + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signed with Schnorr signatures in the past diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 4488988..a01647e 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -125,30 +125,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -157,13 +157,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -183,27 +183,27 @@ MenuModel - + Explore Ontdek - + Settings Instellingen - - Network Details - Netwerk Details + + Security + Security - + About Over Ons - + Wallets Portemonnees diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 4ad09ba..2be3326 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -1,137 +1,439 @@ + + AccountInfo + + + Offline + Rozłączony + + + + Wallet: Up to date + Portfel: Aktualny + + + + Behind: %1 weeks, %2 days + counter on weeks + + W tyle: %1 tyg., %2 d. + Behind: %1 weeks, %2 days + W tyle: %1 tygodni, %2 dzień + W tyle: %1 tyg., %2 d. + + + + + Behind: %1 days + + W tyle: %1 dzień + W tyle: %1 dni + Behind: %1 days + W tyle: %1 dnia + + + + + Up to date + Na bieżąco + + + + Updating + Aktualizowanie + + + + Still %1 hours behind + + Still %1 hours behind + Nadal %1 godzin w tyle + Nadal %1 godziny w tyle + Nadal %1 godzin w tyle + + + + + AccountTypeLabel + + + This wallet is a single-address wallet. + Ten portfel jest portfelem jednoadresowym. + + + + This wallet is based on a HD seed-phrase + Ten portfel bazuje na ciągu losowych słów (seedzie) + + + + This wallet is a simple multiple-address wallet. + Ten portfel jest prostym portfelem wieloadresowym. + + + + AddressInfoWidget + + + self + payment to self + Ja + + + + BroadcastFeedback + + + Sending Payment + Wysyłanie Płatności + + + + Payment Sent + Płatność Wysłana + + + + Transaction rejected by network + Transakcja odrzucona przez sieć + + + + Add a personal note + Dodaj osobistą notatkę + + + + Copied TXID to clipboard + ID transakcji skopiowane do schowka + + + + Opening Website + Otwieranie Strony + + + + Close + Zamknij + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + Moneta poddana fuzji dla podniesienia anonimowości + + FloweePay - - + + Initial Wallet + Portfel Początkowy + + + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp %1 minutę temu + %1 minut temu %1 minuty temu %1 minut temu - %1 min. temu - + ½ hour ago timestamp - Pół godziny temu + ½ godziny temu - + %1 hours ago timestamp + + %1 hours ago + %1 hours ago + %1 godziy temu + %1 hours ago + + + + + LabelWithClipboard + + + Copy + Skopiuj + + + + MenuModel + + + Explore + Eksploruj + + + + Settings + Ustawienia + + + + Security + Security + + + + About + O programie + + + + Wallets + Portfele + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Blok Bitcoin Cash został wykopany. Wysokość: %1 + + + + tBCH (testnet4) block mined: %1 + Wykopano blok tBCH (testnet4): %1 + + + + Mute + Wycisz + + + + New Transactions + dialog-title + + Nowe transakcje + Nowe Transakcje + Nowych transakcji + New Transactions + + + + + %1 new transactions across %2 wallets found (%3) + %1 nowe transakcje w %2 portfelach (%3) + + + + A payment of %1 has been sent + Wysłano płatność w wysokości %1 + + + + %1 new transactions found (%2) - Godzinę temu - %1 godziny temu - %1 godzin temu - %1 godziny temu + Znaleziono %1 nową transakcję (%2) + Znaleziono %1 nowe transakcje (%2) + Znaleziono %1 nowych transakcji (%2) + Znaleziono %1 nowej transakcji (%2) Payment - - Not enough funds selected for fees - Nie wybrano wystarczającej ilości środków, by pokryć koszt transakcji + + Invalid PIN + Nieprawidłowy PIN - + + Not enough funds selected for fees + Brak środków, by pokryć koszt transakcji + + + Not enough funds in wallet to make payment! Niewystarczająca ilość środków w portfelu, aby dokonać płatności! - + Transaction too large. Amount selected needs too many coins. Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. + + QRWidget + + + Copied to clipboard + Skopiowano do schowka + + Wallet - - + + Change #%1 Reszta #%1 + + + + Main #%1 + Główny #%1 + WalletCoinsModel - + Unconfirmed - Niepotwierdzone + Niepotwierdzona - + %1 hours age, like: hours old - 1 godzina + %1 godzinę %1 godziny %1 godzin - %1 godziny + %1 godzinami - + %1 days age, like: days old - - 1 dzień + + %1 days %1 dni %1 dni - %1 dnia + %1 days - + %1 weeks age, like: weeks old - - 1 tydzień + + %1 tydzień %1 tygodnie %1 tygodni - %1 tygodnia + %1 weeks - + %1 months age, like: months old - - 1 miesiąc + + %1 miesiąc %1 miesiące %1 miesięcy - %1 miesiąca + %1 months - + Change #%1 Reszta #%1 + + WalletHistoryModel + + + Today + Dzisiaj + + + + Yesterday + Wczoraj + + + + Earlier this week + Wcześniej w tym tygodniu + + + + This week + W tym tygodniu + + + + Earlier this month + Wcześniej w tym miesiącu + + + + WalletSecretsView + + + Explanation + Wyjaśnienie + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Monety a / b + a) liczba monet obecnie. + b) liczba monet w przeszłości. + + + + + Copy Address + Skopiuj adres + + + + Copy Private Key + Skopiuj link prywatny + + + + Coins: %1 / %2 + Monety: %1 / %2 + + + + Signed with Schnorr signatures in the past + Podpisywano sygnatury Schborra + + diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 66eb32b..cc68fad 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -59,85 +59,95 @@ Name - + Sync Status Sync Status - + Encryption Encryption - + Password Password - + Open Open - + Invalid PIN Invalid PIN - + + Include balance in total + Include balance in total + + + + Hide in private mode + Hide in private mode + + + Address List Address List - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. - - - + Backup details Backup details - + Seed-phrase Seed-phrase - + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + AccountListItem @@ -333,19 +343,19 @@ Change will come back to the imported key. - Alternate phrase - Alternate phrase - - - Start Height Start Height - + Derivation Derivation + + + Alternate phrase + Alternate phrase + NewAccountPane @@ -452,74 +462,83 @@ Change will come back to the imported key. ReceiveTransactionPane - + Share your QR code or copy address to receive Share your QR code or copy address to receive - + + Encrypted Wallet + Encrypted Wallet + + + + Import Running... + Import Running... + + + Checking Checking - + Transaction high risk Transaction high risk - + Payment Seen Payment Seen - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Description Description - + Amount Amount - - Remember - payment request - Remember - - - + Clear Clear - + Done Done - - - Delete - Delete - SendTransactionPane + + + Confirm delete + Confirm delete + + + + Do you really want to delete this detail? + Do you really want to delete this detail? + Add Destination @@ -530,6 +549,11 @@ Change will come back to the imported key. Prepare Prepare + + + Enter your PIN + Enter your PIN + Transaction Details @@ -540,6 +564,11 @@ Change will come back to the imported key. Not prepared yet Not prepared yet + + + Copy transaction-ID + Copy transaction-ID + Fee @@ -571,31 +600,6 @@ Change will come back to the imported key. Send Send - - - Cancel - Cancel - - - - Copy transaction-ID - Copy transaction-ID - - - - Confirm delete - Confirm delete - - - - Do you really want to delete this detail? - Do you really want to delete this detail? - - - - Enter your PIN - Enter your PIN - Destination @@ -614,53 +618,52 @@ Change will come back to the imported key. %1 to %2 - + Enter Bitcoin Cash Address Enter Bitcoin Cash Address - + Copy Address Copy Address - - self - payment to self - self - - - + Amount Amount - + Max Max - + Warning Warning - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + + Cancel + Cancel + + + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -669,53 +672,53 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin @@ -728,42 +731,57 @@ Change will come back to the imported key. Settings - + Unit Unit - + + Show Bitcoin Cash value on Activity page + Show Bitcoin Cash value on Activity page + + + Show Block Notifications Show Block Notifications - + When a new block is mined, Flowee Pay shows a desktop notification When a new block is mined, Flowee Pay shows a desktop notification - + Night Mode Night Mode - + + Private Mode + Private Mode + + + + Hides private wallets while enabled + Hides private wallets while enabled + + + Version Version - + Library Version Library Version - + Synchronization Synchronization - + Network Status Network Status @@ -1023,95 +1041,95 @@ Change will come back to the imported key. main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - - Offline - Offline - - - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + + Offline + Offline + + + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1120,7 +1138,7 @@ Change will come back to the imported key. - + Preparing... Preparing... diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index 861c5fb..5795a14 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -1,139 +1,166 @@ + + AccountConfigMenu + + + Details + Details + + + + Unarchive + Unarchive + + + + Archive Wallet + Archive Wallet + + + + Make Primary + Make Primary + + + + ★ Primary + ★ Primary + + + + Protect With Pin... + Protect With Pin... + + + + Open + Open encrypted wallet + Open + + + + Close + Close encrypted wallet + Close + + AccountDetails - + Wallet Details Szczegóły portfela - + Name Nazwa - + Sync Status Status synchronizacji - - This wallet is a single-address wallet. - Ten portfel jest portfelem jednoadresowym. + + Encryption + Szyfrowanie - - This wallet is based on a HD seed-phrase - Ten portfel bazuje na ciągu losowych słów (seedzie) + + Password + Hasło - - This wallet is a simple multiple-address wallet. - Ten portfel jest prostym portfelem wieloadresowym. + + Open + Otwórz - + + Invalid PIN + Nieprawidłowy PIN + + + + Include balance in total + Include balance in total + + + + Hide in private mode + Hide in private mode + + + Address List Lista adresów - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - - Coins: %1 / %2 - Monety: %1 / %2 - - - - Signed with Schnorr signatures in the past - Podpisano w przeszłości podpisem Schnorr - - - - Copy Address - Kopiuj adres - - - - Copy Private Key - Kopiuj klucz prywatny - - - + Backup details Szczegóły kopii zapasowej - + Seed-phrase Seed - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. + AccountListItem - + Last Transaction Ostatnia transakcja - - - Details - Szczegóły - - - - Main Wallet - Główny portfel - - - - LabelWithClipboard - - - Copy Address - Kopiuj adres - NetView - + Peers (%1) Peer (%1) @@ -143,48 +170,48 @@ - + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - + initializing connection Inicjowanie połączenia... - + Verifying peer Weryfikowanie peera - + Assigning... Przypisywanie... - + Peer for wallet: %1 Peer dla portfela: %1 - + Peer for initial wallet Peer dla pierwszego portfela - + Close Zamknij @@ -192,32 +219,32 @@ NewAccountCreateBasicAccount - + This creates a new empty wallet with simple multi-address capability Tworzy nowy, pusty portfel jedno- lub wieloadresowy - + Name Nazwa - + Go Dalej - + Advanced Options Opcje zaawansowane - + Force Single Address Wymuś jeden adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Gdy włączone, ten portfel będzie ograniczony do jednego adresu. @@ -227,27 +254,27 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountCreateHDAccount - + This creates a new empty wallet with smart creation of addresses from a single seed-phrase Tworzy nowy, pusty portfel z możliwością kreowania adresów z losowego ciągu wyrazów (seeda) - + Name Nazwa - + Go Dalej - + Advanced Options Opcje zaawansowane - + Derivation Derywacja @@ -255,162 +282,162 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Wprowadź sekrety portfela, który chcesz importować. Może to być seed lub klucz prywatny. - + Secret The seed-phrase or private key Sekret - + Example: %1 placeholder text Przykład: %1 - + Name Nazwa - + Private key description of type Klucz prywatny - + BIP 39 seed-phrase description of type Seed BIP 39 - + Unrecognized word Word from the seed-phrases lexicon Nierozpoznawalne słowo - + Import wallet Importuj portfel - + Advanced Options Opcje zaawansowane - + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - Alternate phrase - Alternatywna fraza - - - + Start Height Wysokość początkowa - + Derivation Derywacja + + + Alternate phrase + Alternatywna fraza + NewAccountPane - + New Bitcoin Cash Wallet Nowy portfel Bitcoin Cash - + Basic Podstawowy - + Private keys based Property of a wallet Bazuje na kluczach prywatnych - + Difficult to backup Context: wallet type Trudna kopia zapasowa - + Great for brief usage Context: wallet type Świetny do krótkotrwałego użytku - + HD wallet Portfel HD - + Seed-phrase based Context: wallet type Bazuje na seedzie - + Easy to backup Context: wallet type Łatwa kopia zapasowa - + Most compatible The most compatible wallet type Najbardziej kompatybilny - + Import Import - + Imports seed-phrase Importuje seeda - + Imports private key Importuje klucz prywatny - + Basic wallet with private keys Podstawowy portfel z kluczami prywatnymi - + Seed-phrase wallet Portfel bazujący na ciągu losowych słów (seedzie) - + Import of an existing wallet Import istniejącego portfela @@ -418,17 +445,17 @@ Change will come back to the imported key. PaymentTweakingPanel - + Add Detail Dodaj szczegóły - + Coin Selector Wybór monet - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. Aby zastąpić domyślny zestaw monet, które zostaną użyte do zapłaty transakcji, użyj 'Wyboru monet', za pomocą którego zobaczysz i wybierzesz monety z tych zawartych w portfelu. @@ -436,214 +463,208 @@ Change will come back to the imported key. ReceiveTransactionPane - + Share your QR code or copy address to receive Udostępnij kod QR lub skopiuj adres, aby otrzymać wpłatę - - Copied to clipboard - Skopiowano do schowka + + Encrypted Wallet + Encrypted Wallet - + + Import Running... + Import Running... + + + Checking Sprawdzanie - + Transaction high risk Transakcja o wysokim poziomie ryzyka - + Payment Seen Wykryto płatność - + Payment Accepted Płatność zaakceptowana - + Payment Settled Płatność rozliczona - + Instant payment failed. Wait for confirmation. (double spent proof received) Płatność błyskawiczna nie powiodła się. Poczekaj na potwierdzenie. (otrzymano dowód podwójnej płatności) - + Description Opis - + Amount Kwota - - Remember - payment request - Zapisz - - - + Clear Wyczyść - + Done Gotowe - - - Delete - Usuń - SendTransactionPane - - Destination - Odbiorca + + Confirm delete + Potwierdź usunięcie - - Max available - The maximum balance available - Maksymalnie dostępne + + Do you really want to delete this detail? + Czy na pewno chcesz usunąć te szczegóły? - - %1 to %2 - summary text to pay X-euro to address M - %1 do %2 - - - - Enter Bitcoin Cash Address - Wprowadź adres Bitcoin Cash - - - - Amount - Kwota - - - - Max - Maks. - - - - Prepare - Przygotuj - - - + Add Destination Dodaj odbiorcę - + + Prepare + Przygotuj + + + + Enter your PIN + Wprowadź PIN + + + Transaction Details Szczegóły transakcji - + Not prepared yet Jeszcze nie przygotowano - + + Copy transaction-ID + Kopiuj ID transakcji + + + Fee Opłata - + Transaction size Rozmiar transakcji - + %1 bytes %1 B - + Fee per byte Opłata za bajt - + %1 sat/byte fee %1 sat/byte - + Send Wyślij - + + Destination + Odbiorca + + + + Max available + The maximum balance available + Maksymalnie dostępne + + + + %1 to %2 + summary text to pay X-euro to address M + %1 do %2 + + + + Enter Bitcoin Cash Address + Wprowadź adres Bitcoin Cash + + + + Copy Address + Copy Address + + + + Amount + Kwota + + + + Max + Maks. + + + + Warning + Uwaga! + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? + + + + Continue + Kontynuuj + + + Cancel Anuluj - - Transaction rejected by network - Transakcja odrzucona przez sieć - - - - Payment Sent - Płatność wysłana - - - - Copy transaction-ID - Kopiuj ID transakcji - - - - Your payment can be found by its identifyer: %1 - Twoja płatność może zostać znaleziona za pomocą identyfikatora: %1 - - - - Copy - Kopiuj - - - - Internet - Internet - - - - Comment - Komentarz - - - - Close - Zamknij - - - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -654,173 +675,298 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę - - - Coin has been fused for increased anonymity - Monety zostały przemieszane w celu zwiększenia anonimowości - SettingsPane - + Settings Ustawienia - - Night mode - Tryb nocny - - - + Unit Jednostka - + + Show Bitcoin Cash value on Activity page + Show Bitcoin Cash value on Activity page + + + + Show Block Notifications + Pokaż powiadomienia o blokach + + + + When a new block is mined, Flowee Pay shows a desktop notification + Gdy nowy blok zostanie wykopany, Flowee Pay pokazuje powiadomienia na pulpicie + + + + Night Mode + Tryb nocny + + + + Private Mode + Private Mode + + + + Hides private wallets while enabled + Hides private wallets while enabled + + + Version Wersja - + Library Version Wersja biblioteki - + + Synchronization + Synchronization + + + Network Status Status sieci - SyncIndicator + WalletEncryption - - - Up to date - Zsynchronizowany - - - - %1 weeks behind - - Pozostał %1 tydzień - Pozostały %1 tygodnie - Pozostało %1 tygodni - Pozostało %1 tygodni - - - - - %1 days behind - - Pozostał %1 dzień - Pozostały %1 dni - Pozostało %1 dni - Pozostało %1 dni - + + Protect your wallet with a password + Zabezpiecz swój portfel hasłem - - Updating - Aktualizowanie + + Pin to Pay + PIN by płacić - - - %1 hours behind - - Pozostała %1 godzina - Pozostały %1 godziny - Pozostało %1 godzin - Pozostało %1 godzin - + + + Protect your funds + pin to pay + Zabezpiecz swoje środki + + + + Fully open, except for sending funds + pin to pay + W pełni otwarty, z wyjątkiem funkcji wysyłania środków + + + + Keeps in sync + pin to pay + Utrzymuje synchronizację + + + + Pin to Open + PIN by otworzyć + + + + Protect your entire wallet + pin to open + Zabezpiecz cały portfel + + + + Balance and history protected + pin to open + Zabezpieczone saldo i historia + + + + Requires Pin to view, sync or pay + pin to open + Wymaga pinu do wyświetlenia, synchronizacji lub zapłaty + + + + Make "%1" wallet require a pin to pay + Portfel "%1" będzie wymagał podania PINu by płacić + + + + Make "%1" wallet require a pin to open + Portfel "%1" będzie wymagał podania PINu by go otworzyć + + + + Wallet already has pin to pay applied + Ten portfel ma już opcję "PIN by płacić" + + + + + Wallet already has pin to open applied + Ten portfel ma już opcję "PIN by otworzyć" + + + + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Twój portfel zostanie częściowo zaszyfrowany, a płatności staną się niemożliwe bez hasła. Jeśli nie masz kopii zapasowej tego portfela, zrób ją najpierw. + + + + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Cały Twój portfel zostanie zaszyfrowany, otwarcie go będzie wymagało hasła. Jeśli nie masz kopii zapasowej tego portfelu, najpierw ją wykonaj. + + + + Password + Hasło + + + + Wallet + Portfel + + + + Encrypt + Zaszyfruj + + + + Invalid password to open this wallet + Nieprawidłowe hasło do otwarcia tego portfela + + + + Close + Zamknij + + + + Repeat password + Powtórz hasło + + + + Please confirm the password by entering it again + Proszę potwierdzić hasło wpisując je ponownie + + + + Typed passwords do not match + Podane hasła różnią się + + + + WalletEncryptionStatus + + + Pin to Pay + PIN by płacić + + + + Pin to Open + PIN by otworzyć + + + + (Opened) + Wallet is decrypted + (otwarty) WalletTransaction - + Miner Reward Nagroda dla górnika - + Cash Fusion Cash Fusion - + Received Otrzymane - + Moved Przeniesione - + Sent Wysłane - + rejected odrzucono - + unconfirmed niepotwierdzone @@ -828,27 +974,27 @@ Change will come back to the imported key. WalletTransactionDetails - + Copy transaction-ID Kopiuj ID transakcji - + Status Status - + rejected odrzucono - + unconfirmed niepotwierdzone - + %1 confirmations (mined in block %2) %1 potwierdzenie (wykopane w bloku %2) @@ -858,17 +1004,22 @@ Change will come back to the imported key. - + + Copy block height + Kopiuj wysokość bloku + + + Fees Koszt - + Size Rozmiar - + %1 bytes %1 bajt @@ -878,18 +1029,18 @@ Change will come back to the imported key. - + Inputs Wejścia - - + + Copy Address Kopiuj adres - + Outputs Wyjścia @@ -897,70 +1048,106 @@ Change will come back to the imported key. main - + Activity Aktywność - + + Archived wallets do not check for activities. Balance may be out of date. + Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. + + + + Unarchive + Przywróć + + + + This wallet needs a password to open. + Ten portfel wymaga hasła do otwarcia. + + + + Password: + Hasło: + + + + Invalid password + Nieprawidłowe hasło + + + + Open + Otwórz + + + Send Wyślij - + Receive Odbierz - - Total balance - Wszystkie środki - - - + Balance Saldo - - Show Wallet Details - Pokaż szczegóły portfela - - - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + + Offline + Offline + + + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash + + + Archived wallets [%1] + Arg is wallet count + + Zarchiwizowany portfel [%1] + Zarchiwizowane portfele [%1] + Zarchiwizowane portfele [%1] + Zarchiwizowane portfele [%1] + + - + Preparing... Przygotowuję… diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index ceded87..5ea8027 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -4,34 +4,43 @@ About + About About + Help translate this app Help translate this app + License License + + Credits Credits + © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander and contributors + Project Home Project Home + With git repository and issues tracker With git repository and issues tracker + Telegram Telegram @@ -39,38 +48,57 @@ AccountHistory + Home Home + + Pay + Pay + + + + Receive + Receive + + + Miner Reward Miner Reward + Cash Fusion Cash Fusion + Received Received + Moved Moved + Sent Sent + Sending Sending + Seen Seen + Rejected Rejected @@ -78,86 +106,145 @@ AccountPageListItem - Sync Status - Sync Status + + Name + Name - Primary Wallet - Primary Wallet + + Archived wallets do not check for activities. Balance may be out of date + Archived wallets do not check for activities. Balance may be out of date + Backup information Backup information + Backup Details Backup Details + Wallet seed-phrase Wallet seed-phrase - Derivation Path - Derivation Path - - + Starting Height height refers to block-height Starting Height + + Derivation Path + Derivation Path + + + + xpub + xpub + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! + Wallet keys Wallet keys + Show Index toggle to show numbers Show Index + Change Addresses Change Addresses + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Used Addresses Used Addresses - Addresses and Keys - Addresses and Keys - - + Switches between still in use addresses and formerly used, new empty, addresses Switches between still in use addresses and formerly used, new empty, addresses + + + Addresses and keys + Addresses and keys + + + + Sync Status + Sync Status + + + + Hide balance in overviews + Hide balance in overviews + + + + Hide in private mode + Hide in private mode + + + + Unarchive Wallet + Unarchive Wallet + + + + Archive Wallet + Archive Wallet + AccountSelectorPopup + Your Wallets Your Wallets + last active last active + + + Needs PIN to open + Needs PIN to open + + + + Balance Total + Balance Total + AccountSyncState + Status: Offline Status: Offline @@ -165,40 +252,102 @@ AccountsList - Wallet Information - Wallet Information + + Wallet + Wallet + + + + Wallets + Wallets + + + + Add Wallet + Add Wallet + + + + Default Wallet + Default Wallet + + + + %1 is used on startup + %1 is used on startup + + + + Exit Private Mode + Exit Private Mode + + + + Enter Private Mode + Enter Private Mode + + + + Reveals wallets marked private + Reveals wallets marked private + + + + Hides wallets marked private + Hides wallets marked private CurrencySelector + Select Currency Select Currency + + ExploreModules + + + Explore + Explore + + + + ON + Enabled. SHORT TEXT! + ON + + GuiSettings + Display Settings Display Settings + Font sizing Font sizing + Unit Unit + Change Currency (%1) Change Currency (%1) + Main View Main View + Show Bitcoin Cash value Show Bitcoin Cash value @@ -206,361 +355,430 @@ ImportWalletPage + Import Wallet Import Wallet - Create - Create - - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Secret The seed-phrase or private key Secret + Private key description of type Private key + BIP 39 seed-phrase description of type BIP 39 seed-phrase + Unrecognized word Word from the seed-phrases lexicon Unrecognized word + Name Name + Force Single Address Force Single Address + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - Alternate phrase - Alternate phrase - - + Oldest Transaction Oldest Transaction + Derivation Derivation + + + Alternate phrase + Alternate phrase + + + + Create + Create + + + + InstaPayConfigButton + + + Enable Instant Pay + Enable Instant Pay + + + + Configure Instant Pay + Configure Instant Pay + + + + Fast payments for low amounts + Fast payments for low amounts + + + + Not configured + Not configured + + + + Limit set to: %1 + Limit set to: %1 + + + + InstaPayConfigPage + + + Instant Pay + Instant Pay + + + + Requests for payment can be approved automatically using Instant Pay. + Requests for payment can be approved automatically using Instant Pay. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Protected wallets can not be used for InstaPay because they require a PIN before usage + + + + Enable Instant Pay for this wallet + Enable Instant Pay for this wallet + + + + Maximum Amount + Maximum Amount + + + + LockApplication + + + Security + Security + + + + PIN on startup + PIN on startup + + + + Enter current PIN + Enter current PIN + + + + Enter new PIN + Enter new PIN + + + + Repeat PIN + Repeat PIN + + + + Remove PIN + Remove PIN + + + + Set PIN + Set PIN + + + + PIN has been removed + PIN has been removed + + + + PIN has been set + PIN has been set + + + + Encrypt your wallet data + Encrypt your wallet data + + + + Require PIN to Pay on wallet + Require PIN to Pay on wallet + + + + Secure against spending + Secure against spending + + + + Require PIN to Open wallet + Require PIN to Open wallet + + + + Secure against all usage + Secure against all usage + + + + Ok + Ok + MenuOverlay + Add Wallet Add Wallet - - NetView - - Peers - Peers - - - Address - network address (IP) - Address - - - Start-height: %1 - Start-height: %1 - - - ban-score: %1 - ban-score: %1 - - - initializing connection - initializing connection - - - Verifying peer - Verifying peer - - - Peer for wallet: %1 - Peer for wallet: %1 - - - Peer for wallet - Peer for wallet - - NewAccount + New Bitcoin Cash Wallet New Bitcoin Cash Wallet - Next - Next - - + Create a New Wallet Create a New Wallet - Basic - Basic - - - Private keys based - Property of a wallet - Private keys based - - - Difficult to backup - Context: wallet type - Difficult to backup - - - Great for brief usage - Context: wallet type - Great for brief usage - - + HD wallet HD wallet + Seed-phrase based Context: wallet type Seed-phrase based + Easy to backup Context: wallet type Easy to backup + Most compatible The most compatible wallet type Most compatible + + Basic + Basic + + + + Private keys based + Property of a wallet + Private keys based + + + + Difficult to backup + Context: wallet type + Difficult to backup + + + + Great for brief usage + Context: wallet type + Great for brief usage + + + Import Existing Wallet Import Existing Wallet + Import Import + Imports seed-phrase Imports seed-phrase + Imports private key Imports private key + New Wallet New Wallet - Create - Create - - + This creates a new empty wallet with simple multi-address capability This creates a new empty wallet with simple multi-address capability + + Name Name + Force Single Address Force Single Address + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up - Derivation - Derivation + + + Create + Create + New HD-Wallet New HD-Wallet - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - - - - PayToOthers - - Build Transaction - Build Transaction + + This creates a new wallet that can be backed up with a seed-phrase + This creates a new wallet that can be backed up with a seed-phrase - Building Error - error during build - Building Error - - - Add Payment Detail - page title - Add Payment Detail - - - Add Destination - Add Destination - - - an address to send money to - an address to send money to - - - Confirm Sending - confirm we want to send the transaction - Confirm Sending - - - TXID - TXID - - - Copy transaction-ID - Copy transaction-ID - - - Fee - Fee - - - Transaction size - Transaction size - - - %1 bytes - %1 bytes - - - Fee per byte - Fee per byte - - - %1 sat/byte - fee - %1 sat/byte - - - Destination - Destination - - - unset - indication of empty - unset - - - Copy Address - Copy Address - - - Amount - Amount - - - Edit Destination - Edit Destination - - - Send All - all money in wallet - Send All - - - Bitcoin Cash Address - Bitcoin Cash Address - - - Warning - Warning - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - - - I am certain - I am certain - - - Drag to Edit - Drag to Edit - - - Drag to Delete - Drag to Delete - - - Prepare Payment... - Prepare Payment... + + Derivation + Derivation PayWithQR + Approve Payment Approve Payment + Send All all money in wallet Send All + + Show Address + to show a bitcoincash address + Show Address + + + + Edit Amount + Edit amount of money to send + Edit Amount + + + + Invalid QR code + Invalid QR code + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + I don't understand the scanned code. I'm sorry, I can't start a payment. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Scanned text: <pre>%1</pre> + + + Payment description Payment description + + + Destination Address + Destination Address + + + + Unlock Wallet + Unlock Wallet + PriceDetails + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 + 7d 7 days 7d + 1m 1 month 1m + 3m 3 months 3m @@ -569,91 +787,156 @@ This ensures only one private key will need to be backed up PriceInputWidget + All Currencies All Currencies + + QRScannerOverlay + + + Paste + Paste + + + + Failed + Failed + + ReceiveTab + Receive Receive + Share this QR to receive Share this QR to receive + Encrypted Wallet Encrypted Wallet + Import Running... Import Running... + + Description Description + Amount + requested amount of coin Amount + + Address + Bitcoin Cash address + Address + + + Clear Clear + + Payment Seen Payment Seen + Checking... Checking... + Transaction high risk Transaction high risk + Partially Paid Partially Paid + Payment Accepted Payment Accepted + Payment Settled Payment Settled + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) + Continue Continue + + SelectDefaultAccountPage + + + Select Wallet + Select Wallet + + + + Pick which wallet will be selected on starting Flowee Pay + Pick which wallet will be selected on starting Flowee Pay + + + + No InstaPay limit set + No InstaPay limit set + + + + InstaPay till %1 + InstaPay till %1 + + + + InstaPay is turned off + InstaPay is turned off + + SendTransactionsTab + Send Send - Scan a QR code - Scan a QR code - - - Build transaction - Build transaction + + Start Payment + Start Payment SlideToApprove + SLIDE TO SEND SLIDE TO SEND @@ -661,80 +944,157 @@ This ensures only one private key will need to be backed up StartupScreen + Welcome! Welcome! + Continue Continue + Moving the world towards a Bitcoin Cash economy Moving the world towards a Bitcoin Cash economy - I already have a wallet - I already have a wallet + + Scan me to send funds to your HD wallet + Scan me to send funds to your HD wallet + OR OR - I want to send funds to my new wallet - I want to send funds to my new wallet + + Add a different wallet + Add a different wallet TransactionDetails + Transaction Details Transaction Details + + Transaction Hash + Transaction Hash + + + Rejected Rejected + Unconfirmed Unconfirmed - Mined - Mined + + Mined at + Mined at + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + + + Transaction comment + Transaction comment + + + + Size: %1 bytes + Size: %1 bytes + + + Coinbase Coinbase - CashFusion transaction - CashFusion transaction + + Is a CashFusion transaction. + Is a CashFusion transaction. - Size - Size + + Fees paid + Fees paid - %1 bytes - %1 bytes + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Fused from my addresses + + + + Sent from my addresses + Sent from my addresses + + + + Sent from addresses + Sent from addresses + + + + + Copy Address + Copy Address + + + + Fused into my addresses + Fused into my addresses + + + + Received at addresses + Received at addresses + + + + Received at my addresses + Received at my addresses TxInfoSmall + Transaction is rejected Transaction is rejected + Processing Processing + Mined Mined + %1 blocks ago Confirmations @@ -743,36 +1103,68 @@ This ensures only one private key will need to be backed up + Miner Reward Miner Reward + Cash Fusion Cash Fusion + Received Received + Payment to self Payment to self + Sent Sent - Value then - Value then + + Holds a token + Holds a token + + Sent to + Sent to + + + Value now Value now + + Value then + Value then + + + Transaction Details Transaction Details + + UnlockWidget + + + Enter your wallet passcode + Enter your wallet passcode + + + + Open + open wallet with PIN + Open + + diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 244f665..a4c6351 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -53,42 +53,52 @@ Start - + + Pay + Betaal + + + + Receive + Ontvang + + + Miner Reward Mijnwerker Beloning - + Cash Fusion Cash Fusion - + Received Ontvangen - + Moved Zelf-betaling - + Sent Verzonden - + Sending Verzenden - + Seen Gezien - + Rejected Geweigerd @@ -96,9 +106,14 @@ AccountPageListItem - - Sync Status - Synchronisatie status + + Name + Naam + + + + Archived wallets do not check for activities. Balance may be out of date + Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd @@ -111,94 +126,94 @@ Back-up gegevens - + Wallet seed-phrase Herstelzin opslaan - - Derivation Path - Derivatie pad - - - + Starting Height height refers to block-height Beginhoogte - - Name - Naam + + Derivation Path + Derivatie pad - - Archived wallets do not check for activities. Balance may be out of date - Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd - - - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between still in use addresses and formerly used, new empty, addresses Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen - + Addresses and keys Adressen en sleutels - + + Sync Status + Synchronisatie status + + + + Hide balance in overviews + Hide balance in overviews + + + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren @@ -206,15 +221,25 @@ AccountSelectorPopup - + Your Wallets Uw portemonnees - + last active Laatst actief + + + Needs PIN to open + Needs PIN to open + + + + Balance Total + Balance Total + AccountSyncState @@ -280,6 +305,20 @@ Selecteer valuta + + ExploreModules + + + Explore + Ontdek + + + + ON + Enabled. SHORT TEXT! + AAN + + GuiSettings @@ -321,71 +360,71 @@ Portemonnee importeren - - Create - Creëer - - - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - + Secret The seed-phrase or private key Geheim - + Private key description of type Privé-sleutel - + BIP 39 seed-phrase description of type BIP 39 herstelzin - + Unrecognized word Word from the seed-phrases lexicon Niet herkend woord - + Name Naam - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - Alternate phrase - Alternatieve zin - - - + Oldest Transaction Oudste transactie - + Derivation Derivatie + + + Alternate phrase + Alternatieve zin + + + + Create + Creëer + InstaPayConfigButton @@ -443,6 +482,84 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Maximum Bedrag + + LockApplication + + + Security + Security + + + + PIN on startup + PIN on startup + + + + Enter current PIN + Enter current PIN + + + + Enter new PIN + Enter new PIN + + + + Repeat PIN + Repeat PIN + + + + Remove PIN + Remove PIN + + + + Set PIN + Set PIN + + + + PIN has been removed + PIN has been removed + + + + PIN has been set + PIN has been set + + + + Encrypt your wallet data + Encrypt your wallet data + + + + Require PIN to Pay on wallet + Require PIN to Pay on wallet + + + + Secure against spending + Secure against spending + + + + Require PIN to Open wallet + Require PIN to Open wallet + + + + Secure against all usage + Secure against all usage + + + + Ok + Ok + + MenuOverlay @@ -451,50 +568,6 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Portemonnee toevoegen - - NetView - - - Peers - Peers - - - - Address - network address (IP) - Adres - - - - Start-height: %1 - Beginhoogte: %1 - - - - ban-score: %1 - ban-score: %1 - - - - initializing connection - verbinding initialiseren - - - - Verifying peer - Peer verifiëren - - - - Peer for wallet: %1 - Peer voor portemonnee: %1 - - - - Peer for wallet - Peer voor portemonnee - - NewAccount @@ -508,262 +581,120 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Maak een nieuwe portemonnee - - Basic - Eenvoudig - - - - Private keys based - Property of a wallet - Privé sleutels gebaseerd - - - - Difficult to backup - Context: wallet type - Back-up moeilijk - - - - Great for brief usage - Context: wallet type - Ideaal voor kort gebruik - - - + HD wallet HD portemonnee - + Seed-phrase based Context: wallet type Herstelzin gebaseerd - + Easy to backup Context: wallet type Back-upt makkelijk - + Most compatible The most compatible wallet type Meest compatibel - + + Basic + Eenvoudig + + + + Private keys based + Property of a wallet + Privé sleutels gebaseerd + + + + Difficult to backup + Context: wallet type + Back-up moeilijk + + + + Great for brief usage + Context: wallet type + Ideaal voor kort gebruik + + + Import Existing Wallet Importeer bestaande portemonnee - + Import Importeer - + Imports seed-phrase Importeert herstelzin - + Imports private key Importeert Privésleutel - + New Wallet Nieuwe Portemonnee - - - Create - Creëer - - - + This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden - - + + Name Naam - + Force Single Address Forceer één adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + + + Create + Creëer + + + + New HD-Wallet + Nieuwe Portemonnee + + + This creates a new wallet that can be backed up with a seed-phrase Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin - + Derivation Derivatie - - - New HD-Wallet - Nieuwe Portemonnee - - - - PayToOthers - - - Build Transaction - Bouw Transactie - - - - Building Error - error during build - Fout bij bouwen - - - - Add Payment Detail - page title - Voeg betalingsgegevens toe - - - - - Add Destination - Voeg bestemming toe - - - - an address to send money to - een adres om geld naar te sturen - - - - Confirm Sending - confirm we want to send the transaction - Verzenden bevestigen - - - - TXID - TXID - - - - Copy transaction-ID - Transactie-ID kopiëren - - - - Fee - Transactiekosten - - - - Transaction size - Transactie grootte - - - - %1 bytes - %1 bytes - - - - Fee per byte - Transactiekosten per byte - - - - %1 sat/byte - fee - %1 sats/byte - - - - Destination - Bestemming - - - - unset - indication of empty - niet ingesteld - - - - invalid - address is not correct - ongeldig - - - - - Copy Address - Kopieer adres - - - - Edit Destination - Bewerk Bestemming - - - - Send All - all money in wallet - Alles verzenden - - - - Bitcoin Cash Address - Bitcoin Cash-adres - - - - Warning - Waarschuwing - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - - - - I am certain - Ik ben zeker - - - - Drag to Edit - Sleep om te bewerken - - - - Drag to Delete - Sleep om te verwijderen - - - - Prepare Payment... - Betaling voorbereiden... - PayWithQR @@ -791,35 +722,40 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres + + + Unlock Wallet + Unlock Wallet + PriceDetails @@ -851,11 +787,24 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PriceInputWidget - + All Currencies Alle valuta's + + QRScannerOverlay + + + Paste + Plak + + + + Failed + Mislukt! + + ReceiveTab @@ -864,84 +813,84 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Ontvangen - + Share this QR to receive Betaler moet deze QR lezen - - Transaction high risk - Transactie met hoog risico - - - - - Payment Seen - Betaling gezien - - - + Encrypted Wallet Versleutelde portemonnee - + Import Running... Bezig met importeren... - + + + Description + Omschrijving + + + Amount requested amount of coin Bedrag - + Address Bitcoin Cash address Adres - + + Clear + Wissen + + + + + Payment Seen + Betaling gezien + + + Checking... Controleren... - + + Transaction high risk + Transactie met hoog risico + + + Partially Paid Deels betaald - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - + Continue Doorgaan - - - - Description - Omschrijving - - - - Clear - Wissen - SelectDefaultAccountPage @@ -980,13 +929,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Scan a QR code - Scan QR-code - - - - Build transaction - Bouw transactie + Start Payment + Betaling starten @@ -1019,16 +963,16 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Scan me to send funds to your HD wallet Scan mij om geld naar uw HD-portemonnee te sturen - - - Add a different wallet - Een andere portemonnee toevoegen - OR OF + + + Add a different wallet + Een andere portemonnee toevoegen + TransactionDetails @@ -1075,6 +1019,11 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Size: %1 bytes Grootte: %1 bytes + + + Coinbase + Coinbase + Is a CashFusion transaction. @@ -1126,11 +1075,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Received at my addresses Ontvangen op mijn adressen - - - Coinbase - Coinbase - TxInfoSmall @@ -1173,26 +1117,16 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Received Ontvangen - - - Sent - Verzonden - - - - Value then - Waarde toen - - - - Value now - Waarde nu - Payment to self Betaling aan uzelf + + + Sent + Verzonden + Holds a token @@ -1203,10 +1137,34 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Sent to Verstuurd naar + + + Value now + Waarde nu + + + + Value then + Waarde toen + Transaction Details Transactiedetails + + UnlockWidget + + + Enter your wallet passcode + Enter your wallet passcode + + + + Open + open wallet with PIN + Open + + diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index fb60fbb..f66078b 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -1,4 +1,1174 @@ - + + + About + + + About + O programie + + + + Help translate this app + Pomóż w tłumaczeniu tej aplikacji + + + + License + Licencja + + + + + Credits + Autorzy + + + + © 2020-2023 Tom Zander and contributors + ©️ 2020-2023 Tom Zander i współtwórcy + + + + Project Home + Strona projektu + + + + With git repository and issues tracker + Z repozytorium git i trackerem problemów + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Strona główna + + + + Pay + Zapłać + + + + Receive + Otrzymaj + + + + Miner Reward + Nagroda dla górnika + + + + Cash Fusion + Cash Fusion + + + + Received + Otrzymane + + + + Moved + Przeniesione + + + + Sent + Wysłane + + + + Sending + Wysyłam + + + + Seen + Seen + + + + Rejected + Odrzucona + + + + AccountPageListItem + + + Name + Nazwa + + + + Archived wallets do not check for activities. Balance may be out of date + Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne + + + + Backup information + Kopia zapasowa informacji + + + + Backup Details + Szczegóły kopii zapasowej + + + + Wallet seed-phrase + Fraza seedowa portfela + + + + Starting Height + height refers to block-height + Wysokość przy utworzeniu + + + + Derivation Path + Ścieżka derywacji + + + + xpub + xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! + + + + Wallet keys + Klucze portfela + + + + Show Index + toggle to show numbers + Pokaż indeks + + + + Change Addresses + Adres Reszty + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. + + + + Used Addresses + Wykorzystane adresy + + + + Switches between still in use addresses and formerly used, new empty, addresses + Przełącza się pomiędzy nadal używanymi adresami i wcześniej używanymi, nowymi pustymi adresami + + + + Addresses and keys + Adresy i klucze + + + + Sync Status + Status synchronizacji + + + + Hide balance in overviews + Hide balance in overviews + + + + Hide in private mode + Ukryj w trybie prywatnym + + + + Unarchive Wallet + Anuluj archiwizację portfela + + + + Archive Wallet + Zarchiwizuj portfel + + + + AccountSelectorPopup + + + Your Wallets + Twoje portfele + + + + last active + Ostatnio aktywny + + + + Needs PIN to open + Needs PIN to open + + + + Balance Total + Balance Total + + + + AccountSyncState + + + Status: Offline + Status: Offline + + + + AccountsList + + + Wallet + Portfel + + + + Wallets + Portfele + + + + Add Wallet + Dodaj Portfel + + + + Default Wallet + Domyślny Portfel + + + + %1 is used on startup + %1 jest używany przy starcie + + + + Exit Private Mode + Wyjdź z trybu edycji + + + + Enter Private Mode + Włącz Tryb Prywatny + + + + Reveals wallets marked private + Reveals wallets marked private + + + + Hides wallets marked private + Hides wallets marked private + + + + CurrencySelector + + + Select Currency + Select Currency + + + + ExploreModules + + + Explore + Explore + + + + ON + Enabled. SHORT TEXT! + ON + + + + GuiSettings + + + Display Settings + Display Settings + + + + Font sizing + Font sizing + + + + Unit + Unit + + + + Change Currency (%1) + Change Currency (%1) + + + + Main View + Main View + + + + Show Bitcoin Cash value + Show Bitcoin Cash value + + + + ImportWalletPage + + + Import Wallet + Import Wallet + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + + + + Secret + The seed-phrase or private key + Secret + + + + Private key + description of type + Private key + + + + BIP 39 seed-phrase + description of type + BIP 39 seed-phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unrecognized word + + + + Name + Name + + + + Force Single Address + Force Single Address + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + + + + Oldest Transaction + Oldest Transaction + + + + Derivation + Derivation + + + + Alternate phrase + Alternate phrase + + + + Create + Create + + + + InstaPayConfigButton + + + Enable Instant Pay + Enable Instant Pay + + + + Configure Instant Pay + Configure Instant Pay + + + + Fast payments for low amounts + Fast payments for low amounts + + + + Not configured + Not configured + + + + Limit set to: %1 + Limit set to: %1 + + + + InstaPayConfigPage + + + Instant Pay + Instant Pay + + + + Requests for payment can be approved automatically using Instant Pay. + Requests for payment can be approved automatically using Instant Pay. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Protected wallets can not be used for InstaPay because they require a PIN before usage + + + + Enable Instant Pay for this wallet + Enable Instant Pay for this wallet + + + + Maximum Amount + Maximum Amount + + + + LockApplication + + + Security + Security + + + + PIN on startup + PIN on startup + + + + Enter current PIN + Enter current PIN + + + + Enter new PIN + Enter new PIN + + + + Repeat PIN + Repeat PIN + + + + Remove PIN + Remove PIN + + + + Set PIN + Set PIN + + + + PIN has been removed + PIN has been removed + + + + PIN has been set + PIN has been set + + + + Encrypt your wallet data + Encrypt your wallet data + + + + Require PIN to Pay on wallet + Require PIN to Pay on wallet + + + + Secure against spending + Secure against spending + + + + Require PIN to Open wallet + Require PIN to Open wallet + + + + Secure against all usage + Secure against all usage + + + + Ok + Ok + + + + MenuOverlay + + + Add Wallet + Add Wallet + + + + NewAccount + + + New Bitcoin Cash Wallet + New Bitcoin Cash Wallet + + + + Create a New Wallet + Create a New Wallet + + + + HD wallet + Portfel HD + + + + Seed-phrase based + Context: wallet type + Portfel bazujący na ciągu losowych słów (seedzie) + + + + Easy to backup + Context: wallet type + Easy to backup + + + + Most compatible + The most compatible wallet type + Najbardziej kompatybilny + + + + Basic + Basic + + + + Private keys based + Property of a wallet + Private keys based + + + + Difficult to backup + Context: wallet type + Difficult to backup + + + + Great for brief usage + Context: wallet type + Great for brief usage + + + + Import Existing Wallet + Importuj istniejący portfel + + + + Import + Importuj + + + + Imports seed-phrase + Importuje seeda + + + + Imports private key + Importuj klucz prywatny + + + + New Wallet + Nowy Portfel + + + + This creates a new empty wallet with simple multi-address capability + Tworzy nowy, pusty portfel jedno- lub wieloadresowy + + + + + Name + Nazwa + + + + Force Single Address + Wymuś jeden adres + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Gdy włączone, ten portfel będzie ograniczony do jednego adresu. +Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego + + + + + Create + Utwórz + + + + New HD-Wallet + Nowy portfel HD + + + + This creates a new wallet that can be backed up with a seed-phrase + Tworzy to nowy portfel, który może być zachowany przy pomocy frazy seeda + + + + Derivation + Derywacja + + + + PayWithQR + + + Approve Payment + Zatwierdź płatność + + + + Send All + all money in wallet + Wyślij Wszystko + + + + Show Address + to show a bitcoincash address + Pokaż adres + + + + Edit Amount + Edit amount of money to send + Edytuj kwotę + + + + Invalid QR code + Nieprawidłowy kod QR + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Scanned text: <pre>%1</pre> + + + + Payment description + Payment description + + + + Destination Address + Destination Address + + + + Unlock Wallet + Unlock Wallet + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + PriceInputWidget + + + All Currencies + All Currencies + + + + QRScannerOverlay + + + Paste + Paste + + + + Failed + Failed + + + + ReceiveTab + + + Receive + Receive + + + + Share this QR to receive + Share this QR to receive + + + + Encrypted Wallet + Encrypted Wallet + + + + Import Running... + Import Running... + + + + + Description + Description + + + + Amount + requested amount of coin + Amount + + + + Address + Bitcoin Cash address + Address + + + + Clear + Clear + + + + + Payment Seen + Payment Seen + + + + Checking... + Checking... + + + + Transaction high risk + Transaction high risk + + + + Partially Paid + Partially Paid + + + + Payment Accepted + Payment Accepted + + + + Payment Settled + Payment Settled + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + + Continue + Continue + + + + SelectDefaultAccountPage + + + Select Wallet + Select Wallet + + + + Pick which wallet will be selected on starting Flowee Pay + Pick which wallet will be selected on starting Flowee Pay + + + + No InstaPay limit set + No InstaPay limit set + + + + InstaPay till %1 + InstaPay till %1 + + + + InstaPay is turned off + InstaPay is turned off + + + + SendTransactionsTab + + + Send + Send + + + + Start Payment + Start Payment + + + + SlideToApprove + + + SLIDE TO SEND + SLIDE TO SEND + + + + StartupScreen + + + Welcome! + Welcome! + + + + Continue + Continue + + + + Moving the world towards a Bitcoin Cash economy + Moving the world towards a Bitcoin Cash economy + + + + Scan me to send funds to your HD wallet + Scan me to send funds to your HD wallet + + + + OR + OR + + + + Add a different wallet + Add a different wallet + + + + TransactionDetails + + + Transaction Details + Transaction Details + + + + Transaction Hash + Transaction Hash + + + + Rejected + Rejected + + + + Unconfirmed + Unconfirmed + + + + Mined at + Mined at + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + %1 blocks ago + %1 blocks ago + + + + + Transaction comment + Transaction comment + + + + Size: %1 bytes + Size: %1 bytes + + + + Coinbase + Coinbase + + + + Is a CashFusion transaction. + Is a CashFusion transaction. + + + + Fees paid + Fees paid + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Fused from my addresses + + + + Sent from my addresses + Sent from my addresses + + + + Sent from addresses + Sent from addresses + + + + + Copy Address + Copy Address + + + + Fused into my addresses + Fused into my addresses + + + + Received at addresses + Received at addresses + + + + Received at my addresses + Received at my addresses + + + + TxInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 blocks ago + %1 blocks ago + %1 blocks ago + %1 blocks ago + + + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Payment to self + Payment to self + + + + Sent + Sent + + + + Holds a token + Holds a token + + + + Sent to + Sent to + + + + Value now + Value now + + + + Value then + Value then + + + + Transaction Details + Transaction Details + + + + UnlockWidget + + + Enter your wallet passcode + Enter your wallet passcode + + + + Open + open wallet with PIN + Open + + diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index eb9b75f..0078391 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -1,4 +1,169 @@ + + BuildTransactionModuleInfo + + + Create Transactions + Create Transactions + + + + This module allows building more powerful transactions in one simple user interface. + This module allows building more powerful transactions in one simple user interface. + + + + Build Transaction + Build Transaction + + + + PayToOthers + + + Build Transaction + Build Transaction + + + + Building Error + error during build + Building Error + + + + Add Payment Detail + page title + Add Payment Detail + + + + + Add Destination + Add Destination + + + + an address to send money to + an address to send money to + + + + Confirm Sending + confirm we want to send the transaction + Confirm Sending + + + + TXID + TXID + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Fee + + + + Transaction size + Transaction size + + + + %1 bytes + %1 bytes + + + + Fee per byte + Fee per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Destination + Destination + + + + unset + indication of empty + unset + + + + invalid + address is not correct + invalid + + + + + Copy Address + Copy Address + + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Send All + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Warning + Warning + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + I am certain + I am certain + + + + Drag to Edit + Drag to Edit + + + + Drag to Delete + Drag to Delete + + + + Prepare Payment... + Prepare Payment... + + + + Unlock Wallet + Unlock Wallet + + diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index bc500bf..6541016 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -40,7 +40,7 @@ - + Add Destination Voeg bestemming toe @@ -131,34 +131,39 @@ Bitcoin Cash-adres - + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + I am certain Ik ben zeker - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - + Prepare Payment... Betaling voorbereiden... + + + Unlock Wallet + Portemonnee ontgrendelen + diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts new file mode 100644 index 0000000..4ede30d --- /dev/null +++ b/translations/module-build-transaction_pl.ts @@ -0,0 +1,169 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Create Transactions + + + + This module allows building more powerful transactions in one simple user interface. + This module allows building more powerful transactions in one simple user interface. + + + + Build Transaction + Build Transaction + + + + PayToOthers + + + Build Transaction + Build Transaction + + + + Building Error + error during build + Building Error + + + + Add Payment Detail + page title + Add Payment Detail + + + + + Add Destination + Add Destination + + + + an address to send money to + an address to send money to + + + + Confirm Sending + confirm we want to send the transaction + Confirm Sending + + + + TXID + TXID + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Fee + + + + Transaction size + Transaction size + + + + %1 bytes + %1 bytes + + + + Fee per byte + Fee per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Destination + Destination + + + + unset + indication of empty + unset + + + + invalid + address is not correct + invalid + + + + + Copy Address + Copy Address + + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Send All + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Warning + Warning + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + I am certain + I am certain + + + + Drag to Edit + Drag to Edit + + + + Drag to Delete + Drag to Delete + + + + Prepare Payment... + Prepare Payment... + + + + Unlock Wallet + Unlock Wallet + + + diff --git a/translations/module-peers-view_en.ts b/translations/module-peers-view_en.ts new file mode 100644 index 0000000..5028c86 --- /dev/null +++ b/translations/module-peers-view_en.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Address + + + + Start-height: %1 + Start-height: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + initializing connection + + + + Verifying peer + Verifying peer + + + + Peer for wallet: %1 + Peer for wallet: %1 + + + + Peer for wallet + Peer for wallet + + + + PeersViewModuleInfo + + + Peers View + Peers View + + + + This module provides a view of network servers we connect to often called 'peers'. + This module provides a view of network servers we connect to often called 'peers'. + + + + Network Details + Network Details + + + diff --git a/translations/module-peers-view_nl.ts b/translations/module-peers-view_nl.ts new file mode 100644 index 0000000..ae756f0 --- /dev/null +++ b/translations/module-peers-view_nl.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adres + + + + Start-height: %1 + Beginhoogte: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + verbinding initialiseren + + + + Verifying peer + Peer verifiëren + + + + Peer for wallet: %1 + Peer voor portemonnee: %1 + + + + Peer for wallet + Peer voor portemonnee + + + + PeersViewModuleInfo + + + Peers View + Network Peers bekijken + + + + This module provides a view of network servers we connect to often called 'peers'. + Deze module biedt een overzicht van netwerkservers waarmee we verbinding maken, vaak 'peers' genoemd. + + + + Network Details + Netwerk Details + + + diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts new file mode 100644 index 0000000..326ecb8 --- /dev/null +++ b/translations/module-peers-view_pl.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adres + + + + Start-height: %1 + Wysokość początkowa: %1 + + + + ban-score: %1 + punktacja banu: %1 + + + + initializing connection + Inicjowanie połączenia + + + + Verifying peer + Weryfikowanie peera + + + + Peer for wallet: %1 + Peer dla portfela: %1 + + + + Peer for wallet + Peer dla portfela + + + + PeersViewModuleInfo + + + Peers View + Peers View + + + + This module provides a view of network servers we connect to often called 'peers'. + This module provides a view of network servers we connect to often called 'peers'. + + + + Network Details + Szczegóły sieci + + + -- 2.54.0 From 2fc269a5cfaecffe4d1af5b06acc0177089dcf27 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 23:16:35 +0200 Subject: [PATCH 0637/1428] Add the new module type to the GUI. This shows an icon that indicates that the module will be made available in the menu, if that module is able to do that. --- guis/mobile.qrc | 2 ++ guis/mobile/ExploreModules.qml | 17 +++++++++++++++++ guis/mobile/images/module-mainmenu-light.svg | 8 ++++++++ guis/mobile/images/module-mainmenu.svg | 8 ++++++++ src/ModuleSection.h | 4 ++++ 5 files changed, 39 insertions(+) create mode 100644 guis/mobile/images/module-mainmenu-light.svg create mode 100644 guis/mobile/images/module-mainmenu.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c9dd4e8..ec47736 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -43,6 +43,8 @@ mobile/images/keyboard-light.svg mobile/images/num-keyboard-light.svg mobile/images/num-keyboard.svg + mobile/images/module-mainmenu.svg + mobile/images/module-mainmenu-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index e6840c8..40147de 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -161,6 +161,23 @@ Page { return null; // module doesn't have such a section. } } + + Image { + source: "qrc:/module-mainmenu" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 16 + height: width + anchors.verticalCenter: parent.verticalCenter + visible: section != null + opacity: (visible && section.enabled) ? 0.8 : 0.3 + property QtObject section: { + // find the section our icon represents. + for (let s of modelData.sections) { + if (s.isSendMethod) + return s; + } + return null; // module doesn't have such a section. + } + } } } } diff --git a/guis/mobile/images/module-mainmenu-light.svg b/guis/mobile/images/module-mainmenu-light.svg new file mode 100644 index 0000000..cfcb763 --- /dev/null +++ b/guis/mobile/images/module-mainmenu-light.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/guis/mobile/images/module-mainmenu.svg b/guis/mobile/images/module-mainmenu.svg new file mode 100644 index 0000000..f0d60c3 --- /dev/null +++ b/guis/mobile/images/module-mainmenu.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/ModuleSection.h b/src/ModuleSection.h index d41325b..de351fb 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -34,6 +34,7 @@ class ModuleSection : public QObject Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) + Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) public: /// The placement in the main app of this section. enum SectionType { @@ -72,6 +73,9 @@ public: bool isSendMethod() const { return m_type == SendMethod; } + bool isMainMenuMethod() const { + return m_type == MainMenuItem; + } bool enabled() const; /** -- 2.54.0 From 89ce6fcf531a73dfc64d85c7037d5aac0cc54d29 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Jul 2023 23:25:14 +0200 Subject: [PATCH 0638/1428] Comment out the not yet supportd part Lets move the UI / UX for encrypting wallets to a future release. --- guis/mobile/LockApplication.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/guis/mobile/LockApplication.qml b/guis/mobile/LockApplication.qml index 9698289..bb7394b 100644 --- a/guis/mobile/LockApplication.qml +++ b/guis/mobile/LockApplication.qml @@ -136,6 +136,7 @@ Page { } } + /* PageTitledBox { title: qsTr("Encrypt your wallet data") width: parent.width @@ -150,13 +151,9 @@ Page { subtext: qsTr("Secure against all usage") showPageIcon: true } - } + } */ } - Item { - } - - // the invisible drag handler is separate but on similar to the input widget. Item { id: dragHandler -- 2.54.0 From 4eb0053042f487ea3d451eab13f1e8338f81005c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Jul 2023 12:24:31 +0200 Subject: [PATCH 0639/1428] A litheny of UX-bugfixes. --- guis/Flowee/CardTypeSelector.qml | 9 +-- guis/Flowee/CheckBox.qml | 14 ++--- guis/Flowee/ComboBox.qml | 13 ++++- guis/Flowee/MultilineTextField.qml | 1 + guis/mobile/About.qml | 2 + guis/mobile/AccountPageListItem.qml | 4 ++ guis/mobile/ExploreModules.qml | 7 ++- guis/mobile/GuiSettings.qml | 1 + guis/mobile/ImportWalletPage.qml | 5 +- guis/mobile/MainViewBase.qml | 6 +- guis/mobile/NewAccount.qml | 6 +- guis/mobile/ReceiveTab.qml | 91 ++++++++++++++++------------- guis/mobile/SendTransactionsTab.qml | 3 +- guis/mobile/TxInfoSmall.qml | 13 ++--- guis/mobile/UnlockWidget.qml | 18 +++--- 15 files changed, 113 insertions(+), 80 deletions(-) diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index bb2d522..b29e696 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -1,5 +1,5 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ Item { property bool selected: parent.selectedKey === key width: name.width * 2 height: 270 + clip: true Rectangle { color: { @@ -51,16 +52,16 @@ Item { Label { id: name anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 14 + font.pixelSize: mainWindow.font.pixelSize * 1.3 font.bold: true - y: 14 + 25 + y: 25 } Column { id: contentArea spacing: 15 width: parent.width anchors.top: name.bottom - anchors.topMargin: 26 + anchors.topMargin: 16 Repeater { model: root.features diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 78f4e92..a354f9f 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -25,16 +25,14 @@ T.CheckBox { property bool sliderOnIndicator: true property string toolTipText: "" - implicitWidth: slider.width + 6 + title.width + (toolTipText === "" ? 0 : (questionMarkIcon.width + 16)) + implicitWidth: slider.width + 6 + title.implicitWidth + (toolTipText === "" ? 0 : (questionMarkIcon.width + 16)) implicitHeight: Math.max(slider.implicitHeight, title.implicitHeight) clip: true indicator: Item { id: slider - width: implicitWidth - height: implicitHeight - implicitWidth: 50 - implicitHeight: 22 + implicitWidth: implicitHeight * 2.1 + implicitHeight: title.implicitHeight / title.lineCount Rectangle { anchors.fill: parent @@ -84,14 +82,16 @@ T.CheckBox { anchors.left: slider.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 6 + anchors.right: parent.right color: enabled ? palette.windowText : "darkgray" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Rectangle { id: questionMarkIcon visible: control.toolTipText !== "" && control.enabled - width: q.width + 14 - height: width + width: title.implicitWidth + 14 + height: title.implicitHeight anchors.left: title.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 16 diff --git a/guis/Flowee/ComboBox.qml b/guis/Flowee/ComboBox.qml index d559e75..9e1ed8e 100644 --- a/guis/Flowee/ComboBox.qml +++ b/guis/Flowee/ComboBox.qml @@ -30,7 +30,7 @@ QQC2.ComboBox { id: basicButton width: root.width highlighted: root.highlightedIndex === index - contentItem: Text { + contentItem: Label { text: modelData color: basicButton.highlighted ? root.palette.highlightedText : root.palette.buttonText verticalAlignment: Text.AlignVCenter @@ -46,6 +46,17 @@ QQC2.ComboBox { radius: 2 } + contentItem: Text { + leftPadding: 6 + rightPadding: root.indicator.width + root.spacing + + text: root.displayText + font: root.font + color: root.palette.light + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + // Personal preference, the single arrow down makes more sense to me. // We've had that for decades. indicator: Canvas { diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 3e84b84..6241755 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -101,6 +101,7 @@ QQC2.Control { selectionColor: palette.highlight selectByMouse: true wrapMode: TextEdit.Wrap + font: root.font property bool showingPlaceholder: false onActiveFocusChanged: { diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index da500b3..81802be 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -33,6 +33,8 @@ Page { Flowee.Label { text: "Flowee Pay (mobile) v" + Application.version + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Item { width: 10; height: 10 } // spacer diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index fccec98..a91ed22 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -205,12 +205,14 @@ QQC2.Control { PageTitledBox { id: optionsBox Flowee.CheckBox { + width: parent.width text: qsTr("Change Addresses") visible: root.account.isHDWallet onClicked: root.account.secrets.showChangeChain = checked toolTipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") } Flowee.CheckBox { + width: parent.width text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked @@ -339,12 +341,14 @@ QQC2.Control { } } Flowee.CheckBox { + Layout.fillWidth: true visible: !singleAccountSetup && !root.account.isArchived checked: root.account.countBalance === false text: qsTr("Hide balance in overviews") onClicked: root.account.countBalance = !checked } Flowee.CheckBox { + Layout.fillWidth: true visible: !singleAccountSetup && !root.account.isArchived checked: root.account.isPrivate text: qsTr("Hide in private mode") diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 40147de..e11a0ce 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -75,6 +75,7 @@ Page { anchors.bottom: statusField.top anchors.bottomMargin: 10 anchors.right: parent.right + z: 10 Rectangle { color: palette.alternateBase anchors.fill: descriptionLabel @@ -128,6 +129,7 @@ Page { width: parent.width height: 40 anchors.bottom: parent.bottom + z: 1 Row { spacing: 10 @@ -138,8 +140,9 @@ Page { anchors.verticalCenter: parent.verticalCenter checked: modelData.enabled onClicked: { + var newValue = checked; for (let s of modelData.sections) { - s.enabled = checked + s.enabled = newValue; } } } @@ -172,7 +175,7 @@ Page { property QtObject section: { // find the section our icon represents. for (let s of modelData.sections) { - if (s.isSendMethod) + if (s.isMainMenuMethod) return s; } return null; // module doesn't have such a section. diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 7f10cf0..cf82e86 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -135,6 +135,7 @@ Page { visible: Pay.isMainChain // because we only have one option right now Flowee.CheckBox { + width: parent.width text: qsTr("Show Bitcoin Cash value") checked: Pay.activityShowsBch onCheckedChanged: Pay.activityShowsBch = checked diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 9d970d3..bfdc294 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -121,7 +121,7 @@ Page { Flow { width: parent.width spacing: 10 - QQC2.ComboBox { + Flowee.ComboBox { id: month model: { let locale = Qt.locale(); @@ -131,8 +131,9 @@ Page { } return list; } + width: implicitWidth * 1.3 // this makes it fit for bigger fonts. } - QQC2.ComboBox { + Flowee.ComboBox { id: year model: { var list = []; diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index c037ad6..890b361 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -158,7 +158,7 @@ QQC2.Control { Repeater { model: stack.children.length delegate: Item { - height: 55 + height: 65 width: root.width / stack.children.length; Rectangle { @@ -176,8 +176,8 @@ QQC2.Control { } Image { source: stack.children[modelData].icon - width: 20 - height: 20 + width: 30 + height: 30 smooth: true y: 8 anchors.horizontalCenter: parent.horizontalCenter diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 4862bb4..905d915 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -17,7 +17,6 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay @@ -126,6 +125,7 @@ Page { } } Flowee.CheckBox { + Layout.fillWidth: true id: singleAddress text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") @@ -133,7 +133,7 @@ Page { Item { width: parent.width height: createButton.implicitHeight - QQC2.Button { + Flowee.Button { id: createButton anchors.right: parent.right text: qsTr("Create") @@ -187,7 +187,7 @@ Page { Item { width: parent.width height: createButton.implicitHeight - QQC2.Button { + Flowee.Button { id: createButton enabled: derivationPath.derivationOk anchors.right: parent.right diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 870d3cd..de41a1f 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -150,53 +150,62 @@ FocusScope { FocusScope { id: feedbackScope width: parent.width - height: column.height - Column { - id: column - width: parent.width - spacing: 10 - opacity: qrViewActive ? 1 : 0 - y: instructions.height + qr.height + 30 + y: instructions.height + qr.height + 30 + height: receiveTab.height - y + clip: true - PageTitledBox { + Flickable { + anchors.fill: parent + contentHeight: column.height + contentWidth: parent.width + boundsMovement: Flickable.StopAtBounds + + Column { + id: column width: parent.width - visible: request.message !== "" - title: qsTr("Description") - Flowee.Label { - text: request.message - } - } - PageTitledBox { - width: parent.width - visible: request.amount > 0 - title: qsTr("Amount", "requested amount of coin") - Flowee.BitcoinAmountLabel { - colorize: false - value: request.amount - } - } - PageTitledBox { - width: parent.width - title: qsTr("Address", "Bitcoin Cash address") - Flowee.LabelWithClipboard { + spacing: 10 + opacity: qrViewActive ? 1 : 0 + + PageTitledBox { width: parent.width - text: request.addressShort - font.pixelSize: instructions.font.pixelSize * 0.9 + visible: request.message !== "" + title: qsTr("Description") + Flowee.Label { + text: request.message + } } - } - Flowee.Button { - anchors.right: parent.right - text: qsTr("Clear") - enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; - onClicked: { - request.clear(); - request.account = portfolio.current; - description.text = ""; - priceInput.paymentBackend.paymentAmount = 0; - request.start(); + PageTitledBox { + width: parent.width + visible: request.amount > 0 + title: qsTr("Amount", "requested amount of coin") + Flowee.BitcoinAmountLabel { + colorize: false + value: request.amount + } } + PageTitledBox { + width: parent.width + title: qsTr("Address", "Bitcoin Cash address") + Flowee.LabelWithClipboard { + width: parent.width + text: request.addressShort + font.pixelSize: instructions.font.pixelSize * 0.9 + } + } + Flowee.Button { + anchors.right: parent.right + text: qsTr("Clear") + enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; + onClicked: { + request.clear(); + request.account = portfolio.current; + description.text = ""; + priceInput.paymentBackend.paymentAmount = 0; + request.start(); + } + } + Behavior on opacity { OpacityAnimator { } } } - Behavior on opacity { OpacityAnimator { } } } } diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 3446ec9..d1067eb 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ FocusScope { ColumnLayout { width: parent.width - 20 x: 10 + y: 10 InstaPayConfigButton { } diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 6f7b605..30f9926 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -33,7 +33,7 @@ ColumnLayout { property QtObject infoObject: null property int minedHeight: model.height // local cache - QQC2.Label { + Flowee.Label { property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' text: { if (isRejected) @@ -43,13 +43,7 @@ ColumnLayout { return ""; } visible: text !== "" - color: { - if (isRejected) { - // Transaction is rejected by network - return Pay.useDarkSkin ? "#ec2327" : "#b41214"; - } - return palette.windowText - } + color: isRejected ? mainWindow.errorRed : palette.windowText } GridLayout { @@ -108,7 +102,7 @@ ColumnLayout { // visible if at least one output has a token. var outputs = infoObject.outputs; for (let o of outputs) { - if (o != null && o.hasCashToken) + if (o !== null && o.hasCashToken) return true; } return false; @@ -193,4 +187,5 @@ ColumnLayout { newItem.infoObject = root.infoObject; } } + Item { width: 10; height: 4 } // spacing } diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index 5c781bb..40c8c6f 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -68,7 +68,7 @@ Item { height: 80 anchors.horizontalCenter: parent.horizontalCenter smooth: true - y: 40 + y: parent.height > 700 ? 40 : 10 } Image { @@ -98,13 +98,16 @@ Item { text: qsTr("Enter your wallet passcode") anchors.top: lockIcon.bottom anchors.topMargin: 20 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: parent.width Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes } // Show the typed pin code, but as bullets as you type. Row { anchors.top: introText.bottom - anchors.topMargin: 20 + anchors.topMargin: root.height > 700 ? 20 : 6 spacing: 10 anchors.horizontalCenter: parent.horizontalCenter visible: switchButton.numericInput @@ -141,7 +144,8 @@ Item { id: keyboard x: switchButton.numericInput ? 0 : 0 - parent.width - y: parent.height - openButton.height - height - 20 + y: parent.height - openButton.height - height + - (root.height > 700 ? 20 : 6) width: parent.width dataInput: Item { property QtObject editor: Item { @@ -200,10 +204,10 @@ Item { } MouseArea { - width: parent.width + 20 // extend to right physical edge - x: -5 + width: parent.width + 50 // extend to right physical edge + x: -15 y: 0 - parent.y - 5 - height: parent.height + 10 + height: pwdField.height + 10 onClicked: pwdField.hideText = !pwdField.hideText } } @@ -216,7 +220,7 @@ Item { y: { if (switchButton.numericInput) return parent.height - height - return pwdField.y + pwdField.height + 10; + return pwdField.y + pwdField.height + 20; } text: qsTr("Open", "open wallet with PIN") -- 2.54.0 From 337f93e44e0104f0952c3fb255ff3bcedc28deac Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Jul 2023 17:04:14 +0200 Subject: [PATCH 0640/1428] Improve the UX on the broadcast screen This adds some new fields and fixes various small UX issues. --- guis/Flowee/BroadcastFeedback.qml | 220 +++++++++++++++++------------- guis/Flowee/TextField.qml | 2 +- guis/mobile/ExploreModules.qml | 1 + 3 files changed, 128 insertions(+), 95 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index b3feecf..3aabb78 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,6 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import QtQuick.Shapes -import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors import Flowee.org.pay @@ -83,9 +82,14 @@ QQC2.Control { StateChangeScript { script: ControlColors.applyDarkSkin(root) } PropertyChanges { target: background; color: "#7f0000" } PropertyChanges { target: circleShape; opacity: 0 } - PropertyChanges { target: root; status: qsTr("Transaction rejected by network") } + PropertyChanges { target: root; status: qsTr("Failed") } + PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } } ] + MouseArea { + anchors.fill: parent // eat all mouse events. + } + Rectangle { id: background width: parent.width @@ -95,102 +99,130 @@ QQC2.Control { color: mainWindow.floweeGreen y: height + 2 - // The 'progress' icon. - Shape { - id: circleShape - anchors.horizontalCenter: parent.horizontalCenter - y: 25 - anchors.top: title.bottom - anchors.topMargin: 10 - width: 160 - height: width - smooth: true - ShapePath { - strokeWidth: 20 - strokeColor: "#dedede" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 100; startY: 10 + Column { + spacing: 10 + width: parent.width + Label { + id: errorLabel + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: parent.width + } - PathAngleArc { - id: progressCircle - centerX: 80 - centerY: 80 - radiusX: 70; radiusY: 70 - startAngle: -80 - sweepAngle: 0 - Behavior on sweepAngle { NumberAnimation { duration: 2500 } } - Behavior on startAngle { NumberAnimation { } } + Item { + anchors.horizontalCenter: parent.horizontalCenter + width: 160 + height: 160 + + // The 'progress' icon. + Shape { + id: circleShape + width: 160 + height: width + smooth: true + ShapePath { + strokeWidth: 20 + strokeColor: "#dedede" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 100; startY: 10 + + PathAngleArc { + id: progressCircle + centerX: 80 + centerY: 80 + radiusX: 70; radiusY: 70 + startAngle: -80 + sweepAngle: 0 + Behavior on sweepAngle { NumberAnimation { duration: 2500 } } + Behavior on startAngle { NumberAnimation { } } + } + } + Behavior on opacity { NumberAnimation { } } + } + Shape { + id: checkShape + anchors.fill: circleShape + smooth: true + opacity: 0 + ShapePath { + id: checkPath + strokeWidth: 16 + strokeColor: "green" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 52; startY: 80 + PathLine { x: 76; y: 110 } + PathLine { x: 125; y: 47 } + } } } - Behavior on opacity { NumberAnimation { } } - } - Shape { - id: checkShape - anchors.fill: circleShape - smooth: true - opacity: 0 - ShapePath { - id: checkPath - strokeWidth: 16 - strokeColor: "green" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 52; startY: 80 - PathLine { x: 76; y: 110 } - PathLine { x: 125; y: 47 } + + Label { + id: fiatAmount + anchors.horizontalCenter: parent.horizontalCenter + // color: "black" + font.pixelSize: paymentAddressLabel.font.pixelSize * 1.5 + text: Fiat.formattedPrice(payment.paymentAmountFiat) + visible: Fiat.price !== 0 } - } - - Label { - id: fiatAmount - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: circleShape.bottom - anchors.topMargin: 10 - color: "black" - font.pixelSize: 38 - text: Fiat.formattedPrice(payment.paymentAmountFiat) - visible: Fiat.price !== 0 - } - BitcoinAmountLabel { - id: cryptoAmount - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: fiatAmount.bottom - anchors.topMargin: 20 - fontPixelSize: 28 - value: payment.paymentAmount - colorize: false - showFiat: false - } - - TextField { - id: transactionComment - anchors.top: txIdFeedback.bottom - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(400, parent.width - 40); - onTextChanged: payment.userComment = text - placeholderText: qsTr("Add a personal note") - placeholderTextColor: "#3e3e3e" - text: payment.userComment - } - - RowLayout { - id: txIdFeedback - spacing: 30 - anchors.top: cryptoAmount.bottom - visible: root.state === "waiting" || root.state === "success" - anchors.topMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - ImageButton { - source: "qrc:/edit-copy.svg" - onClicked: Pay.copyToClipboard(payment.txid); - responseText: qsTr("Copied TXID to clipboard") + BitcoinAmountLabel { + id: cryptoAmount + anchors.horizontalCenter: parent.horizontalCenter + fontPixelSize: paymentAddressLabel.font.pixelSize * 1.15 + value: payment.paymentAmount + colorize: false + showFiat: false } - ImageButton { - source: "qrc:/internet.svg" - onClicked: Pay.openInExplorer(payment.txid); - responseText: qsTr("Opening Website") + + Column { + // column to avoid spacing between these two labels. + width: parent.width + Label { + id: paymentAddressLabel + // color: "black" + text: qsTr("Payment has been sent to:") + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: parent.width + } + Label { + text: payment.niceAddress + // color: "black" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: parent.width + font.pixelSize: paymentAddressLabel.font.pixelSize * 0.8 + } + } + Item { width: 1; height: 10} // spacer + RowLayout { + id: txIdFeedback + spacing: 30 + visible: root.state === "waiting" || root.state === "success" + anchors.horizontalCenter: parent.horizontalCenter + ImageButton { + source: "qrc:/edit-copy.svg" + onClicked: Pay.copyToClipboard(payment.formattedTargetAddress); + responseText: qsTr("Copied address to clipboard") + } + ImageButton { + source: "qrc:/internet.svg" + onClicked: Pay.openInExplorer(payment.txid); + responseText: qsTr("Opening Website") + } + } + + Item { width: 1; height: 10} // spacer + + TextField { + id: transactionComment + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(400, parent.width - 40); + onEditingFinished: payment.userComment = text + placeholderText: qsTr("Add a personal note") + placeholderTextColor: "#505050" + text: payment.userComment } } Button { diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 54866d5..2b6b75e 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -25,7 +25,7 @@ QQC2.TextField { id: root selectByMouse: true // In Qt6.3 this is just always black, so adjust to color - placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#3e3e3e" + placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#505050" color: palette.windowText property string totalText: { diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index e11a0ce..16a6563 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -106,6 +106,7 @@ Page { opacity: modelData.enabled ? 1 : 0 visible: opacity > 0 Behavior on opacity { NumberAnimation { } } + z: 11 Item { rotation: 45 -- 2.54.0 From a56e762853656a324495485e467ca1636038e51a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Jul 2023 11:23:08 +0200 Subject: [PATCH 0641/1428] After hiding on Android for some minutes we lock again The Android design is that you can "close" the app while it is not actually killed. It stays frozen but in the background. This makes Pay check the clock on 'wake up' and if 10 minutes has passed it will lock again. --- guis/mobile/UnlockApplication.qml | 25 ++++++++++++++++++++----- guis/mobile/main.qml | 6 +++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 7cdfde5..88474e7 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -17,11 +17,26 @@ */ import QtQuick -UnlockWidget { - onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); +FocusScope { + Rectangle { + anchors.fill: parent + color: palette.window + } + MouseArea { // eat all mouse events. + anchors.fill: parent + } - // called from main.qml, need to be implemented here - // since we dont extend the Page type. - function takeFocus() { + UnlockWidget { + anchors.fill: parent + anchors.margins: 10 + onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); + + // called from main.qml, need to be implemented here + // since we dont extend the Page type. + function takeFocus() { + } + } + Keys.onPressed: (event)=> { + event.accepted = true; // at all key events. } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 9a03ec7..710a69c 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -63,7 +63,7 @@ ApplicationWindow { StackView { id: thePile anchors.fill: parent - initialItem: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "./Loading.qml"; + initialItem: "./Loading.qml" onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); enabled: !menuOverlay.open @@ -135,4 +135,8 @@ ApplicationWindow { } } + Loader { + source: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "" + anchors.fill: parent + } } -- 2.54.0 From f649d8e39a442812148f6d9f7cf7d0f96c5cfa7f Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Jul 2023 09:41:04 +0200 Subject: [PATCH 0642/1428] Fix crash on pay-to-many-addressses. Hide target address if there is more than one. --- guis/Flowee/BroadcastFeedback.qml | 23 +++++++++++----- modules/build-transaction/PayToOthers.qml | 33 +++++++++++------------ src/Payment.h | 2 ++ 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 3aabb78..b7d5153 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -86,9 +86,6 @@ QQC2.Control { PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } } ] - MouseArea { - anchors.fill: parent // eat all mouse events. - } Rectangle { id: background @@ -98,6 +95,9 @@ QQC2.Control { visible: opacity > 0 color: mainWindow.floweeGreen y: height + 2 + MouseArea { + anchors.fill: parent // eat all mouse events. + } Column { spacing: 10 @@ -161,7 +161,6 @@ QQC2.Control { Label { id: fiatAmount anchors.horizontalCenter: parent.horizontalCenter - // color: "black" font.pixelSize: paymentAddressLabel.font.pixelSize * 1.5 text: Fiat.formattedPrice(payment.paymentAmountFiat) visible: Fiat.price !== 0 @@ -178,17 +177,27 @@ QQC2.Control { Column { // column to avoid spacing between these two labels. width: parent.width + visible: addressLabel.visible Label { id: paymentAddressLabel - // color: "black" text: qsTr("Payment has been sent to:") horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere width: parent.width } Label { - text: payment.niceAddress - // color: "black" + id: addressLabel + text: { + var answer = ""; + for (let detail of payment.details) { + if (detail.isOutput) { + if (answer !== "") // then there are multiple outputs! + return ""; + answer = detail.niceAddress; + } + } + return answer; + } horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere width: parent.width diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 6b261e9..579394d 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -153,22 +153,9 @@ Page { anchors.bottom: parent.bottom anchors.bottomMargin: 10 width: parent.width - onActivated: payment.broadcast() - } - - Flowee.BroadcastFeedback { - id: broadcastPage - anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. - anchors.rightMargin: -10 - onStatusChanged: { - if (status !== "") - root.headerText = status; - } - onCloseButtonPressed: { - var mainView = thePile.get(0); - mainView.currentIndex = 0; // go to the 'main' tab. - thePile.pop(); // this screen - thePile.pop(); // parent screen + onActivated: { + payment.broadcast() + thePile.pop(); // the broadcast feedback is on the main screen. } } } @@ -647,8 +634,20 @@ Page { thePile.push(paymentInfoPage); } } - } } } + Flowee.BroadcastFeedback { + anchors.leftMargin: -10 // go against the margins that Page gave us to show more fullscreen. + anchors.rightMargin: -10 + onStatusChanged: { + if (status !== "") + root.headerText = status; + } + onCloseButtonPressed: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); + } + } } diff --git a/src/Payment.h b/src/Payment.h index 0981c39..8ecb9e5 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -275,6 +275,8 @@ class PaymentDetail : public QObject Q_PROPERTY(Payment::DetailType type READ type CONSTANT) Q_PROPERTY(bool collapsable READ collapsable WRITE setCollapsable NOTIFY collapsableChanged) Q_PROPERTY(bool collapsed READ collapsed WRITE setCollapsed NOTIFY collapsedChanged) + Q_PROPERTY(bool isOutput READ isOutput CONSTANT) + Q_PROPERTY(bool isInput READ isInputs CONSTANT) public: PaymentDetail(Payment *parent, Payment::DetailType type); -- 2.54.0 From 00bb3906be81cc917986e57ee02536ceee7a9796 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Jul 2023 10:51:35 +0200 Subject: [PATCH 0643/1428] New release number --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3777085..d141cce 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="14" android:versionName="2023.07.1"> diff --git a/src/main.cpp b/src/main.cpp index 251da8f..0e2c805 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.07.0"); + qapp.setApplicationVersion("2023.07.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From f741f317e8b0dcaf753b7a77af3d579bf42483cc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Jul 2023 08:38:26 +0200 Subject: [PATCH 0644/1428] Cleanup old files --- CMakeLists.txt | 1 - cmake/BrewHelper.cmake | 21 ----------- cmake/ExternalLibraryHelper.cmake | 58 ------------------------------- cmake/FindQREncode.cmake | 58 ------------------------------- 4 files changed, 138 deletions(-) delete mode 100644 cmake/BrewHelper.cmake delete mode 100644 cmake/ExternalLibraryHelper.cmake delete mode 100644 cmake/FindQREncode.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 60a050f..a668f5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,6 @@ project(flowee_pay VERSION 0.2 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) # calling find_package Qt two times seems to be needed to get the Qt version :shrug: find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core) diff --git a/cmake/BrewHelper.cmake b/cmake/BrewHelper.cmake deleted file mode 100644 index 6bf45d2..0000000 --- a/cmake/BrewHelper.cmake +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2018 The Bitcoin developers - -find_program(BREW brew) - -function(find_brew_prefix VAR NAME) - if(NOT BREW) - return() - endif() - - if(DEFINED ${VAR}) - return() - endif() - - execute_process( - COMMAND ${BREW} --prefix ${NAME} - OUTPUT_VARIABLE PREFIX - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - set(${VAR} ${PREFIX} PARENT_SCOPE) -endfunction() diff --git a/cmake/ExternalLibraryHelper.cmake b/cmake/ExternalLibraryHelper.cmake deleted file mode 100644 index 48cae27..0000000 --- a/cmake/ExternalLibraryHelper.cmake +++ /dev/null @@ -1,58 +0,0 @@ -include(FindPackageMessage) - -# Find a library component, set the variables and create an imported target. -# Variable names are compliant with cmake standards. -function(find_component LIB COMPONENT) - cmake_parse_arguments(ARG - "" - "" - "HINTS;INCLUDE_DIRS;INTERFACE_LINK_LIBRARIES;NAMES;PATHS;PATH_SUFFIXES" - ${ARGN} - ) - - # If the component is not requested, skip the search. - if(${LIB}_FIND_COMPONENTS AND NOT ${COMPONENT} IN_LIST ${LIB}_FIND_COMPONENTS) - return() - endif() - - find_library(${LIB}_${COMPONENT}_LIBRARY - NAMES ${ARG_NAMES} - PATHS "" ${ARG_PATHS} - HINTS "" ${ARG_HINTS} - PATH_SUFFIXES "" ${ARG_PATH_SUFFIXES} - ) - mark_as_advanced(${LIB}_${COMPONENT}_LIBRARY) - - if(${LIB}_${COMPONENT}_LIBRARY) - # On success, set the standard FOUND variable... - set(${LIB}_${COMPONENT}_FOUND TRUE PARENT_SCOPE) - - # ... and append the library path to the LIBRARIES variable ... - list(APPEND ${LIB}_LIBRARIES - "${${LIB}_${COMPONENT}_LIBRARY}" - ${ARG_INTERFACE_LINK_LIBRARIES} - ) - list(REMOVE_DUPLICATES ${LIB}_LIBRARIES) - set(${LIB}_LIBRARIES ${${LIB}_LIBRARIES} PARENT_SCOPE) - - # ... and create an imported target for the component, if not already - # done. - if(NOT TARGET ${LIB}::${COMPONENT}) - add_library(${LIB}::${COMPONENT} UNKNOWN IMPORTED) - set_target_properties(${LIB}::${COMPONENT} PROPERTIES - IMPORTED_LOCATION "${${LIB}_${COMPONENT}_LIBRARY}" - ) - set_property(TARGET ${LIB}::${COMPONENT} PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${ARG_INCLUDE_DIRS} - ) - set_property(TARGET ${LIB}::${COMPONENT} PROPERTY - INTERFACE_LINK_LIBRARIES ${ARG_INTERFACE_LINK_LIBRARIES} - ) - endif() - - find_package_message("${LIB}_${COMPONENT}" - "Found ${LIB} component ${COMPONENT}: ${${LIB}_${COMPONENT}_LIBRARY}" - "[${${LIB}_${COMPONENT}_LIBRARY}][${ARG_INCLUDE_DIRS}]" - ) - endif() -endfunction() diff --git a/cmake/FindQREncode.cmake b/cmake/FindQREncode.cmake deleted file mode 100644 index 8e7538d..0000000 --- a/cmake/FindQREncode.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) 2019-2020 The Bitcoin developers - -#.rst -# FindQREncode -# ------------- -# -# Find the QREncode library. The following -# components are available:: -# qrencode -# -# This will define the following variables:: -# -# QREncode_FOUND - system has QREncode lib -# QREncode_INCLUDE_DIRS - the QREncode include directories -# QREncode_LIBRARIES - Libraries needed to use QREncode -# -# And the following imported target:: -# -# QREncode::qrencode - -include(BrewHelper) -find_brew_prefix(BREW_HINT qrencode) - -find_package(PkgConfig) -pkg_check_modules(PC_QREncode QUIET libqrencode) - -if (ANDROID) - if (EXISTS "/opt/android-qrencode/include/qrencode.h") - set (QREncode_INCLUDE_DIR "/opt/android-qrencode/include") - set (QREncode_FOUND TRUE) - set (QREncode_LIBRARIES "/opt/android-qrencode/lib/libqrencode.a") - endif() -else() - find_path(QREncode_INCLUDE_DIR - NAMES qrencode.h - HINTS ${BREW_HINT} - PATHS ${PC_QREncode_INCLUDE_DIRS} - ) -endif() - -set(QREncode_INCLUDE_DIRS "${QREncode_INCLUDE_DIR}") -mark_as_advanced(QREncode_INCLUDE_DIR) - -include(ExternalLibraryHelper) -find_component(QREncode qrencode - NAMES qrencode - HINTS ${BREW_HINT} - PATHS ${PC_QREncode_LIBRARY_DIR} - INCLUDE_DIRS ${QREncode_INCLUDE_DIR} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QREncode - REQUIRED_VARS - QREncode_INCLUDE_DIR - HANDLE_COMPONENTS -) -include_directories(${QREncode_INCLUDE_DIR}) -- 2.54.0 From d109e0b98716a71ba73641d596dfdb134066fa86 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Jul 2023 16:23:22 +0200 Subject: [PATCH 0645/1428] Avoid weird rounding error. Parsing the amount 0.00522325 caused the double to have a 49999 at the end instead of that 5. This now will be rounded back up the the proper sats value. --- src/PaymentDetailOutput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 57f0491..fb7845f 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -73,7 +73,7 @@ double PaymentDetailOutput::paymentAmount() const void PaymentDetailOutput::setPaymentAmount(double amount_) { - qint64 amount = static_cast(amount_); + qint64 amount = std::round(amount_); if (m_paymentAmount == amount) return; if (m_maxAllowed && m_maxSelected) { -- 2.54.0 From ccbeea04863a50c8d5d25d10a764463925e3d870 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jul 2023 10:14:49 +0200 Subject: [PATCH 0646/1428] Skip modules reporting when mobile is not built. --- CMakeLists.txt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a668f5a..bc1ccd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,18 +340,20 @@ if (${BUILD_PAY_TOOLS}) message ("-> Building Pay tools") endif() -file(GLOB sub_directories ${CMAKE_SOURCE_DIR}/modules/*) -foreach (child ${sub_directories}) - if (IS_DIRECTORY ${child}) - file(GLOB subModule ${child}/*ModuleInfo.h) - if (EXISTS ${subModule}) - file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) - message("-> Including module '${module}'") - get_filename_component(className ${subModule} NAME_WE) - message(" ${className}") +if (${BUILD_MOBILE_PAY}) + file(GLOB sub_directories ${CMAKE_SOURCE_DIR}/modules/*) + foreach (child ${sub_directories}) + if (IS_DIRECTORY ${child}) + file(GLOB subModule ${child}/*ModuleInfo.h) + if (EXISTS ${subModule}) + file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) + message("-> Including module '${module}'") + get_filename_component(className ${subModule} NAME_WE) + message(" ${className}") + endif() endif() - endif() -endforeach() + endforeach() +endif() message("") message("") -- 2.54.0 From a12eba7c6d3453f6173babc9236393a3aa19f45f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Aug 2023 20:10:12 +0200 Subject: [PATCH 0647/1428] Avoid sending duplicate filters. The backend will tell us that there is good reason to believe the remote needs to get a new bloom filter, which is causing a lot of extra traffic if there actually is no change in the utxo of the wallet. This introduces a bool to mark changes that would affect the bloom filter to be send, and only upload a new one when that bool is true. --- src/Wallet.cpp | 24 ++++++++++++++++-------- src/Wallet.h | 8 +++++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index c75dd72..bf29ccd 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -102,7 +102,7 @@ Wallet::Wallet(const boost::filesystem::path &basedir, uint16_t segmentId, uint3 assert(!isDecrypted()); // because, how? m_segment->setEnabled(false); } - rebuildBloom(); + rebuildBloom(ForceBuild); connect (this, SIGNAL(startDelayedSave()), this, SLOT(delayedSave()), Qt::QueuedConnection); // ensure right thread calls us. } @@ -310,6 +310,7 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) m_walletSecrets.insert(std::make_pair(m_nextWalletSecretId++, secret)); m_secretsChanged = true; + m_utxoDirty = true; } rebuildBloom(); @@ -367,9 +368,10 @@ void Wallet::newTransaction(const Tx &tx) if (wtx.outputs.empty() && wtx.inputToWTX.empty()) { // no connection to our UTXOs if (++m_bloomScore > 25) - rebuildBloom(); + rebuildBloom(ForceBuild); return; } + m_utxoDirty = true; setUserOwnedWallet(true); wtx.minedBlockHeight = WalletPriv::Unconfirmed; bool dummy = false; @@ -469,6 +471,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: continue; walletTransactionId = oldTx->second; } + m_utxoDirty = true; const bool wasUnconfirmed = wtx.minedBlockHeight == WalletPriv::Unconfirmed; while (updateHDSignatures(wtx, needNewBloom)) { // if we added a bunch of new private keys, then rerun the matching @@ -989,7 +992,7 @@ int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCa return -1; } -void Wallet::rebuildBloom() +void Wallet::rebuildBloom(RebuildBloomOption option) { int unusedToInclude = filter_unusedToInclude; int hdUnusedToInclude = filter_hdUnusedToInclude; @@ -1012,6 +1015,9 @@ void Wallet::rebuildBloom() return; } } + if (option != ForceBuild && !m_utxoDirty) + return; + m_utxoDirty = false; auto lock = m_segment->clearFilter(); // ^ notice that the 'lock' we get back will broadcast the new filter when it goes out of scope. @@ -1302,8 +1308,10 @@ int Wallet::reserveUnusedAddress(KeyId &keyId, PrivKeyType pkt) continue; secret.reserved = true; keyId = secret.address; - // TODO check if this address has already been sent and we can avoid the bloom rebuild - rebuildBloom(); // make sure that we actually observe changes on this address + + // check if this address has already been sent and we can avoid the bloom rebuild + if (m_hdData == nullptr || m_hdData->lastIncludedMainKey < i->first) + rebuildBloom(ForceBuild); // make sure that we actually observe changes on this address return i->first; } @@ -1407,7 +1415,7 @@ void Wallet::headerSyncComplete() if (changedOne) { // broadcast to peers our bloom filter, which would have skipped all // time-based blocks before. - rebuildBloom(); + rebuildBloom(ForceBuild); // make the wallet initial sync also show something sane. if (m_segment) m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); @@ -1473,7 +1481,7 @@ void Wallet::createNewPrivateKey(uint32_t currentBlockheight) if (currentBlockheight < 10000000) { // if that is false, its a timestamp: headers are not yet synched) - rebuildBloom(); + rebuildBloom(ForceBuild); } } @@ -1499,7 +1507,7 @@ bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) if (startBlockHeight < 10000000) { // if that is false, its a timestamp: headers are not yet synched) - rebuildBloom(); + rebuildBloom(ForceBuild); m_walletIsImporting = true; } return true; diff --git a/src/Wallet.h b/src/Wallet.h index 0aad95a..7cb167b 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -415,7 +415,12 @@ private: int findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const; /// Fill the bloom filter with all the unspent transactions and addresses we handle. - void rebuildBloom(); + enum RebuildBloomOption { + ForceBuild, + OnlyWhenDirty + }; + + void rebuildBloom(RebuildBloomOption option = OnlyWhenDirty); // returns true if any of the transactions are unknown to the wallet bool anythingNew(int blockHeight, const std::deque &transactions) const; @@ -594,6 +599,7 @@ private: bool parsePassword(const QString &password); bool m_saveStarted = false; bool m_haveEncryptionKey = false; + bool m_utxoDirty = false; // if dirty, rebuildBloom makes sense. uint32_t m_encryptionSeed = 0; uint16_t m_encryptionChecksum = 0; EncryptionLevel m_encryptionLevel = NotEncrypted; -- 2.54.0 From 19008819dc0e68a06c7c620c6c5de920142dc971 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Aug 2023 20:11:02 +0200 Subject: [PATCH 0648/1428] ARS does not use digits behind the separator. --- src/PriceDataProvider.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 67df132..1b00cf7 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -74,7 +74,8 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) } // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") - || m_currency == QLatin1String("NOK")); + || m_currency == QLatin1String("NOK") + || m_currency == QLatin1String("ARS")); if (m_currency == QLatin1String("CHF")) { m_currencySymbolPrefix += QLatin1String(" "); -- 2.54.0 From ac12a688a180205a813da9b399d7424941c41344 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Aug 2023 21:47:36 +0200 Subject: [PATCH 0649/1428] Import translations from Crowedin This enables 2 new languages: Spanish for mobile German for mobile and desktop --- CMakeLists.txt | 10 + translations/desktop-i18n.qrc | 2 + translations/floweepay-common_de.ts | 417 +++++++ translations/floweepay-common_es.ts | 415 +++++++ translations/floweepay-common_nl.ts | 2 +- translations/floweepay-desktop_de.ts | 1146 +++++++++++++++++++ translations/floweepay-desktop_es.ts | 1146 +++++++++++++++++++ translations/floweepay-mobile_de.ts | 1145 ++++++++++++++++++ translations/floweepay-mobile_en.ts | 163 ++- translations/floweepay-mobile_es.ts | 1145 ++++++++++++++++++ translations/floweepay-mobile_nl.ts | 191 ++-- translations/floweepay-mobile_pl.ts | 163 ++- translations/mobile-i18n.qrc | 8 + translations/module-build-transaction_de.ts | 169 +++ translations/module-build-transaction_es.ts | 169 +++ translations/module-peers-view_de.ts | 66 ++ translations/module-peers-view_es.ts | 66 ++ 17 files changed, 6126 insertions(+), 297 deletions(-) create mode 100644 translations/floweepay-common_de.ts create mode 100644 translations/floweepay-common_es.ts create mode 100644 translations/floweepay-desktop_de.ts create mode 100644 translations/floweepay-desktop_es.ts create mode 100644 translations/floweepay-mobile_de.ts create mode 100644 translations/floweepay-mobile_es.ts create mode 100644 translations/module-build-transaction_de.ts create mode 100644 translations/module-build-transaction_es.ts create mode 100644 translations/module-peers-view_de.ts create mode 100644 translations/module-peers-view_es.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index bc1ccd1..eceaaf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,22 +79,32 @@ if(NOT ANDROID) translations/floweepay-desktop_en.ts translations/floweepay-desktop_nl.ts translations/floweepay-desktop_pl.ts + translations/floweepay-desktop_de.ts + translations/floweepay-desktop_es.ts translations/floweepay-common_en.ts translations/floweepay-common_nl.ts translations/floweepay-common_pl.ts + translations/floweepay-common_de.ts + translations/floweepay-common_es.ts translations/floweepay-mobile_en.ts translations/floweepay-mobile_nl.ts translations/floweepay-mobile_pl.ts + translations/floweepay-mobile_de.ts + translations/floweepay-mobile_es.ts translations/module-build-transaction_en.ts translations/module-build-transaction_nl.ts translations/module-build-transaction_pl.ts + translations/module-build-transaction_de.ts + translations/module-build-transaction_es.ts translations/module-peers-view_en.ts translations/module-peers-view_nl.ts translations/module-peers-view_pl.ts + translations/module-peers-view_de.ts + translations/module-peers-view_es.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/desktop-i18n.qrc b/translations/desktop-i18n.qrc index eee19a9..c7eba4b 100644 --- a/translations/desktop-i18n.qrc +++ b/translations/desktop-i18n.qrc @@ -3,8 +3,10 @@ floweepay-desktop_nl.qm floweepay-desktop_pl.qm floweepay-desktop_en.qm + floweepay-desktop_de.qm floweepay-common_nl.qm floweepay-common_pl.qm floweepay-common_en.qm + floweepay-common_de.qm diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts new file mode 100644 index 0000000..ac1e06f --- /dev/null +++ b/translations/floweepay-common_de.ts @@ -0,0 +1,417 @@ + + + + + AccountInfo + + + Offline + Offline + + + + Wallet: Up to date + Geldbörse: Aktuell + + + + Behind: %1 weeks, %2 days + counter on weeks + + Hinterher: %1 Woche, %2 Tage + Hinterher: %1 Wochen, %2 Tage + + + + + Behind: %1 days + + Hinterher: %1 Tag + Hinterher: %1 Tage + + + + + Up to date + Aktuell + + + + Updating + Aktualisierung + + + + Still %1 hours behind + + Noch %1 Stunde hinterher + Noch %1 Stunden hinterher + + + + + AccountTypeLabel + + + This wallet is a single-address wallet. + Diese Geldbörse ist eine Geldbörse mit nur einer Adresse. + + + + This wallet is based on a HD seed-phrase + Diese Geldbörse basiert auf einer HD Seed-Phrase + + + + This wallet is a simple multiple-address wallet. + Diese Geldbörse ist eine einfache Geldbörse mit mehreren Adressen. + + + + AddressInfoWidget + + + self + payment to self + selbst + + + + BroadcastFeedback + + + Sending Payment + Sende Zahlung + + + + Payment Sent + Zahlung gesendet + + + + Transaction rejected by network + Transaktion wurde vom Netzwerk abgelehnt + + + + Add a personal note + Eine persönliche Notiz hinzufügen + + + + Copied TXID to clipboard + TXID in die Zwischenablage kopiert + + + + Opening Website + Öffne Website + + + + Close + Schließen + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + Coin wurde für eine erhöhte Anonymität fusioniert + + + + FloweePay + + + Initial Wallet + Initiale Geldbörse + + + + + Today + Heute + + + + + Yesterday + Gestern + + + + Now + timestamp + Jetzt + + + + %1 minutes ago + relative time stamp + + vor %1 Minute + vor %1 Minuten + + + + + ½ hour ago + timestamp + vor ½ Stunde + + + + %1 hours ago + timestamp + + vor %1 Stunde + vor %1 Stunden + + + + + LabelWithClipboard + + + Copy + Kopieren + + + + MenuModel + + + Explore + Erkunden + + + + Settings + Einstellungen + + + + Security + Sicherheit + + + + About + Über + + + + Wallets + Geldbörsen + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash Block abgebaut. Höhe: %1 + + + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) Block abgebaut: %1 + + + + Mute + Stummschalten + + + + New Transactions + dialog-title + + Neue Transaktion + Neue Transaktionen + + + + + %1 new transactions across %2 wallets found (%3) + %1 neue Transaktionen über %2 Wallets gefunden (%3) + + + + A payment of %1 has been sent + Eine Zahlung von %1 wurde gesendet + + + + %1 new transactions found (%2) + + %1 neue Transaktion gefunden (%2) + %1 neue Transaktionen gefunden (%2) + + + + + Payment + + + Invalid PIN + Ungültige PIN + + + + Not enough funds selected for fees + Nicht genug Guthaben für Gebühren ausgewählt + + + + Not enough funds in wallet to make payment! + Nicht genügend Guthaben in der Geldbörse, um eine Zahlung zu machen! + + + + Transaction too large. Amount selected needs too many coins. + Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. + + + + QRWidget + + + Copied to clipboard + In die Zwischenablage kopiert + + + + Wallet + + + + Change #%1 + Wechselgeld #%1 + + + + + Main #%1 + Haupt #%1 + + + + WalletCoinsModel + + + Unconfirmed + Unbestätigt + + + + %1 hours + age, like: hours old + + %1 Stunde + %1 Stunden + + + + + %1 days + age, like: days old + + %1 Tag + %1 Tage + + + + + %1 weeks + age, like: weeks old + + %1 Woche + %1 Wochen + + + + + %1 months + age, like: months old + + %1 Monat + %1 Monate + + + + + Change #%1 + Wechselgeld #%1 + + + + WalletHistoryModel + + + Today + Heute + + + + Yesterday + Gestern + + + + Earlier this week + Früher in dieser Woche + + + + This week + Diese Woche + + + + Earlier this month + Früher in diesem Monat + + + + WalletSecretsView + + + Explanation + Erklärung + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Coins a / b + a) aktive Coin-Anzahl. + b) historische Coin-Anzahl. + + + + + Copy Address + Adresse kopieren + + + + Copy Private Key + Privaten Schlüssel kopieren + + + + Coins: %1 / %2 + Coins: %1 / %2 + + + + Signed with Schnorr signatures in the past + Signiert mit Schnorr-Signaturen in der Vergangenheit + + + diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts new file mode 100644 index 0000000..e6a6850 --- /dev/null +++ b/translations/floweepay-common_es.ts @@ -0,0 +1,415 @@ + + + + + AccountInfo + + + Offline + Sin conexión + + + + Wallet: Up to date + Monedero: Actualizado + + + + Behind: %1 weeks, %2 days + counter on weeks + + Retraso: %1 semana´s, %2 día´s + Retraso: %1 semanas, %2 días + + + + + Behind: %1 days + + Retraso: %1 día[s] + Retraso: %1 días + + + + + Up to date + Actualizado + + + + Updating + Actualizando + + + + Still %1 hours behind + + Todavía %1 hora[s] de retraso + Todavía %1 horas de retraso + + + + + AccountTypeLabel + + + This wallet is a single-address wallet. + Este es un monedero de dirección única. + + + + This wallet is based on a HD seed-phrase + Este monedero está basado en una frase-semilla HD + + + + This wallet is a simple multiple-address wallet. + Este es un monedero de múltiples-direcciones simple. + + + + AddressInfoWidget + + + self + payment to self + propio + + + + BroadcastFeedback + + + Sending Payment + Enviando Pago + + + + Payment Sent + Pago Enviado + + + + Transaction rejected by network + Transacción rechazada por la red + + + + Add a personal note + Añadir una nota personal + + + + Copied TXID to clipboard + ID de transacción copiada al portapapeles + + + + Opening Website + Abriendo Sitio Web + + + + Close + Cerrar + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + La moneda se ha fusionado para aumentar el anonimato + + + + FloweePay + + + Initial Wallet + Monedero inicial + + + + + Today + Hoy + + + + + Yesterday + Ayer + + + + Now + timestamp + Ahora + + + + %1 minutes ago + relative time stamp + + Hace %1 minuto[s] + Hace %1 minutos + + + + + ½ hour ago + timestamp + Hace ½ hora + + + + %1 hours ago + timestamp + + Hace %1 hora[s] + Hace %1 horas + + + + + LabelWithClipboard + + + Copy + Copiar + + + + MenuModel + + + Explore + Explorar + + + + Settings + Configuraciones + + + + Security + Seguridad + + + + About + Acerca de + + + + Wallets + Monederos + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bloque de Bitcoin Cash minado. Altura: %1 + + + + tBCH (testnet4) block mined: %1 + Bloque de tBCH (testnet4) minado: %1 + + + + Mute + Silenciar + + + + New Transactions + dialog-title + + Nuevas transacciones + New Transactions + + + + + %1 new transactions across %2 wallets found (%3) + %1 nuevas transacciones entre %2 monederos encontradas (%3) + + + + A payment of %1 has been sent + Un pago de %1 ha sido enviado + + + + %1 new transactions found (%2) + + %1 nuevas transacciones encontradas (%2) + %1 nuevas transacciones encontradas (%2) + + + + + Payment + + + Invalid PIN + PIN inválido + + + + Not enough funds selected for fees + No se han seleccionado suficientes fondos para cubrir la comisión + + + + Not enough funds in wallet to make payment! + ¡El monedero no posee suficientes fondos para procesar el pago! + + + + Transaction too large. Amount selected needs too many coins. + Transacción demasiado larga. La cantidad seleccionada necesita demasiadas monedas. + + + + QRWidget + + + Copied to clipboard + Copiado al portapapeles + + + + Wallet + + + + Change #%1 + Cambio #%1 + + + + + Main #%1 + Principal #%1 + + + + WalletCoinsModel + + + Unconfirmed + Sin confirmar + + + + %1 hours + age, like: hours old + + %1 hora[s] + %1 horas + + + + + %1 days + age, like: days old + + %1 día[s] + %1 días + + + + + %1 weeks + age, like: weeks old + + %1 semana[s] + %1 semanas + + + + + %1 months + age, like: months old + + %1 mes[es] + %1 meses + + + + + Change #%1 + Cambio #%1 + + + + WalletHistoryModel + + + Today + Hoy + + + + Yesterday + Ayer + + + + Earlier this week + Anteriormente en esta semana + + + + This week + Esta semana + + + + Earlier this month + Anteriormente este mes + + + + WalletSecretsView + + + Explanation + Explicación + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Monedas a / b a) cantidad de monedas en posesión. b) historial de cantidad de monedas. + + + + + Copy Address + Copiar la dirección + + + + Copy Private Key + Copiar clave privada + + + + Coins: %1 / %2 + Monedas: %1 / %2 + + + + Signed with Schnorr signatures in the past + Firmado con firmas de Schnorr en el pasado + + + diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index a01647e..df9cd72 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -195,7 +195,7 @@ Security - Security + Beveiliging diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts new file mode 100644 index 0000000..cbae65b --- /dev/null +++ b/translations/floweepay-desktop_de.ts @@ -0,0 +1,1146 @@ + + + + + AccountConfigMenu + + + Details + Details + + + + Unarchive + Entarchivieren + + + + Archive Wallet + Geldbörse archivieren + + + + Make Primary + Primär setzen + + + + ★ Primary + ★ Primär + + + + Protect With Pin... + Mit Pin schützen... + + + + Open + Open encrypted wallet + Öffnen + + + + Close + Close encrypted wallet + Schließen + + + + AccountDetails + + + Wallet Details + Geldbörsen Details + + + + Name + Name + + + + Sync Status + Sync Status + + + + Encryption + Verschlüsselung + + + + Password + Passwort + + + + Open + Öffnen + + + + Invalid PIN + Ungültige PIN + + + + Include balance in total + Inkludiere Guthaben in Gesamtsumme + + + + Hide in private mode + Im privaten Modus ausblenden + + + + Address List + Adressliste + + + + Change Addresses + Wechselgeldadressen + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. + + + + Used Addresses + Benutzte Adressen + + + + Switches between unused and used Bitcoin addresses + Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen + + + + Backup details + Backup-Details + + + + Seed-phrase + Seed-Phrase + + + + Derivation + Ableitung + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. + + + + AccountListItem + + + Last Transaction + Letzte Transaktion + + + + NetView + + + Peers (%1) + + Peers (%1) + Peers (%1) + + + + + Address + network address (IP) + Adresse + + + + Start-height: %1 + Starthöhe: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + Initialisiere Verbindung + + + + Verifying peer + Überprüfe Peer + + + + Assigning... + Zuweisen... + + + + Peer for wallet: %1 + Peer für Geldbörse: %1 + + + + Peer for initial wallet + Peer für die erste Geldbörse + + + + Close + Schließen + + + + NewAccountCreateBasicAccount + + + This creates a new empty wallet with simple multi-address capability + Dies erzeugt eine neue leere Geldbörse mit einfacher Mehradressen-Fähigkeit + + + + Name + Name + + + + Go + Los + + + + Advanced Options + Erweiterte Optionen + + + + Force Single Address + Erzwinge einzelne Adresse + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Wenn aktiviert, wird diese Geldbörse auf eine Adresse beschränkt. +Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss + + + + NewAccountCreateHDAccount + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + Dies erzeugt eine neue leere Geldbörse mit intelligenter Erstellung von Adressen aus einer einzigen Seed-Phrase + + + + Name + Name + + + + Go + Los + + + + Advanced Options + Erweiterte Optionen + + + + Derivation + Ableitung + + + + NewAccountImportAccount + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. + + + + Secret + The seed-phrase or private key + Geheimnis + + + + Example: %1 + placeholder text + Beispiel: %1 + + + + Name + Name + + + + Private key + description of type + Privater Schlüssel + + + + BIP 39 seed-phrase + description of type + BIP 39 Seed-Phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unerkanntes Wort + + + + Import wallet + Geldbörse importieren + + + + Advanced Options + Erweiterte Optionen + + + + Force Single Address + Erzwinge einzelne Adresse + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. +Wechselgeld wird zum importierten Schlüssel zurückgeführt. + + + + Start Height + Starthöhe + + + + Derivation + Ableitung + + + + Alternate phrase + Alternative Phrase + + + + NewAccountPane + + + New Bitcoin Cash Wallet + Neue Bitcoin Cash Geldbörse + + + + Basic + Einfach + + + + Private keys based + Property of a wallet + Private Schlüssel basiert + + + + Difficult to backup + Context: wallet type + Schwierig zu sichern + + + + Great for brief usage + Context: wallet type + Ideal für kurze Nutzung + + + + HD wallet + HD Geldbörse + + + + Seed-phrase based + Context: wallet type + Seed-Phrase basiert + + + + Easy to backup + Context: wallet type + Einfach zu sichern + + + + Most compatible + The most compatible wallet type + Am kompatibelsten + + + + Import + Import + + + + Imports seed-phrase + Importiert Seed-Phrase + + + + Imports private key + Importiert privaten Schlüssel + + + + Basic wallet with private keys + Einfache Geldbörse mit privaten Schlüsseln + + + + Seed-phrase wallet + Seed-Phrase Geldbörse + + + + Import of an existing wallet + Import einer vorhandenen Geldbörse + + + + PaymentTweakingPanel + + + Add Detail + Detail hinzufügen + + + + Coin Selector + Coin Selektor + + + + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + Um die Standardauswahl von Coins zu überschreiben, die zur Zahlung einer Transaktion verwendet werden, können Sie den 'Coin Selektor' hinzufügen, in dem die Coins der Geldbörse sichtbar gemacht werden. + + + + ReceiveTransactionPane + + + Share your QR code or copy address to receive + Teilen Sie Ihren QR-Code oder kopieren Sie Ihre Adresse zum Empfang + + + + Encrypted Wallet + Verschlüsselte Geldbörse + + + + Import Running... + Import läuft... + + + + Checking + Überprüfe + + + + Transaction high risk + Hoch-Risiko Transaktion + + + + Payment Seen + Zahlung gesichtet + + + + Payment Accepted + Zahlung akzeptiert + + + + Payment Settled + Zahlung abgeschlossen + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) + + + + Description + Beschreibung + + + + Amount + Betrag + + + + Clear + Leeren + + + + Done + Erledigt + + + + SendTransactionPane + + + Confirm delete + Löschen bestätigen + + + + Do you really want to delete this detail? + Möchten Sie dieses Detail wirklich löschen? + + + + Add Destination + Ziel hinzufügen + + + + Prepare + Vorbereiten + + + + Enter your PIN + Geben Sie Ihre PIN ein + + + + Transaction Details + Transaktionsdetails + + + + Not prepared yet + Noch nicht vorbereitet + + + + Copy transaction-ID + Transaktions-ID kopieren + + + + Fee + Gebühr + + + + Transaction size + Transaktionsgröße + + + + %1 bytes + %1 Bytes + + + + Fee per byte + Gebühr pro Byte + + + + %1 sat/byte + fee + %1 Sat/Byte + + + + Send + Senden + + + + Destination + Ziel + + + + Max available + The maximum balance available + Max. verfügbar + + + + %1 to %2 + summary text to pay X-euro to address M + %1 zu %2 + + + + Enter Bitcoin Cash Address + Geben Sie eine Bitcoin Cash Adresse ein + + + + Copy Address + Adresse kopieren + + + + Amount + Betrag + + + + Max + Max + + + + Warning + Warnung + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? + + + + Continue + Fortfahren + + + + Cancel + Abbrechen + + + + Coin Selector + Coin Selektor + + + + Selected %1 %2 in %3 coins + selected 2 BCH in 5 coins + + %1 %2 in %3 Coins ausgewählt + %1 %2 in %3 Coins ausgewählt + + + + + Total + Number of coins + Gesamt + + + + Needed + Benötigt + + + + Selected + Ausgewählt + + + + Value + Wert + + + + Locked coins will never be used for payments. Right-click for menu. + Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. + + + + Age + Alter + + + + Unselect All + Alles abwählen + + + + Select All + Alles auswählen + + + + Unlock coin + Coin entsperren + + + + Lock coin + Coin sperren + + + + SettingsPane + + + Settings + Einstellungen + + + + Unit + Einheit + + + + Show Bitcoin Cash value on Activity page + Bitcoin Cash Wert auf der Aktivitätsseite anzeigen + + + + Show Block Notifications + Zeige Blockbenachrichtigungen + + + + When a new block is mined, Flowee Pay shows a desktop notification + Wenn ein neuer Block erzeugt wird, zeigt Flowee Pay eine Desktop-Benachrichtigung + + + + Night Mode + Nachtmodus + + + + Private Mode + Privater Modus + + + + Hides private wallets while enabled + Versteckt private Geldbörsen, wenn aktiviert + + + + Version + Version + + + + Library Version + Version der Bibliothek + + + + Synchronization + Synchronisation + + + + Network Status + Netzwerkstatus + + + + WalletEncryption + + + Protect your wallet with a password + Schützen Sie Ihre Geldbörse mit einem Passwort + + + + Pin to Pay + Pin um zu bezahlen + + + + Protect your funds + pin to pay + Schützen Sie Ihr Guthaben + + + + Fully open, except for sending funds + pin to pay + Vollständig geöffnet, außer für das Senden von Guthaben + + + + Keeps in sync + pin to pay + Bleibt synchron + + + + Pin to Open + Pin zum Öffnen + + + + Protect your entire wallet + pin to open + Schützen Sie Ihre gesamte Geldbörse + + + + Balance and history protected + pin to open + Guthaben und Verlauf geschützt + + + + Requires Pin to view, sync or pay + pin to open + Benötigt Pin zum Ansehen, Synchronisieren oder Bezahlen + + + + Make "%1" wallet require a pin to pay + Erfordere einen Pin zum Bezahlen für "%1" Geldbörse + + + + Make "%1" wallet require a pin to open + Erfordere einen Pin zum Öffnen für "%1" Geldbörse + + + + Wallet already has pin to pay applied + Geldbörse hat bereits eine PIN zum Bezahlen + + + + + Wallet already has pin to open applied + Geldbörse hat bereits eine PIN zum Öffnen an + + + + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Ihre Geldbörse wird teilweise verschlüsselt und Zahlungen werden ohne Passwort nicht möglich. Wenn Sie über kein Backup dieser Geldbörse verfügen, erstellen Sie zuerst eines. + + + + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Ihre vollständige Geldbörse wird verschlüsselt, um sie zu öffnen, wird ein Passwort benötigen. Wenn Sie über kein Backup dieser Geldbörse verfügen, erstellen Sie zuerst eines. + + + + Password + Passwort + + + + Wallet + Geldbörse + + + + Encrypt + Verschlüsseln + + + + Invalid password to open this wallet + Ungültiges Passwort zum Öffnen dieser Geldbörse + + + + Close + Schließen + + + + Repeat password + Passwort wiederholen + + + + Please confirm the password by entering it again + Bitte bestätigen Sie Ihr Passwort, indem Sie es erneut eingeben + + + + Typed passwords do not match + Die eingegebenen Passwörter stimmen nicht überein + + + + WalletEncryptionStatus + + + Pin to Pay + Pin um zu bezahlen + + + + Pin to Open + Pin zum Öffnen + + + + (Opened) + Wallet is decrypted + (Geöffnet) + + + + WalletTransaction + + + Miner Reward + Miner Belohnung + + + + Cash Fusion + Cash Fusion + + + + Received + Empfangen + + + + Moved + Verschoben + + + + Sent + Gesendet + + + + rejected + abgelehnt + + + + unconfirmed + unbestätigt + + + + WalletTransactionDetails + + + Copy transaction-ID + Transaktions-ID kopieren + + + + Status + Status + + + + rejected + Abgelehnt + + + + unconfirmed + Unbestätigt + + + + %1 confirmations (mined in block %2) + + %1 Bestätigungen (im Block %2) + %1 Bestätigungen (im Block %2) + + + + + Copy block height + Kopiere Blockhöhe + + + + Fees + Gebühren + + + + Size + Größe + + + + %1 bytes + + %1 Bytes + %1 Bytes + + + + + Inputs + Eingaben + + + + + Copy Address + Adresse kopieren + + + + Outputs + Ausgaben + + + + main + + + Activity + Aktivität + + + + Archived wallets do not check for activities. Balance may be out of date. + Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. + + + + Unarchive + Entarchivieren + + + + This wallet needs a password to open. + Diese Geldbörse benötigt ein Passwort zum Öffnen. + + + + Password: + Passwort: + + + + Invalid password + Ungültiges Passwort + + + + Open + Öffnen + + + + Send + Senden + + + + Receive + Empfangen + + + + Balance + Guthaben + + + + Main + balance (money), non specified + Haupt + + + + Unconfirmed + balance (money) + Unbestätigt + + + + Immature + balance (money) + Immature + + + + 1 BCH is: %1 + 1 BCH ist: %1 + + + + Network status + Netzwerkstatus + + + + Offline + Offline + + + + Add Bitcoin Cash wallet + Bitcoin Cash-Geldbörse hinzufügen + + + + Archived wallets [%1] + Arg is wallet count + + Archivierte Geldbörsen [%1] + Archivierte Geldbörsen [%1] + + + + + Preparing... + Wird vorbereitet... + + + diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts new file mode 100644 index 0000000..f624a0c --- /dev/null +++ b/translations/floweepay-desktop_es.ts @@ -0,0 +1,1146 @@ + + + + + AccountConfigMenu + + + Details + Detalles + + + + Unarchive + Desarchivar + + + + Archive Wallet + Archivar monedero + + + + Make Primary + Hacer principal + + + + ★ Primary + ★ Principal + + + + Protect With Pin... + Proteger con Pin... + + + + Open + Open encrypted wallet + Abrir + + + + Close + Close encrypted wallet + Cerrar + + + + AccountDetails + + + Wallet Details + Detalles del monedero + + + + Name + Nombre + + + + Sync Status + Estado de la sincronización + + + + Encryption + Cifrado + + + + Password + Contraseña + + + + Open + Abrir + + + + Invalid PIN + PIN inválido + + + + Include balance in total + Incluir en el balance total + + + + Hide in private mode + Ocultar en modo privado + + + + Address List + Lista de direcciones + + + + Change Addresses + Cambiar direcciones + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Alterna entre direcciones donde otros pueden pagarte y direcciones que el monedero usa para enviarte el cambio de vuelta. + + + + Used Addresses + Direcciones usadas + + + + Switches between unused and used Bitcoin addresses + Alterna entre direcciones no usadas y usadas de Bitcoin + + + + Backup details + Detalles de la copia de seguridad + + + + Seed-phrase + Frase semilla + + + + Derivation + Derivación + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. + + + + AccountListItem + + + Last Transaction + Última transacción + + + + NetView + + + Peers (%1) + + Pares (%1) + Peers (%1) + + + + + Address + network address (IP) + Dirección + + + + Start-height: %1 + Altura de inicio: %1 + + + + ban-score: %1 + calificación de no confiabilidad: %1 + + + + initializing connection + Inicializando conexión + + + + Verifying peer + Verificando par + + + + Assigning... + Asignando... + + + + Peer for wallet: %1 + Par para el monedero: %1 + + + + Peer for initial wallet + Par para la cartera inicial + + + + Close + Cerrar + + + + NewAccountCreateBasicAccount + + + This creates a new empty wallet with simple multi-address capability + Esto crea un nuevo monedero vacío con capacidad de multi-dirección simple + + + + Name + Nombre + + + + Go + Ir + + + + Advanced Options + Opciones Avanzadas + + + + Force Single Address + Forzar dirección única + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Cuando está habilitado, este monedero se limitará a una dirección. +Esto asegura que solo una clave privada tendrá que ser respaldada + + + + NewAccountCreateHDAccount + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + Esto crea un nuevo monedero vacío con la creación inteligente de direcciones a partir de una sola frase de semilla + + + + Name + Nombre + + + + Go + Ir + + + + Advanced Options + Opciones Avanzadas + + + + Derivation + Derivación + + + + NewAccountImportAccount + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. + + + + Secret + The seed-phrase or private key + Secreto + + + + Example: %1 + placeholder text + Ejemplo: %1 + + + + Name + Nombre + + + + Private key + description of type + Llave privada + + + + BIP 39 seed-phrase + description of type + Frase semilla BIP 39 + + + + Unrecognized word + Word from the seed-phrases lexicon + Palabra no reconocida + + + + Import wallet + Importar monedero + + + + Advanced Options + Opciones Avanzadas + + + + Force Single Address + Forzar dirección única + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. +El cambio volverá a la clave importada. + + + + Start Height + Altura de inicio + + + + Derivation + Derivación + + + + Alternate phrase + Frase alternativa + + + + NewAccountPane + + + New Bitcoin Cash Wallet + Nuevo monedero Bitcoin Cash + + + + Basic + Básico + + + + Private keys based + Property of a wallet + Basado en Claves privadas + + + + Difficult to backup + Context: wallet type + Difícil de respaldar + + + + Great for brief usage + Context: wallet type + Ideal para un uso breve + + + + HD wallet + Monedero HD + + + + Seed-phrase based + Context: wallet type + Basado en frase semilla + + + + Easy to backup + Context: wallet type + Fácil de respaldar + + + + Most compatible + The most compatible wallet type + El más compatible + + + + Import + Importar + + + + Imports seed-phrase + Importa la frase semilla + + + + Imports private key + Importa la clave privada + + + + Basic wallet with private keys + Basic wallet with private keys + + + + Seed-phrase wallet + Seed-phrase wallet + + + + Import of an existing wallet + Import of an existing wallet + + + + PaymentTweakingPanel + + + Add Detail + Add Detail + + + + Coin Selector + Coin Selector + + + + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + + + + ReceiveTransactionPane + + + Share your QR code or copy address to receive + Share your QR code or copy address to receive + + + + Encrypted Wallet + Encrypted Wallet + + + + Import Running... + Import Running... + + + + Checking + Checking + + + + Transaction high risk + Transaction high risk + + + + Payment Seen + Payment Seen + + + + Payment Accepted + Payment Accepted + + + + Payment Settled + Payment Settled + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + + Description + Description + + + + Amount + Amount + + + + Clear + Clear + + + + Done + Done + + + + SendTransactionPane + + + Confirm delete + Confirm delete + + + + Do you really want to delete this detail? + Do you really want to delete this detail? + + + + Add Destination + Add Destination + + + + Prepare + Prepare + + + + Enter your PIN + Enter your PIN + + + + Transaction Details + Transaction Details + + + + Not prepared yet + Not prepared yet + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Fee + + + + Transaction size + Transaction size + + + + %1 bytes + %1 bytes + + + + Fee per byte + Fee per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Send + Send + + + + Destination + Destination + + + + Max available + The maximum balance available + Max available + + + + %1 to %2 + summary text to pay X-euro to address M + %1 to %2 + + + + Enter Bitcoin Cash Address + Enter Bitcoin Cash Address + + + + Copy Address + Copy Address + + + + Amount + Amount + + + + Max + Max + + + + Warning + Warning + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + Continue + Continue + + + + Cancel + Cancel + + + + Coin Selector + Coin Selector + + + + Selected %1 %2 in %3 coins + selected 2 BCH in 5 coins + + Selected %1 %2 in %3 coins + Selected %1 %2 in %3 coins + + + + + Total + Number of coins + Total + + + + Needed + Needed + + + + Selected + Selected + + + + Value + Value + + + + Locked coins will never be used for payments. Right-click for menu. + Locked coins will never be used for payments. Right-click for menu. + + + + Age + Age + + + + Unselect All + Unselect All + + + + Select All + Select All + + + + Unlock coin + Unlock coin + + + + Lock coin + Lock coin + + + + SettingsPane + + + Settings + Settings + + + + Unit + Unit + + + + Show Bitcoin Cash value on Activity page + Show Bitcoin Cash value on Activity page + + + + Show Block Notifications + Show Block Notifications + + + + When a new block is mined, Flowee Pay shows a desktop notification + When a new block is mined, Flowee Pay shows a desktop notification + + + + Night Mode + Night Mode + + + + Private Mode + Private Mode + + + + Hides private wallets while enabled + Hides private wallets while enabled + + + + Version + Version + + + + Library Version + Library Version + + + + Synchronization + Synchronization + + + + Network Status + Network Status + + + + WalletEncryption + + + Protect your wallet with a password + Protect your wallet with a password + + + + Pin to Pay + Pin to Pay + + + + Protect your funds + pin to pay + Protect your funds + + + + Fully open, except for sending funds + pin to pay + Fully open, except for sending funds + + + + Keeps in sync + pin to pay + Keeps in sync + + + + Pin to Open + Pin to Open + + + + Protect your entire wallet + pin to open + Protect your entire wallet + + + + Balance and history protected + pin to open + Balance and history protected + + + + Requires Pin to view, sync or pay + pin to open + Requires Pin to view, sync or pay + + + + Make "%1" wallet require a pin to pay + Make "%1" wallet require a pin to pay + + + + Make "%1" wallet require a pin to open + Make "%1" wallet require a pin to open + + + + Wallet already has pin to pay applied + Wallet already has pin to pay applied + + + + + Wallet already has pin to open applied + Wallet already has pin to open applied + + + + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + + + + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + + + + Password + Password + + + + Wallet + Wallet + + + + Encrypt + Encrypt + + + + Invalid password to open this wallet + Invalid password to open this wallet + + + + Close + Close + + + + Repeat password + Repeat password + + + + Please confirm the password by entering it again + Please confirm the password by entering it again + + + + Typed passwords do not match + Typed passwords do not match + + + + WalletEncryptionStatus + + + Pin to Pay + Pin to Pay + + + + Pin to Open + Pin to Open + + + + (Opened) + Wallet is decrypted + (Opened) + + + + WalletTransaction + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + WalletTransactionDetails + + + Copy transaction-ID + Copy transaction-ID + + + + Status + Status + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + %1 confirmations (mined in block %2) + + %1 confirmations (mined in block %2) + %1 confirmations (mined in block %2) + + + + + Copy block height + Copy block height + + + + Fees + Fees + + + + Size + Size + + + + %1 bytes + + %1 bytes + %1 bytes + + + + + Inputs + Inputs + + + + + Copy Address + Copy Address + + + + Outputs + Outputs + + + + main + + + Activity + Activity + + + + Archived wallets do not check for activities. Balance may be out of date. + Archived wallets do not check for activities. Balance may be out of date. + + + + Unarchive + Unarchive + + + + This wallet needs a password to open. + This wallet needs a password to open. + + + + Password: + Password: + + + + Invalid password + Invalid password + + + + Open + Open + + + + Send + Send + + + + Receive + Receive + + + + Balance + Balance + + + + Main + balance (money), non specified + Main + + + + Unconfirmed + balance (money) + Unconfirmed + + + + Immature + balance (money) + Immature + + + + 1 BCH is: %1 + 1 BCH is: %1 + + + + Network status + Network status + + + + Offline + Offline + + + + Add Bitcoin Cash wallet + Add Bitcoin Cash wallet + + + + Archived wallets [%1] + Arg is wallet count + + Archived wallets [%1] + Archived wallets [%1] + + + + + Preparing... + Preparing... + + + diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts new file mode 100644 index 0000000..f5d3f9b --- /dev/null +++ b/translations/floweepay-mobile_de.ts @@ -0,0 +1,1145 @@ + + + + + About + + + About + Über + + + + Help translate this app + Hilf mit, diese App zu übersetzen + + + + License + Lizenz + + + + + Credits + Mitwirkende + + + + © 2020-2023 Tom Zander and contributors + ©️ 2020-2023 Tom Zander und Mitwirkende + + + + Project Home + Projekt-Startseite + + + + With git repository and issues tracker + Mit git Repository und Issue Tracker + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Startseite + + + + Pay + Bezahlen + + + + Receive + Empfange + + + + Miner Reward + Miner Belohnung + + + + Cash Fusion + Cash Fusion + + + + Received + Empfangen + + + + Moved + Verschoben + + + + Sent + Gesendet + + + + Sending + Sende + + + + Seen + Gesehen + + + + Rejected + Abgelehnt + + + + AccountPageListItem + + + Name + Name + + + + Archived wallets do not check for activities. Balance may be out of date + Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein + + + + Backup information + Backup-Informationen + + + + Backup Details + Backup-Details + + + + Wallet seed-phrase + Geldbörsen-Seed-Phrase + + + + Starting Height + height refers to block-height + Starthöhe + + + + Derivation Path + Ableitungspfad + + + + xpub + xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! + + + + Wallet keys + Geldbörsen-Schlüssel + + + + Show Index + toggle to show numbers + Index anzeigen + + + + Change Addresses + Wechselgeldadressen + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. + + + + Used Addresses + Benutzte Adressen + + + + Switches between still in use addresses and formerly used, new empty, addresses + Wechselt zwischen noch benutzten Adressen und früher genutzten, neuen leeren Adressen + + + + Addresses and keys + Adressen und Schlüssel + + + + Sync Status + Synchronisierungsstatus + + + + Hide balance in overviews + Guthaben in Übersichten ausblenden + + + + Hide in private mode + Im privaten Modus ausblenden + + + + Unarchive Wallet + Geldbörse entarchivieren + + + + Archive Wallet + Geldbörse archivieren + + + + AccountSelectorPopup + + + Your Wallets + Ihre Geldbörsen + + + + last active + zuletzt aktiv + + + + Needs PIN to open + Benötigt PIN zum Öffnen + + + + Balance Total + Gesamtguthaben + + + + AccountSyncState + + + Status: Offline + Status: Offline + + + + AccountsList + + + Wallet + Geldbörse + + + + Wallets + Geldbörsen + + + + Add Wallet + Geldbörse hinzufügen + + + + Default Wallet + Standard-Geldbörse + + + + %1 is used on startup + %1 wird beim Starten verwendet + + + + Exit Private Mode + Privaten Modus beenden + + + + Enter Private Mode + Privaten Modus aktivieren + + + + Reveals wallets marked private + Zeigt als privat markierte Geldbörsen + + + + Hides wallets marked private + Versteckt als privat markierte Geldbörsen + + + + CurrencySelector + + + Select Currency + Währung auswählen + + + + ExploreModules + + + Explore + Erkunden + + + + ON + Enabled. SHORT TEXT! + ON + + + + GuiSettings + + + Display Settings + Anzeigeeinstellungen + + + + Font sizing + Schriftgröße + + + + Unit + Einheit + + + + Change Currency (%1) + Währung ändern (%1) + + + + Main View + Hauptansicht + + + + Show Bitcoin Cash value + Zeige Bitcoin Cash Wert + + + + ImportWalletPage + + + Import Wallet + Geldbörse importieren + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. + + + + Secret + The seed-phrase or private key + Geheimnis + + + + Private key + description of type + Privater Schlüssel + + + + BIP 39 seed-phrase + description of type + BIP 39 Seed-Phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unerkanntes Wort + + + + Name + Name + + + + Force Single Address + Erzwinge einzelne Adresse + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. +Wechselgeld wird zum importierten Schlüssel zurückgeführt. + + + + Oldest Transaction + Älteste Transaktion + + + + Derivation + Ableitung + + + + Alternate phrase + Alternative Phrase + + + + Create + Erstelle + + + + InstaPayConfigButton + + + Enable Instant Pay + Sofortzahlung aktivieren + + + + Configure Instant Pay + Sofortzahlung konfigurieren + + + + Fast payments for low amounts + Schnelle Zahlungen für niedrige Beträge + + + + Not configured + Nicht konfiguriert + + + + Limit set to: %1 + Limit gesetzt auf: %1 + + + + InstaPayConfigPage + + + Instant Pay + Sofortzahlung + + + + Requests for payment can be approved automatically using Instant Pay. + Zahlungsanfragen können automatisch mit Sofortzahlung bewilligt werden. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Geschützte Geldbörsen können nicht für Sofortzahlung verwendet werden, da sie vor der Nutzung eine PIN benötigen + + + + Enable Instant Pay for this wallet + Sofortzahlung für diese Geldbörse aktivieren + + + + Maximum Amount + Höchstbetrag + + + + LockApplication + + + Security + Sicherheit + + + + PIN on startup + PIN beim Start + + + + Enter current PIN + Aktuelle PIN eintragen + + + + Enter new PIN + Geben Sie eine neue PIN ein + + + + Repeat PIN + PIN wiederholen + + + + Remove PIN + PIN entfernen + + + + Set PIN + PIN setzen + + + + PIN has been removed + PIN wurde entfernt + + + + PIN has been set + PIN wurde gesetzt + + + + Ok + Ok + + + + MenuOverlay + + + Add Wallet + Geldbörse hinzufügen + + + + NewAccount + + + New Bitcoin Cash Wallet + Neue Bitcoin Cash Geldbörse + + + + Create a New Wallet + Erstelle eine neue Geldbörse + + + + HD wallet + HD Geldbörse + + + + Seed-phrase based + Context: wallet type + Seed-Phrase basiert + + + + Easy to backup + Context: wallet type + Einfach zu sichern + + + + Most compatible + The most compatible wallet type + Am kompatibelsten + + + + Basic + Einfach + + + + Private keys based + Property of a wallet + Private Schlüssel basiert + + + + Difficult to backup + Context: wallet type + Schwierig zu sichern + + + + Great for brief usage + Context: wallet type + Ideal für kurze Nutzung + + + + Import Existing Wallet + Import einer vorhandenen Geldbörse + + + + Import + Import + + + + Imports seed-phrase + Importiert Seed-Phrase + + + + Imports private key + Importiert privaten Schlüssel + + + + New Wallet + Neue Geldbörse + + + + This creates a new empty wallet with simple multi-address capability + Dies erzeugt eine neue leere Geldbörse mit einfacher Mehradressen-Fähigkeit + + + + + Name + Name + + + + Force Single Address + Erzwinge einzelne Adresse + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Wenn aktiviert, wird diese Geldbörse auf eine Adresse beschränkt. +Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss + + + + + Create + Erstelle + + + + New HD-Wallet + Neue HD-Geldbörse + + + + This creates a new wallet that can be backed up with a seed-phrase + Dies erzeugt eine neue Geldbörse, die mit einer Seed-Phrase gesichert werden kann + + + + Derivation + Ableitung + + + + PayWithQR + + + Approve Payment + Zahlung genehmigen + + + + Send All + all money in wallet + Alles senden + + + + Show Address + to show a bitcoincash address + Adresse anzeigen + + + + Edit Amount + Edit amount of money to send + Betrag bearbeiten + + + + Invalid QR code + Ungültiger QR Code + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Gescannter Text: <pre>%1</pre> + + + + Payment description + Zahlungsbeschreibung + + + + Destination Address + Zieladresse + + + + Unlock Wallet + Geldbörse entsperren + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH ist: %1 + + + + 7d + 7 days + 7T + + + + 1m + 1 month + 1M + + + + 3m + 3 months + 3M + + + + PriceInputWidget + + + All Currencies + Alle Währungen + + + + QRScannerOverlay + + + Paste + Einfügen + + + + Failed + Fehlgeschlagen + + + + ReceiveTab + + + Receive + Empfange + + + + Share this QR to receive + Diesen QR teilen um zu empfangen + + + + Encrypted Wallet + Verschlüsselte Geldbörse + + + + Import Running... + Import läuft... + + + + + Description + Beschreibung + + + + Amount + requested amount of coin + Betrag + + + + Address + Bitcoin Cash address + Adresse + + + + Clear + Leeren + + + + + Payment Seen + Zahlung gesichtet + + + + Checking... + Überprüfe... + + + + Transaction high risk + Hoch-Risiko Transaktion + + + + Partially Paid + Teilweise bezahlt + + + + Payment Accepted + Zahlung akzeptiert + + + + Payment Settled + Zahlung abgeschlossen + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) + + + + Continue + Fortfahren + + + + SelectDefaultAccountPage + + + Select Wallet + Geldbörse auswählen + + + + Pick which wallet will be selected on starting Flowee Pay + Wählen Sie aus, welche Geldbörse beim Start von Flowee Pay ausgewählt wird + + + + No InstaPay limit set + Kein Sofortzahlungslimit gesetzt + + + + InstaPay till %1 + Sofortzahlung bis %1 + + + + InstaPay is turned off + Sofortzahlung ist deaktiviert + + + + SendTransactionsTab + + + Send + Senden + + + + Start Payment + Starte Zahlung + + + + SlideToApprove + + + SLIDE TO SEND + ZUM SENDEN SCHIEBEN + + + + StartupScreen + + + Welcome! + Willkommen! + + + + Continue + Fortfahren + + + + Moving the world towards a Bitcoin Cash economy + Bewege die Welt in Richtung einer Bitcoin Cash Wirtschaft + + + + Scan me to send funds to your HD wallet + Scanne mich, um Geld an deine HD-Geldbörse zu senden + + + + OR + OR + + + + Add a different wallet + Eine andere Geldbörse hinzufügen + + + + TransactionDetails + + + Transaction Details + Transaktionsdetails + + + + Transaction Hash + Transaktions-Hash + + + + Rejected + Abgelehnt + + + + Unconfirmed + Unbestätigt + + + + Mined at + Abgebaut am + + + + %1 blocks ago + + %1 Block her + %1 Blöcke her + + + + + Transaction comment + Transaktionskommentar + + + + Size: %1 bytes + Größe: %1 Bytes + + + + Coinbase + Coinbase + + + + Is a CashFusion transaction. + Ist eine CashFusion Transaktion. + + + + Fees paid + Bezahlte Gebühren + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 Bytes + + + + Fused from my addresses + Fusioniert von meinen Adressen + + + + Sent from my addresses + Gesendet von meinen Adressen + + + + Sent from addresses + Gesendet von den Adressen + + + + + Copy Address + Adresse kopieren + + + + Fused into my addresses + Fusioniert in meine Adressen + + + + Received at addresses + Empfangen auf den Adressen + + + + Received at my addresses + Empfangen auf meinen Adressen + + + + TxInfoSmall + + + Transaction is rejected + Transaktion ist abgelehnt + + + + Processing + Verarbeite + + + + Mined + Abgebaut + + + + %1 blocks ago + Confirmations + + %1 Block her + %1 Blöcke her + + + + + Miner Reward + Miner Belohnung + + + + Cash Fusion + Cash Fusion + + + + Received + Empfangen + + + + Payment to self + Zahlung an sich selbst + + + + Sent + Gesendet + + + + Holds a token + Hält ein Token + + + + Sent to + Gesendet an + + + + Value now + Wert jetzt + + + + Value then + Wert zuvor + + + + Transaction Details + Transaktionsdetails + + + + UnlockWidget + + + Enter your wallet passcode + Geben Sie Ihren Geldbörsen-Zugangscode ein + + + + Open + open wallet with PIN + Öffnen + + + diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 5ea8027..84b0fe9 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -9,38 +9,38 @@ About - + Help translate this app Help translate this app - + License License - - + + Credits Credits - + © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander and contributors - + Project Home Project Home - + With git repository and issues tracker With git repository and issues tracker - + Telegram Telegram @@ -168,52 +168,52 @@ Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between still in use addresses and formerly used, new empty, addresses Switches between still in use addresses and formerly used, new empty, addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet @@ -313,7 +313,7 @@ Explore - + ON Enabled. SHORT TEXT! ON @@ -347,7 +347,7 @@ Main View - + Show Bitcoin Cash value Show Bitcoin Cash value @@ -411,17 +411,17 @@ Change will come back to the imported key. Oldest Transaction - + Derivation Derivation - + Alternate phrase Alternate phrase - + Create Create @@ -530,32 +530,7 @@ Change will come back to the imported key. PIN has been set - - Encrypt your wallet data - Encrypt your wallet data - - - - Require PIN to Pay on wallet - Require PIN to Pay on wallet - - - - Secure against spending - Secure against spending - - - - Require PIN to Open wallet - Require PIN to Open wallet - - - - Secure against all usage - Secure against all usage - - - + Ok Ok @@ -571,93 +546,93 @@ Change will come back to the imported key. NewAccount - + New Bitcoin Cash Wallet New Bitcoin Cash Wallet - + Create a New Wallet Create a New Wallet - + HD wallet HD wallet - + Seed-phrase based Context: wallet type Seed-phrase based - + Easy to backup Context: wallet type Easy to backup - + Most compatible The most compatible wallet type Most compatible - + Basic Basic - + Private keys based Property of a wallet Private keys based - + Difficult to backup Context: wallet type Difficult to backup - + Great for brief usage Context: wallet type Great for brief usage - + Import Existing Wallet Import Existing Wallet - + Import Import - + Imports seed-phrase Imports seed-phrase - + Imports private key Imports private key - + New Wallet New Wallet - + This creates a new empty wallet with simple multi-address capability This creates a new empty wallet with simple multi-address capability - + Name Name @@ -828,66 +803,66 @@ This ensures only one private key will need to be backed up Import Running... - - + + Description Description - + Amount requested amount of coin Amount - + Address Bitcoin Cash address Address - + Clear Clear - - + + Payment Seen Payment Seen - + Checking... Checking... - + Transaction high risk Transaction high risk - + Partially Paid Partially Paid - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Continue Continue @@ -928,7 +903,7 @@ This ensures only one private key will need to be backed up Send - + Start Payment Start Payment @@ -1089,12 +1064,12 @@ This ensures only one private key will need to be backed up Processing - + Mined Mined - + %1 blocks ago Confirmations @@ -1103,52 +1078,52 @@ This ensures only one private key will need to be backed up - + Miner Reward Miner Reward - + Cash Fusion Cash Fusion - + Received Received - + Payment to self Payment to self - + Sent Sent - + Holds a token Holds a token - + Sent to Sent to - + Value now Value now - + Value then Value then - + Transaction Details Transaction Details @@ -1161,7 +1136,7 @@ This ensures only one private key will need to be backed up Enter your wallet passcode - + Open open wallet with PIN Open diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts new file mode 100644 index 0000000..34b7410 --- /dev/null +++ b/translations/floweepay-mobile_es.ts @@ -0,0 +1,1145 @@ + + + + + About + + + About + Acerca de + + + + Help translate this app + Ayuda en la traducción de esta app + + + + License + Licencia + + + + + Credits + Créditos + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander y contribuyentes + + + + Project Home + Página del proyecto + + + + With git repository and issues tracker + Con repositorio git y seguimiento de incidencias + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Inicio + + + + Pay + Pagar + + + + Receive + Recibir + + + + Miner Reward + Recompensa del minero + + + + Cash Fusion + Fusión de Monedas + + + + Received + Recibido + + + + Moved + Movido + + + + Sent + Enviado + + + + Sending + Enviando + + + + Seen + Visto + + + + Rejected + Rechazado + + + + AccountPageListItem + + + Name + Nombre + + + + Archived wallets do not check for activities. Balance may be out of date + Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado + + + + Backup information + Respaldar información + + + + Backup Details + Detalles de la copia de seguridad + + + + Wallet seed-phrase + Frase-semilla del monedero + + + + Starting Height + height refers to block-height + Altura inicial + + + + Derivation Path + Ruta de Derivación + + + + xpub + xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que pierda su móvil. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! + + + + Wallet keys + Llaves del monedero + + + + Show Index + toggle to show numbers + Mostrar índice + + + + Change Addresses + Cambiar direcciones + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Intercambia entre direcciones que otros pueden pagarle y direcciones que el monedero usa para enviarte el cambio de vuelta. + + + + Used Addresses + Direcciones usadas + + + + Switches between still in use addresses and formerly used, new empty, addresses + Alterna entre direcciones en uso, previamente usadas y direcciones sin usar + + + + Addresses and keys + Direcciones y claves + + + + Sync Status + Estado de la sincronización + + + + Hide balance in overviews + Ocultar balance en vistas generales + + + + Hide in private mode + Ocultar en modo privado + + + + Unarchive Wallet + Desarchivar monedero + + + + Archive Wallet + Archivar monedero + + + + AccountSelectorPopup + + + Your Wallets + Tus monederos + + + + last active + Última vez activo + + + + Needs PIN to open + Necesita PIN para abrir + + + + Balance Total + Balance total + + + + AccountSyncState + + + Status: Offline + Estado: Desconectado + + + + AccountsList + + + Wallet + Monedero + + + + Wallets + Monederos + + + + Add Wallet + Añadir Monedero + + + + Default Wallet + Monedero predeterminado + + + + %1 is used on startup + %1 se utiliza al iniciar + + + + Exit Private Mode + Salir del modo privado + + + + Enter Private Mode + Entrar en modo privado + + + + Reveals wallets marked private + Revela monederos marcados como privados + + + + Hides wallets marked private + Oculta monederos marcados como privados + + + + CurrencySelector + + + Select Currency + Seleccionar moneda + + + + ExploreModules + + + Explore + Explorar + + + + ON + Enabled. SHORT TEXT! + ENCENDIDO + + + + GuiSettings + + + Display Settings + Mostrar ajustes + + + + Font sizing + Tamaño de fuente + + + + Unit + Unidad + + + + Change Currency (%1) + Cambiar moneda (%1) + + + + Main View + Vista principal + + + + Show Bitcoin Cash value + Mostrar valor de Bitcoin Cash + + + + ImportWalletPage + + + Import Wallet + Importar monedero + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. + + + + Secret + The seed-phrase or private key + Secreto + + + + Private key + description of type + Llave privada + + + + BIP 39 seed-phrase + description of type + Frase semilla BIP 39 + + + + Unrecognized word + Word from the seed-phrases lexicon + Palabra no reconocida + + + + Name + Nombre + + + + Force Single Address + Forzar dirección única + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. +El cambio volverá a la clave importada. + + + + Oldest Transaction + Transacción más antigua + + + + Derivation + Derivación + + + + Alternate phrase + Frase alternativa + + + + Create + Crear + + + + InstaPayConfigButton + + + Enable Instant Pay + Activar Pago Instantáneo + + + + Configure Instant Pay + Configurar Pago Instantáneo + + + + Fast payments for low amounts + Pagos rápidos para cantidades bajas + + + + Not configured + Sin configurar + + + + Limit set to: %1 + Límite establecido a: %1 + + + + InstaPayConfigPage + + + Instant Pay + Pago instantáneo + + + + Requests for payment can be approved automatically using Instant Pay. + Las solicitudes de pago pueden ser aprobadas automáticamente usando Pago Instantáneo. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Los monederos protegidos no se pueden utilizar para InstaPagos porque requieren un PIN antes de usarse + + + + Enable Instant Pay for this wallet + Habilitar Pago Instantáneo para este monedero + + + + Maximum Amount + Monto máximo + + + + LockApplication + + + Security + Seguridad + + + + PIN on startup + PIN al iniciar + + + + Enter current PIN + Introduzca el PIN actual + + + + Enter new PIN + Introduce un nuevo PIN + + + + Repeat PIN + Repetir PIN + + + + Remove PIN + Eliminar PIN + + + + Set PIN + Establecer PIN + + + + PIN has been removed + El PIN ha sido eliminado + + + + PIN has been set + El PIN ha sido establecido + + + + Ok + Aceptar + + + + MenuOverlay + + + Add Wallet + Añadir Monedero + + + + NewAccount + + + New Bitcoin Cash Wallet + Nuevo monedero Bitcoin Cash + + + + Create a New Wallet + Crear un nuevo monedero + + + + HD wallet + Monedero HD + + + + Seed-phrase based + Context: wallet type + Basado en frase semilla + + + + Easy to backup + Context: wallet type + Fácil de respaldar + + + + Most compatible + The most compatible wallet type + El más compatible + + + + Basic + Básico + + + + Private keys based + Property of a wallet + Basado en Claves privadas + + + + Difficult to backup + Context: wallet type + Difícil de respaldar + + + + Great for brief usage + Context: wallet type + Ideal para un uso breve + + + + Import Existing Wallet + Importar un monedero existente + + + + Import + Importar + + + + Imports seed-phrase + Importa la frase semilla + + + + Imports private key + Importa la clave privada + + + + New Wallet + Nuevo Monedero + + + + This creates a new empty wallet with simple multi-address capability + Esto crea un nuevo monedero vacío con capacidad de multi-dirección simple + + + + + Name + Nombre + + + + Force Single Address + Forzar dirección única + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Cuando está habilitado, este monedero se limitará a una dirección. +Esto asegura que solo una clave privada tendrá que ser respaldada + + + + + Create + Crear + + + + New HD-Wallet + Nuevo monedero-HD + + + + This creates a new wallet that can be backed up with a seed-phrase + Esto crea un nuevo monedero que puede ser respaldado con una frase semilla + + + + Derivation + Derivación + + + + PayWithQR + + + Approve Payment + Aprobar pago + + + + Send All + all money in wallet + Enviar Todo + + + + Show Address + to show a bitcoincash address + Mostrar dirección + + + + Edit Amount + Edit amount of money to send + Editar cantidad + + + + Invalid QR code + Código QR inválido + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + No entiendo el código escaneado. Lo siento, no puedo iniciar un pago. + + + + details + detalles + + + + Scanned text: <pre>%1</pre> + Texto escaneado: <pre>%1</pre> + + + + Payment description + Descripción del pago + + + + Destination Address + Dirección de destino + + + + Unlock Wallet + Desbloquear Monedero + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH es: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + PriceInputWidget + + + All Currencies + Todas las divisas + + + + QRScannerOverlay + + + Paste + Pegar + + + + Failed + Fallido + + + + ReceiveTab + + + Receive + Recibir + + + + Share this QR to receive + Comparte este QR para recibir + + + + Encrypted Wallet + Monedero encriptado + + + + Import Running... + Ejecutando Importación... + + + + + Description + Descripción + + + + Amount + requested amount of coin + Monto + + + + Address + Bitcoin Cash address + Dirección + + + + Clear + Borrar + + + + + Payment Seen + Pago Enviado + + + + Checking... + Comprobando... + + + + Transaction high risk + Transacción de alto riesgo + + + + Partially Paid + Parcialmente pagado + + + + Payment Accepted + Pago Aceptado + + + + Payment Settled + Pago Enviado + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Pago instantáneo fallido. Espere la confirmación. (prueba de doble gasto recibida) + + + + Continue + Continuar + + + + SelectDefaultAccountPage + + + Select Wallet + Seleccione el monedero + + + + Pick which wallet will be selected on starting Flowee Pay + Seleccione que monedero será seleccionado al iniciarse Flowee Pay + + + + No InstaPay limit set + No hay límite para InstaPay establecido + + + + InstaPay till %1 + InstaPay hasta %1 + + + + InstaPay is turned off + InstaPay está desactivado + + + + SendTransactionsTab + + + Send + Enviar + + + + Start Payment + Iniciar pago + + + + SlideToApprove + + + SLIDE TO SEND + DESLIZAR PARA ENVIAR + + + + StartupScreen + + + Welcome! + ¡Bienvenido! + + + + Continue + Continuar + + + + Moving the world towards a Bitcoin Cash economy + Moviendo el mundo hacia una economía de Bitcoin Cash + + + + Scan me to send funds to your HD wallet + Escanéame para enviar fondos a tu monedero HD + + + + OR + O + + + + Add a different wallet + Añadir un monedero diferente + + + + TransactionDetails + + + Transaction Details + Detalles de la transacción + + + + Transaction Hash + Hash de transacción + + + + Rejected + Rechazada + + + + Unconfirmed + Sin confirmar + + + + Mined at + Minado en + + + + %1 blocks ago + + Hace %1 bloque + Hace %1 bloques + + + + + Transaction comment + Comentario de la transacción + + + + Size: %1 bytes + Tamaño: %1 bytes + + + + Coinbase + Coinbase + + + + Is a CashFusion transaction. + Es una transacción de CashFusion. + + + + Fees paid + Comisiones pagadas + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Fusionado desde mis direcciones + + + + Sent from my addresses + Enviado desde mis direcciones + + + + Sent from addresses + Enviado desde direcciones + + + + + Copy Address + Copiar dirección + + + + Fused into my addresses + Fusionado en mis direcciones + + + + Received at addresses + Recibido en direcciones + + + + Received at my addresses + Recibido en mis direcciones + + + + TxInfoSmall + + + Transaction is rejected + Transacción rechazada + + + + Processing + Procesando + + + + Mined + Minado + + + + %1 blocks ago + Confirmations + + Hace %1 bloque + %1 blocks ago + + + + + Miner Reward + Recompensa del minero + + + + Cash Fusion + Fusión de Monedas + + + + Received + Recibido + + + + Payment to self + Auto pago + + + + Sent + Enviado + + + + Holds a token + Contiene un token + + + + Sent to + Enviado a + + + + Value now + Valor ahora + + + + Value then + Valor entonces + + + + Transaction Details + Detalles de la transacción + + + + UnlockWidget + + + Enter your wallet passcode + Introduzca su código de acceso al monedero + + + + Open + open wallet with PIN + Abrir + + + diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index a4c6351..1b92487 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -9,38 +9,38 @@ Over Ons - + Help translate this app Help deze app te vertalen - + License Licentie - - + + Credits Met dank aan - + © 2020-2023 Tom Zander and contributors © 2020-2023 Tom Zander en bijdragers - + Project Home Startpagina project - + With git repository and issues tracker Met git data en takenlijst - + Telegram Telegram @@ -168,52 +168,52 @@ Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between still in use addresses and formerly used, new empty, addresses Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews - Hide balance in overviews + Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren @@ -233,12 +233,12 @@ Needs PIN to open - Needs PIN to open + Benodigd pincode bij openen Balance Total - Balance Total + Totale saldo @@ -313,7 +313,7 @@ Ontdek - + ON Enabled. SHORT TEXT! AAN @@ -347,7 +347,7 @@ Hoofd overzicht - + Show Bitcoin Cash value Toon Bitcoin Cash waarde @@ -411,17 +411,17 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Oudste transactie - + Derivation Derivatie - + Alternate phrase Alternatieve zin - + Create Creëer @@ -487,75 +487,50 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Security - Security + Beveiliging PIN on startup - PIN on startup + Pincode voor starten Enter current PIN - Enter current PIN + Voer huidige pincode in Enter new PIN - Enter new PIN + Voer nieuwe pincode in Repeat PIN - Repeat PIN + Herhaal pincode Remove PIN - Remove PIN + Verwijder pincode Set PIN - Set PIN + Pincode instellen PIN has been removed - PIN has been removed + Pincode verwijderd PIN has been set - PIN has been set + Pincode gezet - - Encrypt your wallet data - Encrypt your wallet data - - - - Require PIN to Pay on wallet - Require PIN to Pay on wallet - - - - Secure against spending - Secure against spending - - - - Require PIN to Open wallet - Require PIN to Open wallet - - - - Secure against all usage - Secure against all usage - - - + Ok Ok @@ -571,93 +546,93 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. NewAccount - + New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee - + Create a New Wallet Maak een nieuwe portemonnee - + HD wallet HD portemonnee - + Seed-phrase based Context: wallet type Herstelzin gebaseerd - + Easy to backup Context: wallet type Back-upt makkelijk - + Most compatible The most compatible wallet type Meest compatibel - + Basic Eenvoudig - + Private keys based Property of a wallet Privé sleutels gebaseerd - + Difficult to backup Context: wallet type Back-up moeilijk - + Great for brief usage Context: wallet type Ideaal voor kort gebruik - + Import Existing Wallet Importeer bestaande portemonnee - + Import Importeer - + Imports seed-phrase Importeert herstelzin - + Imports private key Importeert Privésleutel - + New Wallet Nieuwe Portemonnee - + This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden - + Name Naam @@ -754,7 +729,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Unlock Wallet - Unlock Wallet + Portemonnee ontgrendelen @@ -828,66 +803,66 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bezig met importeren... - - + + Description Omschrijving - + Amount requested amount of coin Bedrag - + Address Bitcoin Cash address Adres - + Clear Wissen - - + + Payment Seen Betaling gezien - + Checking... Controleren... - + Transaction high risk Transactie met hoog risico - + Partially Paid Deels betaald - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - + Continue Doorgaan @@ -928,7 +903,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Versturen - + Start Payment Betaling starten @@ -1089,12 +1064,12 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt In behandeling - + Mined Gedolven - + %1 blocks ago Confirmations @@ -1103,52 +1078,52 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + Miner Reward Mijnwerker Beloning - + Cash Fusion Cash Fusion - + Received Ontvangen - + Payment to self Betaling aan uzelf - + Sent Verzonden - + Holds a token Heeft een token - + Sent to Verstuurd naar - + Value now Waarde nu - + Value then Waarde toen - + Transaction Details Transactiedetails @@ -1158,10 +1133,10 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Enter your wallet passcode - Enter your wallet passcode + Voer pincode voor portemonnee in - + Open open wallet with PIN Open diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index f66078b..b0a2ac0 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -9,38 +9,38 @@ O programie - + Help translate this app Pomóż w tłumaczeniu tej aplikacji - + License Licencja - - + + Credits Autorzy - + © 2020-2023 Tom Zander and contributors ©️ 2020-2023 Tom Zander i współtwórcy - + Project Home Strona projektu - + With git repository and issues tracker Z repozytorium git i trackerem problemów - + Telegram Telegram @@ -168,52 +168,52 @@ Pokaż indeks - + Change Addresses Adres Reszty - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between still in use addresses and formerly used, new empty, addresses Przełącza się pomiędzy nadal używanymi adresami i wcześniej używanymi, nowymi pustymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet Anuluj archiwizację portfela - + Archive Wallet Zarchiwizuj portfel @@ -313,7 +313,7 @@ Explore - + ON Enabled. SHORT TEXT! ON @@ -347,7 +347,7 @@ Main View - + Show Bitcoin Cash value Show Bitcoin Cash value @@ -411,17 +411,17 @@ Change will come back to the imported key. Oldest Transaction - + Derivation Derivation - + Alternate phrase Alternate phrase - + Create Create @@ -530,32 +530,7 @@ Change will come back to the imported key. PIN has been set - - Encrypt your wallet data - Encrypt your wallet data - - - - Require PIN to Pay on wallet - Require PIN to Pay on wallet - - - - Secure against spending - Secure against spending - - - - Require PIN to Open wallet - Require PIN to Open wallet - - - - Secure against all usage - Secure against all usage - - - + Ok Ok @@ -571,93 +546,93 @@ Change will come back to the imported key. NewAccount - + New Bitcoin Cash Wallet New Bitcoin Cash Wallet - + Create a New Wallet Create a New Wallet - + HD wallet Portfel HD - + Seed-phrase based Context: wallet type Portfel bazujący na ciągu losowych słów (seedzie) - + Easy to backup Context: wallet type Easy to backup - + Most compatible The most compatible wallet type Najbardziej kompatybilny - + Basic Basic - + Private keys based Property of a wallet Private keys based - + Difficult to backup Context: wallet type Difficult to backup - + Great for brief usage Context: wallet type Great for brief usage - + Import Existing Wallet Importuj istniejący portfel - + Import Importuj - + Imports seed-phrase Importuje seeda - + Imports private key Importuj klucz prywatny - + New Wallet Nowy Portfel - + This creates a new empty wallet with simple multi-address capability Tworzy nowy, pusty portfel jedno- lub wieloadresowy - + Name Nazwa @@ -828,66 +803,66 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoImport Running... - - + + Description Description - + Amount requested amount of coin Amount - + Address Bitcoin Cash address Address - + Clear Clear - - + + Payment Seen Payment Seen - + Checking... Checking... - + Transaction high risk Transaction high risk - + Partially Paid Partially Paid - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Continue Continue @@ -928,7 +903,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSend - + Start Payment Start Payment @@ -1091,12 +1066,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoProcessing - + Mined Mined - + %1 blocks ago Confirmations @@ -1107,52 +1082,52 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - + Miner Reward Miner Reward - + Cash Fusion Cash Fusion - + Received Received - + Payment to self Payment to self - + Sent Sent - + Holds a token Holds a token - + Sent to Sent to - + Value now Value now - + Value then Value then - + Transaction Details Transaction Details @@ -1165,7 +1140,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoEnter your wallet passcode - + Open open wallet with PIN Open diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index cab2b97..ab89c2c 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -3,14 +3,22 @@ floweepay-common_en.qm floweepay-common_nl.qm floweepay-common_pl.qm + floweepay-common_de.qm + floweepay-common_es.qm floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm + floweepay-mobile_de.qm + floweepay-mobile_es.qm module-build-transaction_en.qm module-build-transaction_nl.qm module-build-transaction_pl.qm + module-build-transaction_de.qm + module-build-transaction_es.qm module-peers-view_en.qm module-peers-view_nl.qm module-peers-view_pl.qm + module-peers-view_de.qm + module-peers-view_es.qm diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts new file mode 100644 index 0000000..b76d818 --- /dev/null +++ b/translations/module-build-transaction_de.ts @@ -0,0 +1,169 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Transaktionen erstellen + + + + This module allows building more powerful transactions in one simple user interface. + Dieses Modul ermöglicht das Erstellen von leistungsfähigeren Transaktionen in einer einfachen Benutzeroberfläche. + + + + Build Transaction + Baue Transaktion + + + + PayToOthers + + + Build Transaction + Baue Transaktion + + + + Building Error + error during build + Fehler beim Bauen + + + + Add Payment Detail + page title + Zahlungsdetails hinzufügen + + + + + Add Destination + Ziel hinzufügen + + + + an address to send money to + eine Adresse zum Überweisen von Geld + + + + Confirm Sending + confirm we want to send the transaction + Senden bestätigen + + + + TXID + TXID + + + + Copy transaction-ID + Transaktions-ID kopieren + + + + Fee + Gebühr + + + + Transaction size + Transaktionsgröße + + + + %1 bytes + %1 Bytes + + + + Fee per byte + Gebühr pro Byte + + + + %1 sat/byte + fee + %1 Sat/Byte + + + + Destination + Ziel + + + + unset + indication of empty + ungesetzt + + + + invalid + address is not correct + ungültig + + + + + Copy Address + Adresse kopieren + + + + Edit Destination + Ziel bearbeiten + + + + Send All + all money in wallet + Alles senden + + + + Bitcoin Cash Address + Bitcoin Cash Adresse + + + + Warning + Warnung + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? + + + + I am certain + Ich bin mir sicher + + + + Drag to Edit + Ziehen zum Bearbeiten + + + + Drag to Delete + Ziehen zum Löschen + + + + Prepare Payment... + Zahlung wird vorbereitet... + + + + Unlock Wallet + Geldbörse entsperren + + + diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts new file mode 100644 index 0000000..d545112 --- /dev/null +++ b/translations/module-build-transaction_es.ts @@ -0,0 +1,169 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Crear Transacciones + + + + This module allows building more powerful transactions in one simple user interface. + Este módulo permite construir transacciones más potentes en una interfaz de usuario simple. + + + + Build Transaction + Construir transacción + + + + PayToOthers + + + Build Transaction + Construir transacción + + + + Building Error + error during build + Error al construir + + + + Add Payment Detail + page title + Añadir detalles del pago + + + + + Add Destination + Añadir destino + + + + an address to send money to + una dirección para enviar dinero a + + + + Confirm Sending + confirm we want to send the transaction + Confirmar envío + + + + TXID + TXID + + + + Copy transaction-ID + Copiar ID de la transacción + + + + Fee + Comisión + + + + Transaction size + Tamaño de la transacción + + + + %1 bytes + %1 bytes + + + + Fee per byte + Comisión por byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Destination + Destino + + + + unset + indication of empty + no establecido + + + + invalid + address is not correct + inválido + + + + + Copy Address + Copiar dirección + + + + Edit Destination + Editar destino + + + + Send All + all money in wallet + Enviar Todo + + + + Bitcoin Cash Address + Dirección de Bitcoin Cash + + + + Warning + Advertencia + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? + + + + I am certain + Estoy seguro + + + + Drag to Edit + Arrastre para editar + + + + Drag to Delete + Arrastre para eliminar + + + + Prepare Payment... + Preparar pago... + + + + Unlock Wallet + Desbloquear Monedero + + + diff --git a/translations/module-peers-view_de.ts b/translations/module-peers-view_de.ts new file mode 100644 index 0000000..50d68c3 --- /dev/null +++ b/translations/module-peers-view_de.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adresse + + + + Start-height: %1 + Starthöhe: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + Initialisiere Verbindung + + + + Verifying peer + Überprüfe Peer + + + + Peer for wallet: %1 + Peer für Geldbörse: %1 + + + + Peer for wallet + Peer für Geldbörse + + + + PeersViewModuleInfo + + + Peers View + Peer-Ansicht + + + + This module provides a view of network servers we connect to often called 'peers'. + Dieses Modul bietet eine Ansicht der Netzwerk-Server, zu denen wir uns verbinden, häufig als 'Peers' bezeichnet. + + + + Network Details + Netzwerkdetails + + + diff --git a/translations/module-peers-view_es.ts b/translations/module-peers-view_es.ts new file mode 100644 index 0000000..bd4952a --- /dev/null +++ b/translations/module-peers-view_es.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Pares + + + + Address + network address (IP) + Dirección + + + + Start-height: %1 + Altura de inicio: %1 + + + + ban-score: %1 + calificación de no confiabilidad: %1 + + + + initializing connection + Inicializando conexión + + + + Verifying peer + Verificando par + + + + Peer for wallet: %1 + Par para el monedero: %1 + + + + Peer for wallet + Par para el monedero + + + + PeersViewModuleInfo + + + Peers View + Vista de pares + + + + This module provides a view of network servers we connect to often called 'peers'. + Este módulo proporciona una vista de servidores en la red a los que nos conectamos, a menudo llamados 'pares'. + + + + Network Details + Detalles de la red + + + -- 2.54.0 From 2b3feb6df605effa430138ec2d1204ac93044cf4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Aug 2023 21:57:03 +0200 Subject: [PATCH 0650/1428] increase minor version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 0e2c805..b83bab7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.07.1"); + qapp.setApplicationVersion("2023.07.2"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From ea04836b452df9ecce6c24b931da2b911f68d592 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Aug 2023 11:01:15 +0200 Subject: [PATCH 0651/1428] increase minor version for Android too --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d141cce..e542dfc 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="15" android:versionName="2023.07.2"> -- 2.54.0 From 97f4599464e1edd60829cdf808073188b0c1c08c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Aug 2023 11:01:17 +0200 Subject: [PATCH 0652/1428] move copy line to the sign script --- android/build-pay.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 8953602..691b2a8 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -101,6 +101,9 @@ export QT_ANDROID_KEYSTORE_KEY_PASS=longPassword --input /home/builds/build/android-pay_mobile-deployment-settings.json \ --output /home/builds/build/android-build \ --sign /home/builds/src/android/selfsigned.keystore floweepay + +echo -n "-- COPYING: " +cp -v android-build//build/outputs/apk/release/android-build-release-signed.apk floweepay.apk HERE chmod 755 .sign fi @@ -161,8 +164,6 @@ fi if test -n "\$MAKE_SIGNED_APK" then \$execInDocker build/.sign - echo -n "-- COPYING: " - cp -v android-build//build/outputs/apk/release/android-build-release-signed.apk floweepay.apk fi HERE -- 2.54.0 From ae9bb0827dbc2dc4fd44b1049d654da59446bd8a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 18:46:13 +0200 Subject: [PATCH 0653/1428] Fix typed characters showing twice. --- guis/mobile/EditableLabel.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 26a7a57..56c98ca 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -72,10 +72,10 @@ Item { visible: false focus: visible text: ourLabel.text - onTotalTextChanged: { + onEditingFinished: { ourLabel.text = totalText; root.edited(); + visible = false } - onEditingFinished: visible = false } } -- 2.54.0 From 9b5381a11d654298e8c0c09ab57b3c26e80fbdcc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 18:48:30 +0200 Subject: [PATCH 0654/1428] Cleanups of old CMake stuff --- CMakeLists.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eceaaf1..6868bff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,18 +234,11 @@ if (ANDROID AND BUILD_MOBILE_PAY) qt6_add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) - # For 6.2 - 6.4 the Qt Core permission APIs are only available as Android private apis - if (${Qt6Multimedia_FOUND} AND ${QT_VERSION_MINOR} GREATER_EQUAL 2 AND ${QT_VERSION_MINOR} LESS_EQUAL 4) - LIST (APPEND PAY_MOBILE_LIBS Qt6::CorePrivate) - message(STATUS "including private QtCore APIs for Android support") - endif () target_link_libraries(pay_mobile PRIVATE pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES - QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android + QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/android COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) - qt6_android_generate_deployment_settings(pay_mobile) - qt6_android_add_apk_target(pay_mobile) #notice; this call is deprecated since 6.5 endif () if(NOT ANDROID AND BUILD_MOBILE_PAY) -- 2.54.0 From fe5d819234aea5372e1017c3b53aaa4dd7e159f3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 19:09:43 +0200 Subject: [PATCH 0655/1428] Mention the new translators in the credits. Also remove the beta-testers, we've been out of Beta for AGES now :-) --- guis/mobile/About.qml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 81802be..dddb9c5 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -64,6 +64,19 @@ Page { text: "## Author and maintainer Tom Zander +## Translations + +Deutc +
h
+
Georg Engelmann
+
Español
+
Gerard H.R.(devperate)
+
Nederlands
+
Tom Zander
+
Polski
+
Yantri & Karol Trzeszczkowski
+
+ ## Code Contributors You? @@ -71,19 +84,6 @@ You? ## Art Contributors You? - -## Beta Testers - -fshinetop - -## Translations - -Nederland
-
s
-
Tom Zander
-
Polski
-
yantri
-
" textFormat: Text.MarkdownText wrapMode: Text.WordWrap -- 2.54.0 From 1b82e3f4f9fca4ce94f3b272bf0bd20f30601e17 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 19:55:13 +0200 Subject: [PATCH 0656/1428] fix typo --- src/CameraController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 11cbf69..e4a26a3 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -215,7 +215,7 @@ void CameraControllerPrivate::checkState() logFatal() << "CameraController found cam error:" << cam->errorString(); cam->setCameraFormat(preferredFormat); - // best too least-acceptbale focus mode. + // best too least-acceptable focus mode. constexpr QCamera::FocusMode f_modes[] = { QCamera::FocusModeAutoNear, QCamera::FocusModeAuto, QCamera::FocusModeHyperfocal, QCamera::FocusModeInfinity }; for (auto m : f_modes) { -- 2.54.0 From 73d89ec1b95e717baa2ed28be0d63cdae7754753 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 20:05:48 +0200 Subject: [PATCH 0657/1428] Avoid issues on non-camera holding hardware Avoid setting a format that is null, seems Qt doesn't like it if you do. --- src/CameraController.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index e4a26a3..55d0c2f 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -214,7 +214,8 @@ void CameraControllerPrivate::checkState() if (cam->error() != QCamera::NoError) logFatal() << "CameraController found cam error:" << cam->errorString(); - cam->setCameraFormat(preferredFormat); + if (!preferredFormat.isNull()) + cam->setCameraFormat(preferredFormat); // best too least-acceptable focus mode. constexpr QCamera::FocusMode f_modes[] = { QCamera::FocusModeAutoNear, QCamera::FocusModeAuto, QCamera::FocusModeHyperfocal, QCamera::FocusModeInfinity }; -- 2.54.0 From 632223d0f0701ebb10a6a6fbe0393bcf384fe289 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 20:36:30 +0200 Subject: [PATCH 0658/1428] Fix layout of warning --- guis/mobile/InstaPayConfigPage.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/mobile/InstaPayConfigPage.qml b/guis/mobile/InstaPayConfigPage.qml index e1c659f..36dd3f5 100644 --- a/guis/mobile/InstaPayConfigPage.qml +++ b/guis/mobile/InstaPayConfigPage.qml @@ -16,7 +16,6 @@ * along with this program. If not, see . */ import QtQuick -// import QtQuick.Layouts import "../Flowee" as Flowee Page { @@ -36,6 +35,7 @@ Page { } Rectangle { + id: warningLabel anchors.top: introText.bottom anchors.topMargin: 10 width: parent.width @@ -51,14 +51,16 @@ Page { width: parent.width - 20 id: warning text: qsTr("Protected wallets can not be used for InstaPay because they require a PIN before usage") + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } } Flowee.CheckBox { id: mainToggle - anchors.top: introText.bottom + anchors.top: root.account.needsPinToPay ? warningLabel.bottom : introText.bottom anchors.topMargin: 10 width: parent.width + enabled: root.account.needsPinToPay === false text: qsTr("Enable Instant Pay for this wallet") checked: root.account.allowInstaPay onClicked: root.account.allowInstaPay = checked -- 2.54.0 From 5fbd7a3f0d342c02f3cf8252111eec603c641f3b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 20:39:46 +0200 Subject: [PATCH 0659/1428] Show instant pay threshold on QR scan page --- guis/mobile/QRScannerOverlay.qml | 48 +++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 47a8731..1e62ce0 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -112,13 +112,6 @@ FocusScope { PathLine { x: 0; y: 0 } } } - Flowee.CloseIcon { - anchors.right: parent.right - anchors.rightMargin: 10 - y: 10 - onClicked: CameraController.abort(); - } - Rectangle { id: pasteFrame @@ -180,6 +173,47 @@ FocusScope { } } + Rectangle { + width: parent.width + height: Math.max(closeIcon.height, instaLabel.height) + 20 + color: mainWindow.floweeBlue + + Flowee.CloseIcon { + id: closeIcon + anchors.right: parent.right + anchors.rightMargin: 10 + y: 10 + onClicked: CameraController.abort(); + } + + Flowee.Label { + id: instaLabel + anchors.left: parent.left + anchors.top: parent.top + anchors.right: closeIcon.left + anchors.margins: 10 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + color: "white" + + text: { + let cur = portfolio.current; + if (cur === null || !cur.allowInstaPay) + return ""; + let fiatName = Fiat.currencyName; + let limit = cur.fiatInstaPayLimit(fiatName); + if (limit === 0) + return ""; + var answer = qsTr("Instant Pay limit is %1").arg(Fiat.formattedPrice(limit)); + + if (!portfolio.singleAccountSetup) + answer += "\n" + qsTr("Selected wallet: '%1'").arg(cur.name); + return answer; + } + } + } + + + // ------ components below this, which are not instantiated by default ----- Component { id: videoFeedPanel Item { -- 2.54.0 From 1bf4b8b493bfe0661b4fd977199a2ddfdef02150 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 23:01:23 +0200 Subject: [PATCH 0660/1428] Support more local currencies and fix ARS This adds support for a convert-from-usd second feed which is advertised by locals as the correct (non government sanctioned) conversion. --- src/PriceDataProvider.cpp | 82 ++++++++++++++++++++++++++++++++++++--- src/PriceDataProvider.h | 5 +++ 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 1b00cf7..47d28d9 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -30,6 +30,10 @@ static const char *CoinGeckoURL = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin-cash&vs_currencies=%1"; static const char *CoinGeckoJSONRoot = "bitcoin-cash"; // CoinGecko end +// Yadio (conversion from USD to various currencies) +static const char * YadioURL = "https://api.yadio.io/exrates/USD"; +static const char * YadioURLJSONRoot = "USD"; +// Yadio end static constexpr int ReloadTimeout = 7 * 60 * 1000; @@ -81,6 +85,12 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) m_currencySymbolPrefix += QLatin1String(" "); } + // some currencies need conversion from USD + if (m_currency == QLatin1String("ARS") + || m_currency == QLatin1String("VES")) { + m_yadioViaUSD = true; + } + emit currencySymbolChanged(); if (!m_basedir.isEmpty()) { assert(m_priceHistory.get()); @@ -201,10 +211,18 @@ QString PriceDataProvider::priceToStringSimple(int64_t cents) const void PriceDataProvider::fetch() { + assert(m_reply == nullptr); + if (m_yadioViaUSD && m_currencyConversions.isEmpty()) { + m_reply = m_network.get(QNetworkRequest(QUrl(YadioURL))); + logInfo() << "fetching yadio.io USD-conversions"; + connect(m_reply, SIGNAL(finished()), this, SLOT(finishedYadioDownload())); + return; // one feed at a time. + } + QString url(CoinGeckoURL); - QNetworkRequest req(QUrl(url.arg(m_currency.toLower()))); - logInfo() << "fetch" << m_currency; - m_reply = m_network.get(req); + url = url.arg(m_yadioViaUSD ? "usd" : m_currency.toLower()); + logInfo() << "fetch" << url; + m_reply = m_network.get(QNetworkRequest(QUrl(url))); connect(m_reply, SIGNAL(finished()), this, SLOT(finishedDownload())); } @@ -243,13 +261,25 @@ void PriceDataProvider::finishedDownload() auto root = doc.object(); // coingecko auto section = root.value(CoinGeckoJSONRoot).toObject(); - auto price = section.value(m_currency.toLower()); + auto price = section.value(m_yadioViaUSD ? QLatin1String("usd") : m_currency.toLower()); if (price.isUndefined()) { // our provider does not support this coin. logCritical() << " provider does not support this coin" << m_currency; + assert(!m_yadioViaUSD); setCountry("en_US"); return; } - m_currentPrice.price = price.toDouble() * 100; + if (m_yadioViaUSD) { + auto multiplier = m_currencyConversions.value(m_currency); + if (multiplier == 0) { + logCritical() << "Currency conversion not available in the yadio feed"; + setCountry("en_US"); + return; + } + + m_currentPrice.price = std::round(price.toDouble() * 100 * multiplier); + } else { + m_currentPrice.price = price.toDouble() * 100; + } m_currentPrice.timestamp = time(nullptr); emit priceChanged(m_currentPrice.price); } catch (const std::runtime_error &error) { @@ -263,6 +293,48 @@ void PriceDataProvider::finishedDownload() m_timer.start(ReloadTimeout); } +void PriceDataProvider::finishedYadioDownload() +{ + logInfo(); + assert(m_reply); + const auto data = m_reply->readAll(); + const bool failed = m_reply->error() != QNetworkReply::NoError || data.isEmpty(); + if (failed) + logCritical() << " failed"; + m_reply->deleteLater(); + m_reply = nullptr; + if (failed) { + m_timer.stop(); + if (m_failedCount++ < 5) + fetch(); + else + m_timer.start(20 * 1000); + return; + } + try { + QJsonDocument doc = QJsonDocument::fromJson(data); + if (doc.isEmpty()) + throw std::runtime_error("Failed parsing the yadio data"); + + m_currencyConversions.clear(); + auto root = doc.object(); + auto prices = root.value(YadioURLJSONRoot).toObject(); + for (auto i = prices.begin(); i != prices.end(); ++i) { + if (i.key().size() != 3) + continue; + auto value = i.value().toDouble(-1); + if (value > 0) + m_currencyConversions.insert(i.key(), value); + } + logInfo() << "got" << m_currencyConversions.size() << "values"; + logInfo() << m_currencyConversions.value("ARS"); + fetch(); // fetch the CoinGecko one next. + } catch (const std::runtime_error &error) { + logWarning() << "PriceDataProvider Yadio feed failed:" << error.what(); + m_timer.start(200 * 1000); + } +} + QString PriceDataProvider::currencySymbolPost() const { return m_currencySymbolPost; diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 6ffc58f..463b0f4 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -117,6 +117,7 @@ signals: private slots: void fetch(); void finishedDownload(); + void finishedYadioDownload(); private: struct Price { @@ -133,6 +134,10 @@ private: int m_failedCount = 0; QTimer m_timer; + // Prices converted via USD + bool m_yadioViaUSD = false; + QMap m_currencyConversions; + QString m_basedir; // where the priceHistory is stored. std::unique_ptr m_priceHistory; }; -- 2.54.0 From 735c2b0265406f614665061c5a231fa3ea314b2e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Aug 2023 23:12:07 +0200 Subject: [PATCH 0661/1428] fixlet --- guis/mobile/CurrencySelector.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index ee778c9..c43e90b 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -80,7 +80,7 @@ Page { Flowee.Label { id: iso y: 10 - text: Qt.locale(modelData).currencySymbol(Locale.XCurrencyIsoCode) + text: Qt.locale(modelData).currencySymbol(Locale.CurrencyIsoCode) } Flowee.Label { @@ -104,6 +104,5 @@ Page { } } } - } } -- 2.54.0 From 31a3715315a413856737a2395849075b7482fb5d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 11:49:30 +0200 Subject: [PATCH 0662/1428] Upgrade to Qt6.5.2 --- android/build-pay.sh | 2 +- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 691b2a8..86fe845 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.1" + _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.2" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index e85ce8d..c52887b 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.5.1 +ARG QtVersion=v6.5.2 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 3fc3faf..793bc16 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.5.1 +QtVersion=v6.5.2 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion -- 2.54.0 From ba08f1e39a805ffc75c028a822ab8081535bab02 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 12:04:51 +0200 Subject: [PATCH 0663/1428] Work around QTBUG-116527 --- guis/mobile/CurrencySelector.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index c43e90b..f942193 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -80,7 +80,8 @@ Page { Flowee.Label { id: iso y: 10 - text: Qt.locale(modelData).currencySymbol(Locale.CurrencyIsoCode) + text: Qt.locale(modelData).currencySymbol(0) + // text: Qt.locale(modelData).currencySymbol(Locale.CurrencyIsoCode) } Flowee.Label { @@ -91,8 +92,9 @@ Page { anchors.right: parent.right text: { var loc = Qt.locale(modelData); - return "(" + loc.currencySymbol(Locale.CurrencySymbol) + ") " - + loc.currencySymbol(Locale.CurrencyDisplayName); + return loc.currencySymbol(2) + " [" + loc.currencySymbol(1) + "]"; + // return loc.currencySymbol(Locale.CurrencyDisplayName) + // + " [" + loc.currencySymbol(Locale.CurrencySymbol) + "]"; } } -- 2.54.0 From 1ff6b61b05d3db7e09753ceb504710f805283f37 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 12:19:26 +0200 Subject: [PATCH 0664/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index e542dfc..52abd80 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="16" android:versionName="2023.08.0"> diff --git a/src/main.cpp b/src/main.cpp index b83bab7..cb757be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.07.2"); + qapp.setApplicationVersion("2023.08.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From d9ce8f4b6efe92ba11aea03c698581a1653f75f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 18:37:32 +0200 Subject: [PATCH 0665/1428] Add feature; drag to open menu. People that only use one hand will no longer need to use the other to reach the button at the top for the menu. Instead we now allow a swipe in the bottom left corner, swiping to the right will make the menu open. --- guis/mobile/MenuOverlay.qml | 42 ++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index dbeb503..d9ee1b5 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2023 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -244,6 +244,46 @@ Item { enabled: root.open onClicked: root.open = false; } + Item { + id: menuSwipy + width: parent.width / 2 + height: parent.height / 3 + anchors.bottom: parent.bottom + + onXChanged: { + // moving this drag area makes the menu slowly open. + let progress = x / width; + let menuX = 0; + if (progress < 0.2) // threshold for movement + return; + if (progress < 0.39) { // first 50% movement + menuX = Math.pow((progress - 0.1) * 10, 2) * width / 10; + } + else { + // progress between 0.4 and 1.0 + // this movement goes linear instead. + menuX = 10 + (progress - 0.4) * 2 * (menuArea.width / 2) + menuArea.width / 2; + } + menuArea.x = Math.min(0, 0 - menuArea.width + menuX + 10); + } + + DragHandler { + id: dragOpenHandler + enabled: root.open === false + yAxis.enabled: false // the anchors of parent do that too ¯\_(ツ)_/¯ + xAxis.minimum: 0 + xAxis.maximum: parent.width + onActiveChanged: { + if (!active) { + if (menuArea.x > -30) + root.open = true; + menuSwipy.x = 0; // reset + // restore the original binding + menuArea.x = root.open ? 0 : 0 - width -3 + } + } + } + } Component { id: addWalletRow -- 2.54.0 From 6cd8061762256f48d6c8bf747429d387b315c460 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 20:47:35 +0200 Subject: [PATCH 0666/1428] Add the gradle file to git This file gets auto-generated if we don't have it by the Qt tools so we didn't really need it before. This changes the default to now set the targetSdkVersion to 33, as required by the Android Play store. --- android/build.gradle | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 android/build.gradle diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..a5984fa --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,80 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.1' + } +} + +repositories { + google() + mavenCentral() +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) +} + +android { + /* ****************************************************** + * The following variables: + * - androidBuildToolsVersion, + * - androidCompileSdkVersion + * - qtAndroidDir - holds the path to qt android files + * needed to build any Qt application + * on Android. + * + * are defined in gradle.properties file. That file is + * created / updated by androiddeployqt. + *******************************************************/ + + compileSdkVersion androidCompileSdkVersion + buildToolsVersion androidBuildToolsVersion + ndkVersion androidNdkVersion + + // Extract native libraries from the APK + packagingOptions.jniLibs.useLegacyPackaging true + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = [qtAndroidDir + '/src', 'java'] + aidl.srcDirs = [qtAndroidDir + '/src', 'aidl'] + res.srcDirs = [qtAndroidDir + '/res', 'res'] + resources.srcDirs = ['resources'] + renderscript.srcDirs = ['src'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } + + tasks.withType(JavaCompile) { + options.incremental = true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + abortOnError false + } + + // Do not compress Qt binary resources file + aaptOptions { + noCompress 'rcc' + } + + defaultConfig { + resConfig "en" + minSdkVersion qtMinSdkVersion + targetSdkVersion 33 + ndk.abiFilters = qtTargetAbiList.split(",") + } +} -- 2.54.0 From 40aaa635fa4c62dc72f5c1c6ef2a0dc52fd9e474 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 21:05:23 +0200 Subject: [PATCH 0667/1428] fetch updates from Crowdin --- translations/floweepay-common_pl.ts | 4 +- translations/floweepay-desktop_es.ts | 128 +++++++-------- translations/floweepay-mobile_pl.ts | 223 +++++++++++++-------------- 3 files changed, 177 insertions(+), 178 deletions(-) diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 2be3326..fdb5309 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -205,7 +205,7 @@ Security - Security + Bezpieczeństwo @@ -240,7 +240,7 @@ New Transactions dialog-title - Nowe transakcje + Nowa Transakcja Nowe Transakcje Nowych transakcji New Transactions diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index f624a0c..5ccc4be 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -428,17 +428,17 @@ El cambio volverá a la clave importada. Basic wallet with private keys - Basic wallet with private keys + Monedero básico con claves privadas Seed-phrase wallet - Seed-phrase wallet + Monedero con frase semilla Import of an existing wallet - Import of an existing wallet + Importar cartera existente
@@ -446,17 +446,17 @@ El cambio volverá a la clave importada. Add Detail - Add Detail + Agregar detalles Coin Selector - Coin Selector + Selector de moneda To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. - To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + Para reemplazar la selección predeterminada de monedas que se utilizan para pagar una transacción, puedes añadir el 'Selector de Monedas' donde se harán visibles las monedas. @@ -464,67 +464,67 @@ El cambio volverá a la clave importada. Share your QR code or copy address to receive - Share your QR code or copy address to receive + Comparte tu código QR o copia tu dirección para recibir Encrypted Wallet - Encrypted Wallet + Monedero encriptado Import Running... - Import Running... + Ejecutando Importación... Checking - Checking + Comprobando Transaction high risk - Transaction high risk + Transacción de alto riesgo Payment Seen - Payment Seen + Pago Enviado Payment Accepted - Payment Accepted + Pago Aceptado Payment Settled - Payment Settled + Pago realizado Instant payment failed. Wait for confirmation. (double spent proof received) - Instant payment failed. Wait for confirmation. (double spent proof received) + Pago instantáneo fallido. Espere la confirmación. (prueba de doble gasto recibida) Description - Description + Descripción Amount - Amount + Monto Clear - Clear + Borrar Done - Done + Hecho @@ -532,7 +532,7 @@ El cambio volverá a la clave importada. Confirm delete - Confirm delete + Confirmar eliminación @@ -542,125 +542,125 @@ El cambio volverá a la clave importada. Add Destination - Add Destination + Añadir destino Prepare - Prepare + Preparar Enter your PIN - Enter your PIN + Introduce tu código PIN Transaction Details - Transaction Details + Detalles de la transacción Not prepared yet - Not prepared yet + Aún no preparado Copy transaction-ID - Copy transaction-ID + Copiar ID de la transacción Fee - Fee + Comisión Transaction size - Transaction size + Tamaño de la transacción %1 bytes - %1 bytes + %1 bytes Fee per byte - Fee per byte + Comisión por byte %1 sat/byte fee - %1 sat/byte + %1 sat/byte Send - Send + Enviar Destination - Destination + Destino Max available The maximum balance available - Max available + Máximo disponible %1 to %2 summary text to pay X-euro to address M - %1 to %2 + %1 a %2 Enter Bitcoin Cash Address - Enter Bitcoin Cash Address + Introduzca la dirección de Bitcoin Cash Copy Address - Copy Address + Copiar dirección Amount - Amount + Monto Max - Max + Máx Warning - Warning + Advertencia This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? Continue - Continue + Continuar Cancel - Cancel + Cancelar Coin Selector - Coin Selector + Selector de monedas @@ -675,22 +675,22 @@ El cambio volverá a la clave importada. Total Number of coins - Total + Total Needed - Needed + Necesario Selected - Selected + Seleccionado Value - Value + Valor @@ -700,27 +700,27 @@ El cambio volverá a la clave importada. Age - Age + Edad Unselect All - Unselect All + Deseleccionar Todo Select All - Select All + Seleccionar Todo Unlock coin - Unlock coin + Desbloquear moneda Lock coin - Lock coin + Bloquear moneda @@ -728,17 +728,17 @@ El cambio volverá a la clave importada. Settings - Settings + Configuraciones Unit - Unit + Unidad Show Bitcoin Cash value on Activity page - Show Bitcoin Cash value on Activity page + Mostrar valor de Bitcoin Cash en la página de Actividad @@ -753,12 +753,12 @@ El cambio volverá a la clave importada. Night Mode - Night Mode + Modo nocturno Private Mode - Private Mode + Modo Privado @@ -768,7 +768,7 @@ El cambio volverá a la clave importada. Version - Version + Versión @@ -778,12 +778,12 @@ El cambio volverá a la clave importada. Synchronization - Synchronization + Sincronización Network Status - Network Status + Estado de la red @@ -791,7 +791,7 @@ El cambio volverá a la clave importada. Protect your wallet with a password - Protect your wallet with a password + Protege tu monedero con una contraseña @@ -802,7 +802,7 @@ El cambio volverá a la clave importada. Protect your funds pin to pay - Protect your funds + Proteja sus fondos @@ -825,7 +825,7 @@ El cambio volverá a la clave importada. Protect your entire wallet pin to open - Protect your entire wallet + Protege todo tu monedero diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index b0a2ac0..0cf67aa 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -95,7 +95,7 @@ Seen - Seen + Wyświetlono @@ -200,7 +200,7 @@ Hide balance in overviews - Hide balance in overviews + Ukryj saldo w podsumowaniach @@ -233,12 +233,12 @@ Needs PIN to open - Needs PIN to open + Do otwarcia wymagany jest PIN Balance Total - Balance Total + Saldo Całkowite @@ -289,12 +289,12 @@ Reveals wallets marked private - Reveals wallets marked private + Pokaż portfele oznaczone jako prywatne Hides wallets marked private - Hides wallets marked private + Ukryj portfele oznaczone jako prywatne @@ -302,7 +302,7 @@ Select Currency - Select Currency + Wybierz Walutę @@ -310,13 +310,13 @@ Explore - Explore + Odkrywaj ON Enabled. SHORT TEXT! - ON + WŁĄCZONE @@ -324,32 +324,32 @@ Display Settings - Display Settings + Ustawienia wyświetlania Font sizing - Font sizing + Rozmiar czcionki Unit - Unit + Jednostka Change Currency (%1) - Change Currency (%1) + Zmień Walutę (%1) Main View - Main View + Ekran Główny Show Bitcoin Cash value - Show Bitcoin Cash value + Pokazuj wartość Bitcoin Cash @@ -357,73 +357,72 @@ Import Wallet - Import Wallet + Importuj Portfel Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Proszę wprowadzić sekret portfela, aby zaimportować. Może to być albo fraza-ziarno, albo klucz prywatny. Secret The seed-phrase or private key - Secret + Sekret Private key description of type - Private key + Klucz prywatny BIP 39 seed-phrase description of type - BIP 39 seed-phrase + Fraza-ziarno BIP 39 Unrecognized word Word from the seed-phrases lexicon - Unrecognized word + Nierozpoznane słowo Name - Name + Nazwa Force Single Address - Force Single Address + Wymuś pojedynczy adres When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - When enabled, no extra addresses will be auto-generated in this wallet. -Change will come back to the imported key. + Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. Oldest Transaction - Oldest Transaction + Najstarsza transakcja Derivation - Derivation + Derywacja Alternate phrase - Alternate phrase + Alternatywna fraza Create - Create + Utwórz @@ -431,27 +430,27 @@ Change will come back to the imported key. Enable Instant Pay - Enable Instant Pay + Włącz Szybką Płatność Configure Instant Pay - Configure Instant Pay + Skonfiguruj Szybką Płatność Fast payments for low amounts - Fast payments for low amounts + Szybkie płatności na małe kwoty Not configured - Not configured + Nie ustawiono Limit set to: %1 - Limit set to: %1 + Limit ustawiony do: %1 @@ -459,27 +458,27 @@ Change will come back to the imported key. Instant Pay - Instant Pay + Szybkie płatności Requests for payment can be approved automatically using Instant Pay. - Requests for payment can be approved automatically using Instant Pay. + Przy Szybkich Płatnościach żądania będą automatycznie zatwierdzane. Protected wallets can not be used for InstaPay because they require a PIN before usage - Protected wallets can not be used for InstaPay because they require a PIN before usage + Zabezpieczone portfele nie mogą być używane do Szybkich Płatności, ponieważ wymagają PIN-u przed użyciem Enable Instant Pay for this wallet - Enable Instant Pay for this wallet + Włącz Szybkie Płatności w tym portfelu Maximum Amount - Maximum Amount + Maksymalna Kwota @@ -487,52 +486,52 @@ Change will come back to the imported key. Security - Security + Bezpieczeństwo PIN on startup - PIN on startup + PIN podczas uruchamiania Enter current PIN - Enter current PIN + Wprowadź obecny PIN Enter new PIN - Enter new PIN + Wprowadź nowy PIN Repeat PIN - Repeat PIN + Powtórz PIN Remove PIN - Remove PIN + Usuń PIN Set PIN - Set PIN + Ustaw PIN PIN has been removed - PIN has been removed + PIN został usunięty PIN has been set - PIN has been set + PIN został ustawiony Ok - Ok + Ok @@ -540,7 +539,7 @@ Change will come back to the imported key. Add Wallet - Add Wallet + Dodaj Portfel @@ -548,12 +547,12 @@ Change will come back to the imported key. New Bitcoin Cash Wallet - New Bitcoin Cash Wallet + Nowy Portfel Bitcoin Cash Create a New Wallet - Create a New Wallet + Utwórz nowy portfel @@ -570,7 +569,7 @@ Change will come back to the imported key. Easy to backup Context: wallet type - Easy to backup + Łatwa kopia zapasowa @@ -581,25 +580,25 @@ Change will come back to the imported key. Basic - Basic + Podstawowy Private keys based Property of a wallet - Private keys based + Portfel bazujący na kluczach prywatnych Difficult to backup Context: wallet type - Difficult to backup + Trudna kopia zapasowa Great for brief usage Context: wallet type - Great for brief usage + Świetny do krótkotrwałego użytku @@ -709,27 +708,27 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego details - details + szczegóły Scanned text: <pre>%1</pre> - Scanned text: <pre>%1</pre> + Zeskanowany tekst: <pre>%1</pre> Payment description - Payment description + Opis płatności Destination Address - Destination Address + Adres docelowy Unlock Wallet - Unlock Wallet + Odblokuj portfel @@ -738,25 +737,25 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego 1 BCH is: %1 Price of a whole bitcoin cash - 1 BCH is: %1 + 1 BCH jest wart: %1 7d 7 days - 7d + 7d 1m 1 month - 1m + 1m 3m 3 months - 3m + 3m @@ -764,7 +763,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego All Currencies - All Currencies + Wszystkie Waluty @@ -772,12 +771,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Paste - Paste + Wklej Failed - Failed + Niepowodzenie @@ -785,56 +784,56 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Receive - Receive + Otrzymaj Share this QR to receive - Share this QR to receive + Pokaż ten QR kod, aby otrzymać Encrypted Wallet - Encrypted Wallet + Zaszyfrowany Portfel Import Running... - Import Running... + Trwa Importowanie... Description - Description + Opis Amount requested amount of coin - Amount + Kwota Address Bitcoin Cash address - Address + Adres Clear - Clear + Wyczyść Payment Seen - Payment Seen + Płatność Wysłana Checking... - Checking... + Sprawdzanie... @@ -964,23 +963,23 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Rejected - Rejected + Odrzucona Unconfirmed - Unconfirmed + Niepotwierdzona Mined at - Mined at + Wykopano %1 blocks ago - %1 blocks ago + %1 bloków temu %1 blocks ago %1 blocks ago %1 blocks ago @@ -989,68 +988,68 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Transaction comment - Transaction comment + Komentarz do transakcji Size: %1 bytes - Size: %1 bytes + Rozmiar: %1 bajtów Coinbase - Coinbase + Coinbase Is a CashFusion transaction. - Is a CashFusion transaction. + Jest transakcją CashFusion. Fees paid - Fees paid + Koszt transakcji %1 Satoshi / 1000 bytes - %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bajtów Fused from my addresses - Fused from my addresses + Fuzja z moich adresów Sent from my addresses - Sent from my addresses + Wysłano z moich adresów Sent from addresses - Sent from addresses + Wysłano z adresów Copy Address - Copy Address + Skopiuj Adres Fused into my addresses - Fused into my addresses + Fuzja na mój adres Received at addresses - Received at addresses + Adresy otrzymujące Received at my addresses - Received at my addresses + Moje adresy otrzymujące @@ -1058,17 +1057,17 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Transaction is rejected - Transaction is rejected + Transakcja została odrzucona Processing - Processing + Przetwarzanie Mined - Mined + Wykopano @@ -1077,59 +1076,59 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego %1 blocks ago %1 blocks ago - %1 blocks ago + %1 bloków temu %1 blocks ago Miner Reward - Miner Reward + Nagroda Górnicza Cash Fusion - Cash Fusion + Cash Fusion Received - Received + Otrzymano Payment to self - Payment to self + Płatność do siebie Sent - Sent + Wysłano Holds a token - Holds a token + Przechowuje token Sent to - Sent to + Wysłano na Value now - Value now + Wartość teraz Value then - Value then + Wartość wtedy Transaction Details - Transaction Details + Szczegóły transakcji @@ -1137,13 +1136,13 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Enter your wallet passcode - Enter your wallet passcode + Wprowadź hasło portfela Open open wallet with PIN - Open + Otwórz -- 2.54.0 From d1e5c99f1bc5a2d8d7ed0cb79f8612428a84ac0b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Aug 2023 22:15:49 +0200 Subject: [PATCH 0668/1428] Limit swipt-for-menu to the main screen. This makes sure we stay consistent and the UX doesn't need complicated code to make sure all user-actions are accounted for. --- guis/mobile/MenuOverlay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index d9ee1b5..bc60f1a 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -269,7 +269,7 @@ Item { DragHandler { id: dragOpenHandler - enabled: root.open === false + enabled: root.open === false && thePile.depth === 1 yAxis.enabled: false // the anchors of parent do that too ¯\_(ツ)_/¯ xAxis.minimum: 0 xAxis.maximum: parent.width -- 2.54.0 From b671dfc3a743c25f0f743dfc7e2d10ca9a9a12dd Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Aug 2023 12:50:40 +0200 Subject: [PATCH 0669/1428] Allow showing address in more cases. The QR scan page now also allows showing the destination address while the number keypad is up, being smarter with the layout of the various lines in order to fit the important info on screen. Additionally we require the user to first enable the 'edit amount' option before we make available the option to 'send all'. --- guis/mobile/PayWithQR.qml | 100 ++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 8702f21..a4e17c3 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -48,6 +48,7 @@ Page { priceInput.forceActiveFocus(); priceInput.fiatFollowsSats = false } + checked: root.allowEditAmount } menuItems: { // only have menu items as long as we are effectively @@ -55,7 +56,7 @@ Page { if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) { // a QR _with_ a bch-amount will turn off editing of amount-to-send if (allowEditAmount) - return [ sendAllAction ]; + return [ showTargetAddress, sendAllAction ]; else return [ showTargetAddress, editPrice ]; } @@ -183,18 +184,85 @@ Page { Flowee.Label { id: commentLabel - text: qsTr("Payment description") + ":" - visible: userComment.text !== "" - anchors.top: priceInput.bottom - anchors.topMargin: 10 + text: qsTr("Payment description") + visible: userComment.visible + y: { + if (!userComment.visible) + return 0; + if (root.allowEditAmount) { // the numpad is on + /* + position based on available space and how many items + need to be visible. + If invisible, place at -100 + */ + let space = walletNameBackground.y - (priceInput.y + priceInput.height) + if (destinationAddress.visible) + space -= destinationAddress.height + 10 + space -= userComment.height + 10; + if (space < height + 10) + return -100; + return priceInput.y + priceInput.height + 10; + } + return walletNameBackground.y + walletNameBackground.height + 10; + } } Flowee.Label { id: userComment text: payment.userComment - visible: text !== "" + visible: text !== "" && !errorLabel.visible color: palette.highlight font.italic: true - anchors.top: commentLabel.bottom + y: { + if (!visible) + return 0; + if (commentLabel.y < 0) + return priceInput.y + priceInput.height + 10; + return commentLabel.y + commentLabel.height + 10; + } + } + + Flowee.Label { + id: destinationAddressHeader + text: qsTr("Destination Address") + visible: destinationAddress.visible + + y: { + if (!visible) + return 0; + if (root.allowEditAmount) { // the numpad is on + let space = walletNameBackground.y - (priceInput.y + priceInput.height) + if (userComment.visible) + space -= userComment.height + 10 + if (commentLabel.visible) + space -= commentLabel.height + 10 + space -= destinationAddress.height + 10; + if (space < height + 10) + return -100; + if (userComment.visible) + return userComment.y + userComment.height + 10; + return priceInput.y + priceInput.height + 10; + } + if (userComment.visible) + return userComment.y + userComment.height + 10; + return walletNameBackground.y + walletNameBackground.height + 10; + } + } + Flowee.LabelWithClipboard { + id: destinationAddress + text: payment.niceAddress + width: parent.width + visible: showTargetAddress.checked && !errorLabel.visible + font.pixelSize: mainWindow.font.pixelSize * 0.9 + y: { + if (!visible) + return 0; + if (destinationAddressHeader.y < 0){ + if (userComment.visible) + return userComment.y + userComment.height + 10; + return priceInput.y + priceInput.height + 10; + } + return destinationAddressHeader.y + destinationAddressHeader.height + 10; + } } Rectangle { @@ -227,7 +295,11 @@ Page { anchors.bottom: numericKeyboard.top anchors.bottomMargin: 10 - balanceActions: [ sendAllAction ] + balanceActions: { + if (editPrice.checked) + return [ sendAllAction ]; + return []; + } } NumericKeyboardWidget { @@ -242,18 +314,6 @@ Page { Behavior on x { NumberAnimation { } } } - PageTitledBox { - anchors.top: numericKeyboard.top - width: parent.width - visible: !allowEditAmount && showTargetAddress.checked - title: qsTr("Destination Address") - Flowee.LabelWithClipboard { - text: payment.niceAddress - width: parent.width - font.pixelSize: mainWindow.font.pixelSize * 0.9 - } - } - SlideToApprove { id: slideToApprove anchors.bottom: parent.bottom -- 2.54.0 From 56ff8fcff72680d5010cb83be8a6bf31fffe988b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Aug 2023 18:11:25 +0200 Subject: [PATCH 0670/1428] Make distclean not delete the blockchain data Makes little sense to delete it only to instantly download it again. --- android/build-pay.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 86fe845..43b7e16 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -1,6 +1,6 @@ #!/bin/bash # This file is part of the Flowee project -# Copyright (C) 2022 Tom Zander +# Copyright (C) 2022-2023 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.2" + _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.1" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then @@ -114,7 +114,10 @@ cat << HERE > smartBuild.sh #Created by build-pay.sh if test "\$1" = "distclean"; then - perl -e 'use File::Path qw(remove_tree); opendir DIR, "."; while (\$entry = readdir DIR) { if (\$entry=~/^\./) { next; } if (\$entry=~/smartBuild.sh$/ || \$entry=~/^imports$/) { next; } unlink "\$entry"; remove_tree "\$entry"; }' + mv android-build/assets/blockheaders . + perl -e 'use File::Path qw(remove_tree); opendir DIR, "."; while (\$entry = readdir DIR) { if (\$entry=~/^\./) { next; } if (\$entry=~/smartBuild.sh$/ || \$entry=~/^imports$/ || \$entry=~/^blockheaders$/) { next; } unlink "\$entry"; remove_tree "\$entry"; }' + mkdir -p android-build/assets/ + mv blockheaders android-build/assets/ fi if test "\$1" = "sign" -o "\$2" = "sign" -- 2.54.0 From bc459f74d5e00ea66fbf34172acca8ae579de85b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 30 Aug 2023 22:42:08 +0200 Subject: [PATCH 0671/1428] PaymentProtocol abstraction The PaymentProtcol class now handles the pasting of a payment uri, like bip21. Additionally this adds the feature where we pass in the payment uri on the commandline which then results in an auto-opened payment screen. --- guis/desktop/SendTransactionPane.qml | 16 +++ guis/desktop/main.qml | 3 + guis/desktop/org.flowee.pay.desktop | 2 +- guis/mobile/PayWithQR.qml | 20 +++- guis/mobile/main.qml | 2 + src/CMakeLists.txt | 2 + src/FloweePay.cpp | 13 +++ src/FloweePay.h | 11 +- src/Payment.cpp | 22 ++-- src/PaymentDetailOutput.cpp | 39 ++----- src/PaymentProtocol.cpp | 145 +++++++++++++++++++++++++++ src/PaymentProtocol.h | 40 ++++++++ src/PaymentProtocol_p.h | 53 ++++++++++ src/main_utils.cpp | 7 ++ src/main_utils_android.cpp | 9 +- 15 files changed, 334 insertions(+), 50 deletions(-) create mode 100644 src/PaymentProtocol.cpp create mode 100644 src/PaymentProtocol.h create mode 100644 src/PaymentProtocol_p.h diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 1dc444f..0f9f6ff 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -31,6 +31,22 @@ Item { id: payment fiatPrice: Fiat.price account: portfolio.current + + Component.onCompleted: { + /* + The application can be started with a click on a payment link, + in that case the link gets made available in the following property + and we start a payment protocol with the value. + Afterwards we reset the property to avoid the next opening of this + screen repeating the payment. + */ + + var paymentProtcolUrl = Pay.paymentProtocolRequest; + if (paymentProtcolUrl !== "") { + payment.targetAddress = paymentProtcolUrl; + Pay.paymentProtocolRequestChanged = ""; + } + } } Rectangle { // background anchors.fill: parent diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 8830171..204bdef 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -48,6 +48,9 @@ ApplicationWindow { if (!portfolio.current.isUserOwned) { // Open on receive tab if the wallet is effectively empty tabbar.currentIndex = 2; } + else if (Pay.paymentProtocolRequestChanged !== "") { + tabbar.currentIndex = 1; + } else { tabbar.currentIndex = 0; } diff --git a/guis/desktop/org.flowee.pay.desktop b/guis/desktop/org.flowee.pay.desktop index 9e25f9e..d195357 100644 --- a/guis/desktop/org.flowee.pay.desktop +++ b/guis/desktop/org.flowee.pay.desktop @@ -6,7 +6,7 @@ Comment=Transact on the Bitcoin Cash network, payments and more Keywords=wallet;bitcoin;bitcoincash;financial; Type=Application Categories=Office;Finance; -Exec=pay +Exec=pay %u Terminal=false Icon=org.flowee.pay MimeType=x-scheme-handler/bitcoincash; diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index a4e17c3..c0382b9 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -69,7 +69,7 @@ Page { QRScanner { id: scanner scanType: QRScanner.PaymentDetails - autostart: true + autostart: Pay.paymentProtocolRequest === "" onFinished: { var rc = scanResult if (rc === "") { // scanning interrupted @@ -100,6 +100,24 @@ Page { autoPrepare: true instaPay: true + Component.onCompleted: { + /* + The application can be started with a click on a payment link, + in that case the link gets made available in the following property + and we start a payment protocol with the value. + Afterwards we reset the property to avoid the next opening of this + screen repeating the payment. + */ + + var paymentProtcolUrl = Pay.paymentProtocolRequest; + if (paymentProtcolUrl !== "") { + scanner.autostart = false; + payment.targetAddress = paymentProtcolUrl; + Pay.paymentProtocolRequest = ""; + root.allowEditAmount = payment.paymentAmount <= 0; + } + } + // easier testing values (for when you don't have a camera) // paymentAmount: 100000000 // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 710a69c..a02fba2 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -42,6 +42,8 @@ ApplicationWindow { thePile.replace("./MainView.qml"); if (!portfolio.current.isUserOwned) thePile.push("./StartupScreen.qml"); + else if (Pay.paymentProtocolRequest !== "") + thePile.push("./PayWithQR.qml") } } Component.onCompleted: updateFontSize(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5eeeff5..e2de775 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,8 @@ set (PAY_SOURCES PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp + + PaymentProtocol.cpp PortfolioDataProvider.cpp PriceDataProvider.cpp PriceHistoryDataProvider.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8183430..ded37dd 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -870,6 +870,19 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +QString FloweePay::paymentProtocolRequest() const +{ + return m_paymentProtocolRequest; +} + +void FloweePay::setPaymentProtocolRequest(const QString &ppr) +{ + if (m_paymentProtocolRequest == ppr) + return; + m_paymentProtocolRequest = ppr; + emit paymentProtocolRequestChanged(); +} + FloweePay::ApplicationProtection FloweePay::appProtection() const { return m_appProtection; diff --git a/src/FloweePay.h b/src/FloweePay.h index b7cee93..cbc64ae 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -69,6 +69,9 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa // notifications Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) + + // unique helper property.. + Q_PROPERTY(QString paymentProtocolRequest READ paymentProtocolRequest WRITE setPaymentProtocolRequest NOTIFY paymentProtocolRequestChanged FINAL) public: enum UnitOfBitcoin { BCH, @@ -311,6 +314,9 @@ public: ApplicationProtection appProtection() const; void setAppProtection(ApplicationProtection newAppProtection); + QString paymentProtocolRequest() const; + void setPaymentProtocolRequest(const QString &newPaymentProtocolRequest); + signals: void loadComplete(); /// \internal @@ -331,8 +337,8 @@ signals: void activityShowsBchChanged(); void totalBalanceConfigChanged(); void privateModeChanged(); - void appProtectionChanged(); + void paymentProtocolRequestChanged(); private slots: void loadingCompleted(); @@ -386,6 +392,9 @@ private: bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true + // the string passed in the app in order to instantly start paying. + QString m_paymentProtocolRequest; + #ifdef TARGET_OS_Android // when the app is no longer the front app we record the time in order to // know how much time has passed when we get active again. diff --git a/src/Payment.cpp b/src/Payment.cpp index 11fa765..43c46c2 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -24,6 +24,7 @@ #include "AccountInfo.h" #include "PriceDataProvider.h" #include "AccountConfig.h" +#include "PaymentProtocol.h" #include #include @@ -80,21 +81,12 @@ void Payment::setPaymentAmountFiat(int amount) void Payment::setTargetAddress(const QString &address) { - soleOut()->setAddress(address); - emit targetAddressChanged(); - if (m_autoPrepare) { - try { prepare(); } catch (...) {} - /* - * InstaPay is typically enabled together with auto-prepare and that gives - * great UX for, well, instantly paying. - * - * The above will have created AND send the transaction. - * BUT, if the prepare() failed, we should stop trying to do the 'instaPay'. - * It failed, now let the user decide when to send. - * - * Either way, we can set it to false now. - */ - m_allowInstaPay = false; + if (m_paymentDetails.size() == 1) { // payment protocols only allowed on empty tx + PaymentProtocol::create(this, address); + return; + } + else { + soleOut()->setAddress(address.trimmed()); } } diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index fb7845f..34304e5 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -96,39 +96,18 @@ const QString &PaymentDetailOutput::address() const return m_address; } -void PaymentDetailOutput::setAddress(const QString &address_) +void PaymentDetailOutput::setAddress(const QString &address) { - const QString addressOrURL = address_.trimmed(); - if (m_address == addressOrURL) + if (m_address == address) + return; + if (address.indexOf('?') >= 12) { + // this is a payment protocol, go via the Payment object to do the right thing. + Payment *p = qobject_cast(parent()); + assert(p); + p->setTargetAddress(address); return; - m_address = addressOrURL; - /* - * Users may paste an address that is really a payment url. - * This basically means we may have a price added after a questionmark. - * bitcoincash:qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45?amount=2.1 - */ - int urlStart = addressOrURL.indexOf('?'); - if (urlStart > 0) { - QUrl url(addressOrURL); - auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); - for (const auto &item : query.queryItems()) { - if (item.first == "amount") { - bool ok; - auto amount = item.second.toDouble(&ok); - if (ok) - setPaymentAmount(amount * 1E8); - } - else if (item.first == "label" || item.first == "message") { - // message goes on the main payment.. - Payment *p = qobject_cast(parent()); - assert(p); - p->setUserComment(item.second); - } - } - - m_address = addressOrURL.left(urlStart); } - + m_address = address; createFormattedAddress(); emit addressChanged(); // always emit at least once. } diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp new file mode 100644 index 0000000..7a9e235 --- /dev/null +++ b/src/PaymentProtocol.cpp @@ -0,0 +1,145 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "PaymentProtocol.h" +#include "PaymentProtocol_p.h" +#include "Payment.h" +#include "PaymentDetailOutput_p.h" + +#include +#include +#include + +PaymentProtocol::PaymentProtocol(Payment *payment) + : QObject{payment}, + m_payment(payment) +{ +} + +PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri) +{ + PaymentProtocol *pp = nullptr; + if (uri.length() < 20 || !uri.startsWith("bitcoincash:")) { + logWarning() << "Payment protocol unrecognized URI:" << uri; + return nullptr; + } + else if (uri.at(12) == '?' && uri.at(13) == 'r') { + pp = new PaymentProtocolBip70(target); + } + else if (uri.at(12) == 'q' || uri.at(12) == 'p') { + pp = new PaymentProtocolBip21(target); + } + else { + logWarning() << "Payment protocol missing support for uri:" << uri; + return nullptr; + } + assert(pp); + pp->setUri(uri); + pp->finished(); + return pp; +} + +void PaymentProtocol::finished() +{ + if (m_payment->autoPrepare()) { + try { m_payment->prepare(); } catch (...) {} + /* + * InstaPay is typically enabled together with auto-prepare and that gives + * great UX for, well, instantly paying. + * + * The above will have created AND send the transaction. + * BUT, if the prepare() failed, we should stop trying to do the 'instaPay'. + * It failed, now let the user decide when to send. + * + * Either way, we can set it to false now. + */ + m_payment->setAllowInstaPay(false); + } + deleteLater(); +} + +// ------------------------------------ + +PaymentProtocolBip21::PaymentProtocolBip21(Payment *payment) + : PaymentProtocol(payment) +{ + assert(payment); +} + +void PaymentProtocolBip21::setUri(const QString &uri) +{ + const QString addressOrURL = uri.trimmed(); + assert (m_payment->paymentDetails().size() == 1); // only one output + auto out = qobject_cast(m_payment->paymentDetails().at(0)); + assert(out); + + int urlStart = addressOrURL.indexOf('?'); + if (urlStart > 0) { + QUrl url(addressOrURL); + auto query = QUrlQuery(url.query(QUrl::FullyDecoded)); + for (const auto &item : query.queryItems()) { + if (item.first == "amount") { + bool ok; + auto amount = item.second.toDouble(&ok); + if (ok) + out->setPaymentAmount(amount * 1E8); + } + else if (item.first == "label" || item.first == "message") { + // message goes on the main payment.. + m_payment->setUserComment(item.second); + } + } + + out->setAddress(addressOrURL.left(urlStart)); + } + else { + out->setAddress(addressOrURL); + } + finished(); +} + +PaymentProtocolBip70::PaymentProtocolBip70(Payment *payment) + : PaymentProtocol(payment) +{ +} + +void PaymentProtocolBip70::setUri(const QString &uri) +{ + if (FloweePay::instance()->isOffline()) { + logCritical() << "App is offline, can't handle BIP70 payment"; + finished(); + return; + } + + assert(uri.startsWith("bitcoincash:?r=")); + // bitcoincash:?r=https:// + QUrl url(uri.mid(15)); + m_reply = m_network.get(QNetworkRequest(url)); + logInfo() << "fetching bip70 file from:" << url.toString(); + connect(m_reply, SIGNAL(finished()), this, SLOT(fetchedRequest())); +} + +void PaymentProtocolBip70::fetchedRequest() +{ + auto data = m_reply->readAll(); + QFile out("bip70Data"); + out.open(QIODevice::WriteOnly); + out.write(data); + + delete m_reply; + m_reply = nullptr; +} diff --git a/src/PaymentProtocol.h b/src/PaymentProtocol.h new file mode 100644 index 0000000..0eab635 --- /dev/null +++ b/src/PaymentProtocol.h @@ -0,0 +1,40 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef PAYMENTPROTOCOL_H +#define PAYMENTPROTOCOL_H + +#include + +class Payment; + +class PaymentProtocol : public QObject +{ + Q_OBJECT +public: + explicit PaymentProtocol(Payment *payment); + + static PaymentProtocol* create(Payment *target, const QString &uri); + +protected: + virtual void setUri(const QString &uri) = 0; + void finished(); + + Payment * const m_payment; +}; + +#endif diff --git a/src/PaymentProtocol_p.h b/src/PaymentProtocol_p.h new file mode 100644 index 0000000..3b8dfa8 --- /dev/null +++ b/src/PaymentProtocol_p.h @@ -0,0 +1,53 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef PAYMENTPROTOCOL_P_H +#define PAYMENTPROTOCOL_P_H + +#include "PaymentProtocol.h" + +#include +#include + +class Payment; + +class PaymentProtocolBip21 : public PaymentProtocol +{ + Q_OBJECT +public: + explicit PaymentProtocolBip21(Payment *payment); + + void setUri(const QString &uri) override; +}; + +class PaymentProtocolBip70 : public PaymentProtocol +{ + Q_OBJECT +public: + explicit PaymentProtocolBip70(Payment *payment); + + void setUri(const QString &uri) override; + +private slots: + void fetchedRequest(); + +private: + QNetworkAccessManager m_network; + QNetworkReply *m_reply = nullptr; +}; + +#endif diff --git a/src/main_utils.cpp b/src/main_utils.cpp index c29b39c..d798a62 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -46,6 +46,7 @@ struct CommandLineParserData parser.addOption(connect); parser.addOption(testnet4); parser.addOption(offline); + parser.addPositionalArgument("pay-data", "Payment URI", "[bitcoincash:/xxx]"); #ifndef NDEBUG // to protect people from the bad effect of having and later not having headers we only allow this // override in debug mode. @@ -59,6 +60,7 @@ struct CommandLineParserData FloweePay::selectChain(chain); if (parser.isSet(offline)) FloweePay::instance()->setOffline(true); + payRequest = parser.positionalArguments(); } QCommandLineParser parser; @@ -70,6 +72,8 @@ struct CommandLineParserData QCommandLineOption offline; QCommandLineOption headers; P2PNet::Chain chain = P2PNet::MainChain; + + QStringList payRequest; }; CommandLineParserData* createCLD(QGuiApplication &app) @@ -133,6 +137,8 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { FloweePay *app = FloweePay::instance(); + if (cld->payRequest.size() == 1) + app->setPaymentProtocolRequest(cld->payRequest.first()); NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); @@ -145,5 +151,6 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c app->p2pNet()->connectionManager().peerAddressDb().addOne( // add it to the DB, making sure there is at least one. EndPoint(cld->parser.value(cld->connect).toStdString(), 8333)); } + app->startNet(); // lets go! } diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 4c2046f..85850ce 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -28,11 +28,14 @@ struct CommandLineParserData { + QStringList payRequest; }; -CommandLineParserData* createCLD(QGuiApplication &) +CommandLineParserData* createCLD(QGuiApplication &app) { - return new CommandLineParserData(); + auto dat = new CommandLineParserData(); + dat->payRequest = app.arguments(); + return dat; } Log::Verbosity logVerbosity(CommandLineParserData*) @@ -97,6 +100,8 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) { FloweePay *app = FloweePay::instance(); + if (cld->payRequest.size() == 1) + app->setPaymentProtocolRequest(cld->payRequest.first()); NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); netData->startRefreshTimer(); -- 2.54.0 From fcf8e825114f4361dd72f350ffdd59a5268987f6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Aug 2023 15:33:35 +0200 Subject: [PATCH 0672/1428] Implement BIP70 (protocolbuffers) payment protocol This adds the basic support of bip70 style payments. Some todos are left which are mostly about interaction with other parts of the app and require bigger changes to make it work smoothly. --- src/Payment.cpp | 5 + src/Payment.h | 5 + src/PaymentDetailOutput_p.h | 2 + src/PaymentProtocol.cpp | 222 +++++++++++++++++++++++++++++++++--- src/PaymentProtocol_p.h | 21 ++++ 5 files changed, 242 insertions(+), 13 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 43c46c2..05688a3 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -421,6 +421,11 @@ void Payment::addDetail(PaymentDetail *detail) doAutoPrepare(); } +Tx Payment::tx() const +{ + return m_tx; +} + bool Payment::allowInstaPay() const { return m_allowInstaPay; diff --git a/src/Payment.h b/src/Payment.h index 8ecb9e5..c0bd2c0 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -165,6 +165,9 @@ public: Q_INVOKABLE PaymentDetail* addExtraOutput(); Q_INVOKABLE PaymentDetail* addInputSelector(); Q_INVOKABLE void remove(PaymentDetail *detail); + /** + * Unlock the account in order to allow funding of this payment. + */ Q_INVOKABLE void decrypt(const QString &password); /// return the txid, should there be a transaction (otherwise empty string) @@ -214,6 +217,8 @@ public: bool allowInstaPay() const; void setAllowInstaPay(bool allowIt); + Tx tx() const; + private slots: void sentToPeer(); void txRejected(short reason, const QString &message); diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 3a6706a..aeb8848 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -105,6 +105,8 @@ private: bool m_forceLegacyOk = false; QString m_address; QString m_formattedTarget; + + }; inline PaymentDetailOutput* PaymentDetail::toOutput() { diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 7a9e235..52b8d61 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -20,6 +20,10 @@ #include "Payment.h" #include "PaymentDetailOutput_p.h" +#include +#include +#include + #include #include #include @@ -34,7 +38,7 @@ PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri) { PaymentProtocol *pp = nullptr; if (uri.length() < 20 || !uri.startsWith("bitcoincash:")) { - logWarning() << "Payment protocol unrecognized URI:" << uri; + logWarning(10003) << "Payment protocol unrecognized URI:" << uri; return nullptr; } else if (uri.at(12) == '?' && uri.at(13) == 'r') { @@ -44,12 +48,11 @@ PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri) pp = new PaymentProtocolBip21(target); } else { - logWarning() << "Payment protocol missing support for uri:" << uri; + logWarning(10003) << "Payment protocol missing support for uri:" << uri; return nullptr; } assert(pp); pp->setUri(uri); - pp->finished(); return pp; } @@ -120,26 +123,219 @@ PaymentProtocolBip70::PaymentProtocolBip70(Payment *payment) void PaymentProtocolBip70::setUri(const QString &uri) { if (FloweePay::instance()->isOffline()) { - logCritical() << "App is offline, can't handle BIP70 payment"; + logCritical(10003) << "App is offline, can't handle BIP70 payment"; finished(); return; } + // uri typically starts with: bitcoincash:?r=https:// assert(uri.startsWith("bitcoincash:?r=")); - // bitcoincash:?r=https:// - QUrl url(uri.mid(15)); - m_reply = m_network.get(QNetworkRequest(url)); - logInfo() << "fetching bip70 file from:" << url.toString(); + QNetworkRequest req(uri.mid(15)); + req.setRawHeader("accept", "application/bitcoincash-paymentrequest"); // binary protocol + req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); + // accept: application/payment-request (JPP) + m_reply = m_network.get(req); + logInfo(10003) << "fetching bip70 file from:" << req.url().toString(); connect(m_reply, SIGNAL(finished()), this, SLOT(fetchedRequest())); + connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), + this, SLOT(errored(QNetworkReply::NetworkError))); + connect(m_reply, SIGNAL(sslErrors(QList)), + this, SLOT(sslErrors(QList))); } void PaymentProtocolBip70::fetchedRequest() { - auto data = m_reply->readAll(); - QFile out("bip70Data"); - out.open(QIODevice::WriteOnly); - out.write(data); - + m_pool.reserve(m_reply->size()); + logInfo(10003) << "BIP 70 request fetched, payload:" << m_reply->size(); + auto readBytes = m_reply->read(m_pool.data(), m_pool.capacity()); + auto buf = m_pool.commit(readBytes); delete m_reply; m_reply = nullptr; + + // PaymentRequest with embedded PaymentDetails / Outputs + Streaming::ProtoParser parser(buf); + bool inDetails = false; + bool inOutput = false; + Output currentOutput; + while (true) { + const auto parsed = parser.next(); + if (parsed == Streaming::EndOfDocument) { + if (inOutput) + inOutput = false; + else if (inDetails) + inDetails = false; + else + break; + assert (parser.depth() > 0); + parser.closeData(); + continue; + } + else if (parsed == Streaming::Error) { + logCritical(10003) << "failed parsing the protobuf"; + // TODO check if its a different format, or an error message? + logCritical(10003) << buf.toString(); + return; + } + + if (inOutput) { + switch (parser.tag()) { + case 1: + currentOutput.amount = parser.longData(); + break; + case 2: + currentOutput.script = parser.bytesDataBuffer(); + m_outputs.push_back(currentOutput); + break; + } + } + else if (inDetails) { + switch (parser.tag()) { + case 1: // chain + if (parser.stringData() != "main") { + logCritical(10003) << "Payment request for not mainchain. Ignoring"; + return; + } + break; + case 2: // outputs + inOutput = true; + parser.enterData(); + currentOutput = Output(); + break; + case 3: // time + m_requestCreated = QDateTime::fromSecsSinceEpoch(parser.longData()); + break; + case 4: // expires + m_requestExpiry = QDateTime::fromSecsSinceEpoch(parser.longData()); + break; + case 5: // memo + m_memo = QString::fromUtf8(parser.stringData()); + break; + case 6: // memo + m_replyUrl = QString::fromUtf8(parser.stringData()); + break; + case 7: // merchant data + m_merchantData = parser.bytesDataBuffer(); + break; + } + } + else { + switch (parser.tag()) { + case 4: + inDetails = true; + parser.enterData(); + break; + } + } + } + + logDebug(10003) << "Received a request for" << m_outputs.size() << "outputs"; + uint64_t amount = 0; + for (const auto &o : m_outputs) + amount += o.amount; + logDebug(10003) << " total bch:" << amount; + logDebug(10003) << "Reply to" << m_replyUrl; + logDebug(10003) << "Created:" << m_requestCreated.toString(); + logDebug(10003) << "Expires:" << m_requestExpiry.toString(); + if (m_requestExpiry < QDateTime::currentDateTimeUtc()) + logWarning(10003) << " + expired"; + + for (const auto &out : m_outputs) { + auto simple = CashAddress::extractAddressFromScript(out.script); + if (simple.hash.empty()) { + // TODO invent new payment output type + logFatal(10003) << "Not an address"; + assert(false); + } else { + auto address = CashAddress::encodeCashAddr(chainPrefix(), simple); + logDebug(10003) << "Payment to" << address; + if (m_outputs.size() == 1) { + // we place the data directly on the payment object because that + // is the most compatible for consumers of its properties. + m_payment->setTargetAddress(QString::fromLatin1(address)); + m_payment->setPaymentAmount(out.amount); + } + else { + auto *addressDetail = m_payment->addExtraOutput()->toOutput(); + addressDetail->setAddress(QString::fromLatin1(address)); + addressDetail->setFiatFollows(true); + addressDetail->setPaymentAmount(out.amount); + } + } + } + + // TODO instead of this, we should be triggerd on the user askign the broadcast the transaction. + if (m_payment->autoPrepare()) { + try { m_payment->prepare(); } catch (...) {} + m_payment->setAllowInstaPay(false); + sendReply(); + } + else { + // then we have to wait for the user to Ok it and only after it has been prepared + // do we send it. + connect (m_payment, &Payment::txPreparedChanged, this, [=]() { + if (m_payment->txPrepared()) + sendReply(); + }); + } +} + +void PaymentProtocolBip70::sentTransaction() +{ + m_pool.reserve(m_reply->size()); + logInfo(10003) << "ACK received. Payload size:" << m_reply->size(); + auto readBytes = m_reply->read(m_pool.data(), m_pool.capacity()); + auto data = m_pool.commit(readBytes); + delete m_reply; + m_reply = nullptr; + + Streaming::ProtoParser::debug(data); + logFatal(10003) << data.toString(); +} + +void PaymentProtocolBip70::errored(QNetworkReply::NetworkError err) +{ + logFatal(10003) << "Error: " << err; + logFatal(10003) << m_reply->errorString(); + if (m_reply->error() == 302) { + logFatal(10003) << " to:" << m_reply->header(QNetworkRequest::LocationHeader).toString(); + } +} + +void PaymentProtocolBip70::sslErrors(const QList &errors) +{ + logFatal(10003) << "multiple errors:" << errors.size(); + for (const auto &e : errors) { + logFatal(10003) << " " << e.errorString(); + } +} + +void PaymentProtocolBip70::sendReply() +{ + // TODO check if we have not passed the expired time + assert(m_payment->txPrepared()); + assert(!m_replyUrl.isEmpty()); + auto tx = m_payment->tx(); + + m_pool.reserve(tx.size() + m_merchantData.size() + 40); + Streaming::ProtoBuilder builder(m_pool); + if (!m_merchantData.isEmpty()) + builder.add(1, m_merchantData); + builder.add(2, tx.data()); + auto data = builder.buffer(); + logDebug() << " reply data size:" << data.size(); + + // unfortunately, Qt uses its own data. So another copy is needed. + QByteArray messageData(data.begin(), data.size()); + assert(messageData.size() == data.size()); + QNetworkRequest req(m_replyUrl); + req.setRawHeader("accept", "application/bitcoincash-paymentack"); + req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); + req.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoincash-payment"); + m_reply = m_network.post(req, messageData); + logInfo(10003) << "Sending bip70 reply to:" << req.url().toString(); + connect(m_reply, SIGNAL(finished()), this, SLOT(sentTransaction())); + connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), + this, SLOT(errored(QNetworkReply::NetworkError))); + connect(m_reply, SIGNAL(sslErrors(QList)), + this, SLOT(sslErrors(QList))); } diff --git a/src/PaymentProtocol_p.h b/src/PaymentProtocol_p.h index 3b8dfa8..d9fee1c 100644 --- a/src/PaymentProtocol_p.h +++ b/src/PaymentProtocol_p.h @@ -23,6 +23,8 @@ #include #include +#include + class Payment; class PaymentProtocolBip21 : public PaymentProtocol @@ -44,10 +46,29 @@ public: private slots: void fetchedRequest(); + void sentTransaction(); + void errored(QNetworkReply::NetworkError err); + void sslErrors(const QList &errors); + + void sendReply(); private: QNetworkAccessManager m_network; QNetworkReply *m_reply = nullptr; + Streaming::BufferPool m_pool; + + QDateTime m_requestCreated; + QDateTime m_requestExpiry; + + QString m_memo; + QString m_replyUrl; + Streaming::ConstBuffer m_merchantData; + + struct Output { + uint64_t amount = 0; + Streaming::ConstBuffer script; + }; + std::vector m_outputs; }; #endif -- 2.54.0 From 69e0fe8869c717fe167c6685bba3730ac9c710a5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 2 Sep 2023 20:23:29 +0200 Subject: [PATCH 0673/1428] Finish up BIP70 feature Squash of 12 commits, this makes the UX actually useful and we detect errors and show errors and warnings to the user. This also adds an 'editable' bool to OutputDetail in order to disable editing of a payment request address. Additionally, this introduces the ability for a Payment to carry a raw script instead of an address. So op-return or more complex payment requests will be sent just fine. Fun fact is that our broadcast is INV/SendData, which is two roundtrips and thus slow, which is needed to be sure we get proper rejection messages. The sending of the transaction via BIP70 causes the network to already know about the tx because the server we send it to has likely much better Internet than we do. So confirmation from the payment provider is used to show success instead of the normal way. --- guis/desktop/SendTransactionPane.qml | 22 +++- guis/mobile/PayWithQR.qml | 54 ++++++++- modules/build-transaction/PayToOthers.qml | 3 +- src/Payment.cpp | 111 ++++++++++++++---- src/Payment.h | 32 +++++- src/PaymentDetailOutput.cpp | 31 +++++ src/PaymentDetailOutput_p.h | 21 +++- src/PaymentProtocol.cpp | 133 ++++++++++++++++------ src/PaymentProtocol.h | 4 +- src/PaymentProtocol_p.h | 5 +- src/main_utils_android.cpp | 2 +- 11 files changed, 343 insertions(+), 75 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 0f9f6ff..9036a6b 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -44,7 +44,7 @@ Item { var paymentProtcolUrl = Pay.paymentProtocolRequest; if (paymentProtcolUrl !== "") { payment.targetAddress = paymentProtcolUrl; - Pay.paymentProtocolRequestChanged = ""; + Pay.paymentProtocolRequest = ""; } } } @@ -158,6 +158,21 @@ Item { payment.prepare(); } } + Flowee.Dialog { + title: qsTr("Warning") + standardButtons: DialogButtonBox.Ok + text: { + var warnings = payment.warnings + if (warnings.length === 0) + return ""; + + return qsTr("Payment Request returned with:") + + "\n" + warnings.join("\n"); + } + visible: text !== "" + + onAccepted: payment.clearWarnings(); + } } Flowee.WarningLabel { id: warningLabel @@ -248,7 +263,7 @@ Item { && prepareButton.portfolioUsed === portfolio.current; // also make sure we prepared for the current portfolio. onCanSendChanged: setEnabled(DialogButtonBox.Ok, canSend) onRejected: payment.reset(); - onAccepted: payment.broadcast(); + onAccepted: payment.markUserApproved(); } } } @@ -325,6 +340,7 @@ Item { return mainWindow.errorRed return palette.windowText } + enabled: paymentDetail.editable } Label { color: "green" @@ -583,7 +599,7 @@ Item { coinsListView.menuIsOpen = false return; } - if (mouse.button == Qt.LeftButton) { + if (mouse.button === Qt.LeftButton) { var willCheck = !selectedBox.checked selectedBox.checked = willCheck inputsPane.paymentDetail.setRowIncluded(index, willCheck) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index c0382b9..e2e0c0e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -119,6 +119,7 @@ Page { } // easier testing values (for when you don't have a camera) + // ps. in case of testing, you want above instaPay: property => false!! // paymentAmount: 100000000 // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" // userComment: "bla bla bla" @@ -338,10 +339,61 @@ Page { anchors.bottomMargin: 10 width: parent.width enabled: payment.isValid && payment.txPrepared - onActivated: payment.broadcast() + onActivated: payment.markUserApproved() visible: payment.account.isDecrypted || !payment.account.needsPinToPay } + Flickable { + anchors.fill: parent + contentWidth: width + contentHeight: warningsColumn.implicitHeight + + Column { + id: warningsColumn + width: parent.width + Repeater { + model: payment.warnings + + Rectangle { + y: 8 + width: root.width - 16 + height: Math.max(75, Math.max(warningIcon.height, warningText.height) + 20) + radius: 20 + color: palette.alternateBase + border.width: 1 + border.color: palette.midlight + + Rectangle { // placeholder icon + id: warningIcon + x: 20 + width: 40 + height: 40 + radius: 20 + color: mainWindow.errorRedBg + anchors.verticalCenter: parent.verticalCenter + } + + Flowee.Label { + id: warningText + text: modelData + wrapMode: Text.Wrap + anchors.left: warningIcon.right + anchors.leftMargin: 10 + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.verticalCenter: parent.verticalCenter + } + Flowee.CloseIcon { + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 10 + onClicked: payment.clearWarnings(); + } + } + } + } + } + Item { id: decryptButton visible: !slideToApprove.visible diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 579394d..58ea45f 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -271,10 +271,11 @@ Page { anchors.left: parent.left anchors.right: parent.right height: Math.max(destinationLabel.height * 2.3, implicitHeight) - focus: true + focus: enabled property var addressType: Pay.identifyString(text); text: paymentDetail.address nextFocusTarget: priceInput + enabled: paymentDetail.editable onTextChanged: { paymentDetail.address = text addressInfo.createInfo(); diff --git a/src/Payment.cpp b/src/Payment.cpp index 05688a3..828ce34 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -82,7 +82,7 @@ void Payment::setPaymentAmountFiat(int amount) void Payment::setTargetAddress(const QString &address) { if (m_paymentDetails.size() == 1) { // payment protocols only allowed on empty tx - PaymentProtocol::create(this, address); + PaymentProtocol::create(this, address, m_paymentDetails.at(0)); return; } else { @@ -127,8 +127,7 @@ void Payment::decrypt(const QString &password) emit errorChanged(); } if (!m_account->decrypt(password)) { - m_error = tr("Invalid PIN"); - emit errorChanged(); + forwardError(tr("Invalid PIN")); } doAutoPrepare(); } @@ -181,22 +180,27 @@ void Payment::prepare() totalOut += o->paymentAmount(); builder.appendOutput(o->paymentAmount()); - bool ok = false; - auto address = o->formattedTarget(); - if (address.isEmpty()) - address = o->address(); - assert(!address.isEmpty()); - CashAddress::Content c = CashAddress::decodeCashAddrContent(address.toStdString(), chainPrefix()); - assert(!c.hash.empty()); - if (c.type == CashAddress::PUBKEY_TYPE) { - builder.pushOutputPay2Address(KeyId(reinterpret_cast(c.hash.data()))); - ok = true; + if (!o->outputScript().isEmpty()) { + builder.pushOutputScript(o->outputScript()); } - else if (c.type == CashAddress::SCRIPT_TYPE) { - builder.pushOutputPay2Script(CScriptID(reinterpret_cast(c.hash.data()))); - ok = true; + else { + bool ok = false; + auto address = o->formattedTarget(); + if (address.isEmpty()) + address = o->address(); + assert(!address.isEmpty()); + CashAddress::Content c = CashAddress::decodeCashAddrContent(address.toStdString(), chainPrefix()); + assert(!c.hash.empty()); + if (c.type == CashAddress::PUBKEY_TYPE) { + builder.pushOutputPay2Address(KeyId(reinterpret_cast(c.hash.data()))); + ok = true; + } + else if (c.type == CashAddress::SCRIPT_TYPE) { + builder.pushOutputPay2Script(CScriptID(reinterpret_cast(c.hash.data()))); + ok = true; + } + assert(ok); // mismatch between PaymentDetailOutput::setAddress and this method... } - assert(ok); // mismatch between PaymentDetailOutput::setAddress and this method... } else if (detail->type() == InputSelector) { inputs = detail->toInputs(); @@ -212,8 +216,7 @@ void Payment::prepare() funding = inputs->selectedInputs(seenMaxAmount ? -1 : totalOut, m_fee, tx.size(), change); if (funding.outputs.empty()) { // not enough funds. // This can only be due to fees as we called 'verify' above. - m_error = tr("Not enough funds selected for fees"); - emit errorChanged(); + forwardError(tr("Not enough funds selected for fees")); m_txPrepared = false; emit txPreparedChanged(); return; @@ -222,8 +225,7 @@ void Payment::prepare() else { funding = m_wallet->findInputsFor(seenMaxAmount ? -1 : totalOut, m_fee, tx.size(), change); if (funding.outputs.empty()) { // not enough funds. - m_error = tr("Not enough funds in wallet to make payment!"); - emit errorChanged(); + forwardError(tr("Not enough funds in wallet to make payment!")); m_txPrepared = false; emit txPreparedChanged(); return; @@ -287,8 +289,7 @@ void Payment::prepare() } if (m_tx.size() > 100000) { // max size of a transaction is 100KB - m_error = tr("Transaction too large. Amount selected needs too many coins."); - emit errorChanged(); + forwardError(tr("Transaction too large. Amount selected needs too many coins.")); return; } @@ -332,13 +333,23 @@ void Payment::prepare() if (output->paymentAmountFiat() > limit) return; - // schedule broadcast in a different event in order to + // schedule markUserApproved in a different event in order to // allow multiple changes and prepare()s to happen and // only send the best version to the network. - QTimer::singleShot(50, this, SLOT(broadcast())); + QTimer::singleShot(50, this, SLOT(markUserApproved())); } } +void Payment::markUserApproved() +{ + assert(m_txPrepared); + // give external factors the opportunity to object. + m_error.clear(); + emit approvedByUser(); + if (m_error.isEmpty()) // nobody objected. + broadcast(); +} + void Payment::broadcast() { if (!m_txPrepared || m_txBroadcastStarted) @@ -426,6 +437,56 @@ Tx Payment::tx() const return m_tx; } +void Payment::forwardError(const QString &error) +{ + if (m_error == error) + return; + m_error = error; + emit errorChanged(); +} + +void Payment::addWarning(Warning warning) +{ + if (m_warnings.contains(warning)) + return; + m_warnings.insert(warning); + emit warningsChanged(); +} + +QStringList Payment::warnings() const +{ + QStringList answer; + for (auto w : m_warnings) { + switch (w) { + case InsecureTransport: + answer.append(tr("Request received over insecure channel. Anyone could have altered it!")); + break; + case DownloadFailed: + answer.append(tr("Download of payment request Failed.")); + break; + } + } + return answer; +} + +void Payment::clearWarnings() +{ + if (m_warnings.isEmpty()) + return; + m_warnings.clear(); + emit warningsChanged(); +} + +void Payment::confirmReceivedOk() +{ + if (m_infoObject.get() == nullptr) + return; + m_infoObject.reset(); + m_sentPeerCount = 1; + m_rejectedPeerCount = 0; + emit broadcastStatusChanged(); +} + bool Payment::allowInstaPay() const { return m_allowInstaPay; diff --git a/src/Payment.h b/src/Payment.h index c0bd2c0..58d82ab 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -20,6 +20,7 @@ #include +#include #include #include @@ -68,6 +69,9 @@ class Payment : public QObject /// If prepare() failed, this is set. Q_PROPERTY(QString error READ error NOTIFY errorChanged) + Q_PROPERTY(QStringList warnings READ warnings NOTIFY warningsChanged) + + Q_ENUMS(DetailType BroadcastStatus) public: enum DetailType { @@ -102,6 +106,12 @@ public: TxRejected //< Tx has been offered, downloaded and rejected by at least one peer. }; + enum Warning { + InsecureTransport, + DownloadFailed + }; + Q_ENUM(Warning); + Payment(QObject *parent = nullptr); void setFeePerByte(int sats); @@ -160,7 +170,7 @@ public: const QString &error() const; Q_INVOKABLE void prepare(); - Q_INVOKABLE void broadcast(); + Q_INVOKABLE void markUserApproved(); Q_INVOKABLE void reset(); Q_INVOKABLE PaymentDetail* addExtraOutput(); Q_INVOKABLE PaymentDetail* addInputSelector(); @@ -219,10 +229,26 @@ public: Tx tx() const; + /* + * When an issue occurred with a consumer of this class and the error should be shown to the user. + */ + void forwardError(const QString &error); + + /** + * When an warning occurred with a consumer of this class and the warning should be shown to the user. + */ + void addWarning(Warning warning); + QStringList warnings() const; + Q_INVOKABLE void clearWarnings(); + + /// Bypass the broadcast mechanism and mark the transaction as received. + void confirmReceivedOk(); + private slots: void sentToPeer(); void txRejected(short reason, const QString &message); void recalcAmounts(); + void broadcast(); signals: void feePerByteChanged(); @@ -242,6 +268,9 @@ signals: void walletPinChanged(); void autoPrepareChanged(); void allowInstaPayChanged(); + void warningsChanged(); + + void approvedByUser(); private: void doAutoPrepare(); @@ -255,6 +284,7 @@ private: AccountInfo *m_account = nullptr; // Payment Variable initialization in reset() please + QSet m_warnings; QList m_paymentDetails; bool m_autoPrepare = false; bool m_txPrepared; diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 34304e5..05c95e6 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -108,12 +108,20 @@ void PaymentDetailOutput::setAddress(const QString &address) return; } m_address = address; + m_outputScript.clear(); createFormattedAddress(); emit addressChanged(); // always emit at least once. } void PaymentDetailOutput::createFormattedAddress() { + if (m_address.isEmpty() && !m_outputScript.isEmpty()) { + m_formattedTarget.clear(); + checkValid(); + emit correctAddressChanged(); // the formatted target is tied to this signal + return; + } + const std::string &chainPrefixCopy = chainPrefix(); std::string encodedAddress; @@ -204,6 +212,14 @@ void PaymentDetailOutput::setWallet(Wallet *) } } +void PaymentDetailOutput::setEditable(bool on) +{ + if (m_editable == on) + return; + m_editable = on; + emit editableChanged(); +} + bool PaymentDetailOutput::maxSelected() const { return m_maxSelected; @@ -291,6 +307,21 @@ const QString &PaymentDetailOutput::formattedTarget() const return m_formattedTarget; } +void PaymentDetailOutput::setOutputScript(const Streaming::ConstBuffer &script) +{ + if (m_outputScript == script) + return; + m_outputScript = script; + m_address.clear(); + createFormattedAddress(); + emit addressChanged(); // always emit at least once. +} + +Streaming::ConstBuffer PaymentDetailOutput::outputScript() const +{ + return m_outputScript; +} + QString PaymentDetailOutput::niceAddress() const { auto a(m_formattedTarget); diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index aeb8848..2da959d 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -39,18 +39,27 @@ class PaymentDetailOutput : public PaymentDetail Q_PROPERTY(bool fiatFollows READ fiatFollows WRITE setFiatFollows NOTIFY fiatFollowsChanged) Q_PROPERTY(bool maxSelected READ maxSelected WRITE setMaxSelected NOTIFY maxSelectedChanged) Q_PROPERTY(bool forceLegacyOk READ forceLegacyOk WRITE setForceLegacyOk NOTIFY forceLegacyOkChanged) + /** + * An output created by a payment protocol is not editable by users. + */ + Q_PROPERTY(bool editable READ editable NOTIFY editableChanged) public: explicit PaymentDetailOutput(Payment *parent); double paymentAmount() const; void setPaymentAmount(double newPaymentAmount); - /// this method also sets formattedTarget if its a proper address. + /// this method also sets formattedTarget if it's a proper address. /// @see FloweePay::identifyString() + /// @see setOutputScript() const QString &address() const; void setAddress(const QString &newAddress); /// is non-empty if the address() is proper. const QString &formattedTarget() const; + + void setOutputScript(const Streaming::ConstBuffer &script); + Streaming::ConstBuffer outputScript() const; + /** * The nicest to display address version. * This will always return (if available) the BCH style (cash-address) address, without @@ -82,6 +91,11 @@ public: void setWallet(Wallet *wallet) override; + bool editable() const { + return m_editable; + } + void setEditable(bool on); + signals: void paymentAmountChanged(); void paymentAmountFiatChanged(); @@ -91,6 +105,7 @@ signals: void maxSelectedChanged(); void forceLegacyOkChanged(); void maxAllowedChanged(); + void editableChanged(); private: void checkValid(); @@ -103,10 +118,10 @@ private: bool m_maxSelected = false; bool m_addressOk = false; bool m_forceLegacyOk = false; + bool m_editable = true; QString m_address; QString m_formattedTarget; - - + Streaming::ConstBuffer m_outputScript; }; inline PaymentDetailOutput* PaymentDetail::toOutput() { diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 52b8d61..284ec23 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -28,13 +28,14 @@ #include #include -PaymentProtocol::PaymentProtocol(Payment *payment) - : QObject{payment}, +PaymentProtocol::PaymentProtocol(Payment *payment, QObject *parent) + : QObject{parent}, m_payment(payment) { + assert(payment); } -PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri) +PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri, QObject *parent) { PaymentProtocol *pp = nullptr; if (uri.length() < 20 || !uri.startsWith("bitcoincash:")) { @@ -42,10 +43,10 @@ PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri) return nullptr; } else if (uri.at(12) == '?' && uri.at(13) == 'r') { - pp = new PaymentProtocolBip70(target); + pp = new PaymentProtocolBip70(target, parent); } else if (uri.at(12) == 'q' || uri.at(12) == 'p') { - pp = new PaymentProtocolBip21(target); + pp = new PaymentProtocolBip21(target, parent); } else { logWarning(10003) << "Payment protocol missing support for uri:" << uri; @@ -77,10 +78,9 @@ void PaymentProtocol::finished() // ------------------------------------ -PaymentProtocolBip21::PaymentProtocolBip21(Payment *payment) - : PaymentProtocol(payment) +PaymentProtocolBip21::PaymentProtocolBip21(Payment *payment, QObject *parent) + : PaymentProtocol(payment, parent) { - assert(payment); } void PaymentProtocolBip21::setUri(const QString &uri) @@ -115,13 +115,21 @@ void PaymentProtocolBip21::setUri(const QString &uri) finished(); } -PaymentProtocolBip70::PaymentProtocolBip70(Payment *payment) - : PaymentProtocol(payment) +PaymentProtocolBip70::PaymentProtocolBip70(Payment *payment, QObject *parent) + : PaymentProtocol(payment, parent) { } void PaymentProtocolBip70::setUri(const QString &uri) { + /* + * Make sure that we account for the async nature of bip70 and also the fact + * that the bip70 usage implies that the user should not edit the amount. + * So, we set a dummy amount in the payment in order to make the UI aware + * of the fact that the amount is pre-filled. + */ + m_payment->setPaymentAmount(1); + if (FloweePay::instance()->isOffline()) { logCritical(10003) << "App is offline, can't handle BIP70 payment"; finished(); @@ -145,14 +153,28 @@ void PaymentProtocolBip70::setUri(const QString &uri) void PaymentProtocolBip70::fetchedRequest() { + m_payment->setPaymentAmount(0); // reset to zero + m_pool.reserve(m_reply->size()); logInfo(10003) << "BIP 70 request fetched, payload:" << m_reply->size(); auto readBytes = m_reply->read(m_pool.data(), m_pool.capacity()); + if (readBytes == -1) { + m_payment->addWarning(Payment::DownloadFailed); + return; + } auto buf = m_pool.commit(readBytes); + + const auto ssl = m_reply->sslConfiguration(); + if (ssl.isNull() || ssl.sessionProtocol() < QSsl::TlsV1_3) { + m_payment->setAllowInstaPay(false); // require user approval + m_payment->addWarning(Payment::InsecureTransport); + } + + const auto contentType = m_reply->header(QNetworkRequest::ContentTypeHeader); delete m_reply; m_reply = nullptr; - // PaymentRequest with embedded PaymentDetails / Outputs + // protocol buffers PaymentRequest with embedded PaymentDetails / Outputs Streaming::ProtoParser parser(buf); bool inDetails = false; bool inOutput = false; @@ -172,8 +194,19 @@ void PaymentProtocolBip70::fetchedRequest() } else if (parsed == Streaming::Error) { logCritical(10003) << "failed parsing the protobuf"; - // TODO check if its a different format, or an error message? logCritical(10003) << buf.toString(); + auto mime(contentType.toString()); + int semicolon = mime.indexOf(';'); + if (semicolon > 0) + mime = mime.left(semicolon); + if ((mime == "text/html" || mime == "text/plain") && buf.size() < 100) { + auto str = buf.toString(); + if (str.find('<') == std::string::npos) { + m_payment->forwardError(QString::fromLatin1(str)); + return; + } + } + m_payment->forwardError(tr("Payment Request unreadable")); return; } @@ -192,6 +225,8 @@ void PaymentProtocolBip70::fetchedRequest() switch (parser.tag()) { case 1: // chain if (parser.stringData() != "main") { + // lets not translate this, its unlikely to ever happen. + m_payment->forwardError("not for mainchain"); logCritical(10003) << "Payment request for not mainchain. Ignoring"; return; } @@ -210,7 +245,7 @@ void PaymentProtocolBip70::fetchedRequest() case 5: // memo m_memo = QString::fromUtf8(parser.stringData()); break; - case 6: // memo + case 6: // reply URL m_replyUrl = QString::fromUtf8(parser.stringData()); break; case 7: // merchant data @@ -236,15 +271,21 @@ void PaymentProtocolBip70::fetchedRequest() logDebug(10003) << "Reply to" << m_replyUrl; logDebug(10003) << "Created:" << m_requestCreated.toString(); logDebug(10003) << "Expires:" << m_requestExpiry.toString(); - if (m_requestExpiry < QDateTime::currentDateTimeUtc()) + if (m_requestExpiry < QDateTime::currentDateTimeUtc()) { logWarning(10003) << " + expired"; + m_payment->forwardError(tr("Payment Request Expired")); + } + m_payment->setUserComment(m_memo); for (const auto &out : m_outputs) { auto simple = CashAddress::extractAddressFromScript(out.script); if (simple.hash.empty()) { - // TODO invent new payment output type - logFatal(10003) << "Not an address"; - assert(false); + logInfo(10003) << "Output is not an address. Using bytearray instead"; + auto *addressDetail = m_payment->addExtraOutput()->toOutput(); + addressDetail->setOutputScript(out.script); + addressDetail->setFiatFollows(true); + addressDetail->setPaymentAmount(out.amount); + addressDetail->setEditable(false); } else { auto address = CashAddress::encodeCashAddr(chainPrefix(), simple); logDebug(10003) << "Payment to" << address; @@ -253,29 +294,38 @@ void PaymentProtocolBip70::fetchedRequest() // is the most compatible for consumers of its properties. m_payment->setTargetAddress(QString::fromLatin1(address)); m_payment->setPaymentAmount(out.amount); + assert(m_payment->paymentDetails().size() == 1); + auto *first = m_payment->paymentDetails().at(0); + assert(first); + auto *out = qobject_cast(first); + assert(out); + out->setEditable(false); } else { auto *addressDetail = m_payment->addExtraOutput()->toOutput(); addressDetail->setAddress(QString::fromLatin1(address)); addressDetail->setFiatFollows(true); addressDetail->setPaymentAmount(out.amount); + addressDetail->setEditable(false); } } } - // TODO instead of this, we should be triggerd on the user askign the broadcast the transaction. + // then we have to wait for the user to Ok it and only after broadcast started, + // we send the transaction also to the payment processor. + connect (m_payment, &Payment::approvedByUser, this, [=]() { + assert(m_payment->txPrepared()); + if (m_requestExpiry < QDateTime::currentDateTimeUtc()) { + m_payment->forwardError(tr("Payment Request Expired")); + return; + } + sendReply(); + }); + + // Help the payment object start the broadcast if its properties automate this. if (m_payment->autoPrepare()) { try { m_payment->prepare(); } catch (...) {} - m_payment->setAllowInstaPay(false); - sendReply(); - } - else { - // then we have to wait for the user to Ok it and only after it has been prepared - // do we send it. - connect (m_payment, &Payment::txPreparedChanged, this, [=]() { - if (m_payment->txPrepared()) - sendReply(); - }); + m_payment->setAllowInstaPay(false); // avoid instapay if the prepare failed. } } @@ -287,31 +337,42 @@ void PaymentProtocolBip70::sentTransaction() auto data = m_pool.commit(readBytes); delete m_reply; m_reply = nullptr; - - Streaming::ProtoParser::debug(data); - logFatal(10003) << data.toString(); + m_payment->confirmReceivedOk(); + deleteLater(); } void PaymentProtocolBip70::errored(QNetworkReply::NetworkError err) { - logFatal(10003) << "Error: " << err; - logFatal(10003) << m_reply->errorString(); + logCritical(10003) << m_reply->errorString(); if (m_reply->error() == 302) { logFatal(10003) << " to:" << m_reply->header(QNetworkRequest::LocationHeader).toString(); } + else if (m_reply->error() == QNetworkReply::SslHandshakeFailedError) { + m_payment->setAllowInstaPay(false); + m_payment->addWarning(Payment::InsecureTransport); + } + else if (m_reply->error() <= 99) { + // covers all the remote network errors. + // notify the user why nothing is happening. + m_payment->addWarning(Payment::DownloadFailed); + } } void PaymentProtocolBip70::sslErrors(const QList &errors) { - logFatal(10003) << "multiple errors:" << errors.size(); + m_payment->setAllowInstaPay(false); + m_payment->addWarning(Payment::InsecureTransport); + logCritical(10003) << "multiple errors:" << errors.size(); for (const auto &e : errors) { - logFatal(10003) << " " << e.errorString(); + logCritical(10003) << " " << e.errorString(); } } void PaymentProtocolBip70::sendReply() { - // TODO check if we have not passed the expired time + if (m_sent) + return; + assert(m_payment->txPrepared()); assert(!m_replyUrl.isEmpty()); auto tx = m_payment->tx(); diff --git a/src/PaymentProtocol.h b/src/PaymentProtocol.h index 0eab635..c9ae61e 100644 --- a/src/PaymentProtocol.h +++ b/src/PaymentProtocol.h @@ -26,9 +26,9 @@ class PaymentProtocol : public QObject { Q_OBJECT public: - explicit PaymentProtocol(Payment *payment); + explicit PaymentProtocol(Payment *payment, QObject *parent); - static PaymentProtocol* create(Payment *target, const QString &uri); + static PaymentProtocol* create(Payment *target, const QString &uri, QObject *parent); protected: virtual void setUri(const QString &uri) = 0; diff --git a/src/PaymentProtocol_p.h b/src/PaymentProtocol_p.h index d9fee1c..6701506 100644 --- a/src/PaymentProtocol_p.h +++ b/src/PaymentProtocol_p.h @@ -31,7 +31,7 @@ class PaymentProtocolBip21 : public PaymentProtocol { Q_OBJECT public: - explicit PaymentProtocolBip21(Payment *payment); + explicit PaymentProtocolBip21(Payment *payment, QObject *parent); void setUri(const QString &uri) override; }; @@ -40,7 +40,7 @@ class PaymentProtocolBip70 : public PaymentProtocol { Q_OBJECT public: - explicit PaymentProtocolBip70(Payment *payment); + explicit PaymentProtocolBip70(Payment *payment, QObject *parent); void setUri(const QString &uri) override; @@ -69,6 +69,7 @@ private: Streaming::ConstBuffer script; }; std::vector m_outputs; + bool m_sent = false; }; #endif diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 85850ce..7af15f4 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -97,7 +97,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) return blockheaders; } -void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData*) +void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { FloweePay *app = FloweePay::instance(); if (cld->payRequest.size() == 1) -- 2.54.0 From e06438d60fa51302e4670f46656f00a0b398d626 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 5 Sep 2023 18:48:22 +0200 Subject: [PATCH 0674/1428] Avoid warning during startup. --- guis/mobile/QRScannerOverlay.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 1e62ce0..aa68662 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -196,6 +196,8 @@ FocusScope { color: "white" text: { + if (isLoading) + return ""; let cur = portfolio.current; if (cur === null || !cur.allowInstaPay) return ""; -- 2.54.0 From 02288299fc4d0a6692dc4c965b3f49632e4e6f17 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 5 Sep 2023 19:09:46 +0200 Subject: [PATCH 0675/1428] Improve BIP activation on startup For desktop we now properly support starting a payment when the user clicks on a bitcoincash link in a browser. For Android the activation logic _should_ work, except that the Android specific way has yet to be tied to our app. --- guis/desktop/SendTransactionPane.qml | 32 +++++++++++++++------------- guis/desktop/main.qml | 2 +- guis/mobile/PayWithQR.qml | 9 ++++---- src/Payment.cpp | 20 +++++++++++------ src/Payment.h | 14 +++++++++++- src/PaymentProtocol.cpp | 16 ++++++++------ src/main_utils_android.cpp | 5 ++++- 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 9036a6b..34e45f8 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -27,26 +27,28 @@ Item { id: sendPanel focus: true + onActiveFocusChanged: { + if (!activeFocus) + return; + /* + The application can be started with a click on a payment link, + in that case the link gets made available in the following property + and we start a payment protocol with the value. + Afterwards we reset the property to avoid the next opening of this + screen repeating the payment. + */ + var paymentProtcolUrl = Pay.paymentProtocolRequest; + if (paymentProtcolUrl !== "") { + payment.targetAddress = paymentProtcolUrl; + Pay.paymentProtocolRequest = ""; + } + } + Payment { // the model behind the Payment logic id: payment fiatPrice: Fiat.price account: portfolio.current - Component.onCompleted: { - /* - The application can be started with a click on a payment link, - in that case the link gets made available in the following property - and we start a payment protocol with the value. - Afterwards we reset the property to avoid the next opening of this - screen repeating the payment. - */ - - var paymentProtcolUrl = Pay.paymentProtocolRequest; - if (paymentProtcolUrl !== "") { - payment.targetAddress = paymentProtcolUrl; - Pay.paymentProtocolRequest = ""; - } - } } Rectangle { // background anchors.fill: parent diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 204bdef..9203046 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -48,7 +48,7 @@ ApplicationWindow { if (!portfolio.current.isUserOwned) { // Open on receive tab if the wallet is effectively empty tabbar.currentIndex = 2; } - else if (Pay.paymentProtocolRequestChanged !== "") { + else if (Pay.paymentProtocolRequest !== "") { tabbar.currentIndex = 1; } else { diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index e2e0c0e..364f37a 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -83,11 +83,9 @@ Page { // Take the entire QR-url and let the Payment object parse it. // this updates things like amount, comment and indeed address. - payment.targetAddress = rc - if (payment.formattedTargetAddress == "") { - // that means that the address is invalid. + let success = payment.pasteTargetAddress(rc); + if (!success) scannedUrlFaultyDialog.open(); - } // should the price be included in the QR code, don't show editing widgets. root.allowEditAmount = payment.paymentAmount <= 0; @@ -379,11 +377,12 @@ Page { wrapMode: Text.Wrap anchors.left: warningIcon.right anchors.leftMargin: 10 - anchors.right: parent.right + anchors.right: closeIcon.left anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter } Flowee.CloseIcon { + id: closeIcon anchors.right: parent.right anchors.top: parent.top anchors.margins: 10 diff --git a/src/Payment.cpp b/src/Payment.cpp index 828ce34..5e11ff4 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -79,15 +79,20 @@ void Payment::setPaymentAmountFiat(int amount) doAutoPrepare(); } -void Payment::setTargetAddress(const QString &address) +bool Payment::pasteTargetAddress(const QString &address) { if (m_paymentDetails.size() == 1) { // payment protocols only allowed on empty tx - PaymentProtocol::create(this, address, m_paymentDetails.at(0)); - return; - } - else { - soleOut()->setAddress(address.trimmed()); + auto protocol = PaymentProtocol::create(this, address, m_paymentDetails.at(0)); + return protocol; } + auto out = soleOut(); + out->setAddress(address.trimmed()); + return !out->formattedTarget().isEmpty(); +} + +void Payment::setTargetAddress(const QString &address) +{ + pasteTargetAddress(address); } QString Payment::targetAddress() @@ -464,6 +469,9 @@ QStringList Payment::warnings() const case DownloadFailed: answer.append(tr("Download of payment request Failed.")); break; + case OfflineWarning: + answer.append("App is offline, can't handle BIP70 payment request"); + break; } } return answer; diff --git a/src/Payment.h b/src/Payment.h index 58d82ab..14ef2f5 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -108,7 +108,8 @@ public: enum Warning { InsecureTransport, - DownloadFailed + DownloadFailed, + OfflineWarning }; Q_ENUM(Warning); @@ -139,8 +140,10 @@ public: * * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. + * @see pasteTargetAddress() */ void setTargetAddress(const QString &address); + /** * Returns the address to pay to, as the user typed it. * @@ -175,6 +178,15 @@ public: Q_INVOKABLE PaymentDetail* addExtraOutput(); Q_INVOKABLE PaymentDetail* addInputSelector(); Q_INVOKABLE void remove(PaymentDetail *detail); + /** + * Set a should-be-correct address and return true if valid. + * + * Calling thie function is very similar to setting the property 'targetAddress' + * with the main difference that should the address pasted not be a valid one, + * this method returns false. + * @see setTargetAddress + */ + Q_INVOKABLE bool pasteTargetAddress(const QString &address); /** * Unlock the account in order to allow funding of this payment. */ diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 284ec23..bd0f46c 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -37,6 +37,7 @@ PaymentProtocol::PaymentProtocol(Payment *payment, QObject *parent) PaymentProtocol *PaymentProtocol::create(Payment *target, const QString &uri, QObject *parent) { + assert(target->paymentDetails().size() == 1); PaymentProtocol *pp = nullptr; if (uri.length() < 20 || !uri.startsWith("bitcoincash:")) { logWarning(10003) << "Payment protocol unrecognized URI:" << uri; @@ -132,10 +133,19 @@ void PaymentProtocolBip70::setUri(const QString &uri) if (FloweePay::instance()->isOffline()) { logCritical(10003) << "App is offline, can't handle BIP70 payment"; + m_payment->addWarning(Payment::OfflineWarning); finished(); return; } + // Mark the detail as not editable as soon as possible. + assert(m_payment->paymentDetails().size() == 1); + auto *first = m_payment->paymentDetails().at(0); + assert(first); + auto *out = qobject_cast(first); + assert(out); + out->setEditable(false); + // uri typically starts with: bitcoincash:?r=https:// assert(uri.startsWith("bitcoincash:?r=")); QNetworkRequest req(uri.mid(15)); @@ -294,12 +304,6 @@ void PaymentProtocolBip70::fetchedRequest() // is the most compatible for consumers of its properties. m_payment->setTargetAddress(QString::fromLatin1(address)); m_payment->setPaymentAmount(out.amount); - assert(m_payment->paymentDetails().size() == 1); - auto *first = m_payment->paymentDetails().at(0); - assert(first); - auto *out = qobject_cast(first); - assert(out); - out->setEditable(false); } else { auto *addressDetail = m_payment->addExtraOutput()->toOutput(); diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 7af15f4..3dd422b 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -34,7 +34,10 @@ struct CommandLineParserData CommandLineParserData* createCLD(QGuiApplication &app) { auto dat = new CommandLineParserData(); - dat->payRequest = app.arguments(); + // disable this for now, the argument has some weird stuff passed in + // upon normal starting of the app. + // Need to figure out how to do this the android style... + // dat->payRequest = app.arguments(); return dat; } -- 2.54.0 From 02bf2ab1df5050f0ea0bd49cf1c44112c77e29a5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 5 Sep 2023 21:18:32 +0200 Subject: [PATCH 0676/1428] Translations update --- guis/desktop/SendTransactionPane.qml | 2 +- src/Payment.cpp | 2 +- src/PaymentProtocol.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 34e45f8..95e4039 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -168,7 +168,7 @@ Item { if (warnings.length === 0) return ""; - return qsTr("Payment Request returned with:") + return qsTr("Payment request warnings:") + "\n" + warnings.join("\n"); } visible: text !== "" diff --git a/src/Payment.cpp b/src/Payment.cpp index 5e11ff4..62ec8f9 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -467,7 +467,7 @@ QStringList Payment::warnings() const answer.append(tr("Request received over insecure channel. Anyone could have altered it!")); break; case DownloadFailed: - answer.append(tr("Download of payment request Failed.")); + answer.append(tr("Download of payment request failed.")); break; case OfflineWarning: answer.append("App is offline, can't handle BIP70 payment request"); diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index bd0f46c..6c6cdbd 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -216,7 +216,7 @@ void PaymentProtocolBip70::fetchedRequest() return; } } - m_payment->forwardError(tr("Payment Request unreadable")); + m_payment->forwardError(tr("Payment request unreadable")); return; } @@ -283,7 +283,7 @@ void PaymentProtocolBip70::fetchedRequest() logDebug(10003) << "Expires:" << m_requestExpiry.toString(); if (m_requestExpiry < QDateTime::currentDateTimeUtc()) { logWarning(10003) << " + expired"; - m_payment->forwardError(tr("Payment Request Expired")); + m_payment->forwardError(tr("Payment request expired")); } m_payment->setUserComment(m_memo); @@ -320,7 +320,7 @@ void PaymentProtocolBip70::fetchedRequest() connect (m_payment, &Payment::approvedByUser, this, [=]() { assert(m_payment->txPrepared()); if (m_requestExpiry < QDateTime::currentDateTimeUtc()) { - m_payment->forwardError(tr("Payment Request Expired")); + m_payment->forwardError(tr("Payment request expired")); return; } sendReply(); -- 2.54.0 From c0598d2f8823a791718a0c034c89c2d0d440f877 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 13:17:35 +0200 Subject: [PATCH 0677/1428] Fix interaction with this screen. A Flickable seems to eat mouse clicks by default, so even if there was no warnings it would stop allowing any interaction with the screen. --- guis/mobile/PayWithQR.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 364f37a..01962cc 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -345,6 +345,7 @@ Page { anchors.fill: parent contentWidth: width contentHeight: warningsColumn.implicitHeight + enabled: warningsColumn.implicitHeight > 0 Column { id: warningsColumn -- 2.54.0 From 8d3091bdd10db8fcb82c42ada82d514681ff251f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 13:58:54 +0200 Subject: [PATCH 0678/1428] Disable input when not visible Avoids weird interactions on the receive tab. --- guis/mobile/ReceiveTab.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index de41a1f..e5cf2b4 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -214,6 +214,7 @@ FocusScope { id: editScope width: parent.width height: editBox.height + enabled: editViewActive PageTitledBox { id: editBox width: parent.width - 50 -- 2.54.0 From 1a93eb18833128b82293379e96e72c77f5807eaf Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 15:42:03 +0200 Subject: [PATCH 0679/1428] Remove crashing destructor of QRScanner This is a QML component and after scanning is completed, on Android, destructing it seems to crash the app. --- src/QRScanner.cpp | 5 ----- src/QRScanner.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index c3f0d2e..8a1b431 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -30,11 +30,6 @@ QRScanner::QRScanner(QObject *parent) QTimer::singleShot(1, this, SLOT(completed())); } -QRScanner::~QRScanner() -{ - abort(); -} - void QRScanner::start() { resetScanResult(); diff --git a/src/QRScanner.h b/src/QRScanner.h index 1f7dda9..3b658a4 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -32,7 +32,6 @@ class QRScanner : public QObject Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) public: explicit QRScanner(QObject *parent = nullptr); - ~QRScanner(); Q_INVOKABLE void start(); Q_INVOKABLE void abort(); -- 2.54.0 From 117ccf0e030e807b8b36d10562b62e82a2367205 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 15:44:56 +0200 Subject: [PATCH 0680/1428] Limit menu-opening gesture to mobile We test on desktop, but this component with an actual mouse stops me from clicking on the "Home" button in the test env. So lets just turn that off and make the slide-to-open only happen on production devices. --- guis/mobile/MenuOverlay.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index bc60f1a..8668eac 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -273,6 +273,7 @@ Item { yAxis.enabled: false // the anchors of parent do that too ¯\_(ツ)_/¯ xAxis.minimum: 0 xAxis.maximum: parent.width + acceptedDevices: PointerDevice.TouchScreen | PointerDevice.Stylus onActiveChanged: { if (!active) { if (menuArea.x > -30) -- 2.54.0 From 109f26718495b5862d1cf3c16c83d55641cdf41d Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 15:45:42 +0200 Subject: [PATCH 0681/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 52abd80..b958bed 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="17" android:versionName="2023.09.0"> diff --git a/src/main.cpp b/src/main.cpp index cb757be..f658927 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.08.0"); + qapp.setApplicationVersion("2023.09.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 81861f7cc5e1b3e7f937e455a37f59e854f7c198 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Sep 2023 21:07:30 +0200 Subject: [PATCH 0682/1428] Follow refactor in Payment --- modules/build-transaction/PayToOthers.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 58ea45f..5d04611 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -154,7 +154,7 @@ Page { anchors.bottomMargin: 10 width: parent.width onActivated: { - payment.broadcast() + payment.markUserApproved() thePile.pop(); // the broadcast feedback is on the main screen. } } -- 2.54.0 From 7cdd3acdf77bfdb53385855f785ae7e18ef1eb09 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 14 Oct 2023 20:47:29 +0200 Subject: [PATCH 0683/1428] Improve clipboard interaction on making payments. This makes available a clipboard helper component in QML. Based on filters we then can make the 'paste' button visible and only allow pasting stuff that is remotely useful. This then also adds a 'paste' button to the transaction-building module, the 'destination' screen. --- guis/mobile/QRScannerOverlay.qml | 10 +- modules/build-transaction/PayToOthers.qml | 47 +++++++- src/CMakeLists.txt | 3 +- src/CameraController.cpp | 33 +----- src/CameraController.h | 5 +- src/QMLClipboardHelper.cpp | 125 ++++++++++++++++++++++ src/QMLClipboardHelper.h | 67 ++++++++++++ src/main.cpp | 2 + 8 files changed, 254 insertions(+), 38 deletions(-) create mode 100644 src/QMLClipboardHelper.cpp create mode 100644 src/QMLClipboardHelper.h diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index aa68662..27c0682 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -20,6 +20,7 @@ import QtQuick.Controls as QQC2 import QtQuick.Shapes import QtMultimedia import "../Flowee" as Flowee +import Flowee.org.pay; FocusScope { id: root @@ -118,7 +119,7 @@ FocusScope { x: 50 anchors.bottom: parent.bottom anchors.bottomMargin: 50 - visible: CameraController.supportsPaste + visible: CameraController.supportsPaste && cbh.text !== "" radius: 6 width: pasteButton.width height: pasteButton.height @@ -127,7 +128,12 @@ FocusScope { id: pasteButton source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); text: qsTr("Paste") - onClicked: pasteFeedback.visible = !CameraController.importScanFromClipboard(); + onClicked: pasteFeedback.visible = !CameraController.importScanData(cbh.text); + } + + ClipboardHelper { + id: cbh + filter: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses | ClipboardHelper.AddressUrl } Rectangle { diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 5d04611..2963a7b 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -272,20 +272,59 @@ Page { anchors.right: parent.right height: Math.max(destinationLabel.height * 2.3, implicitHeight) focus: enabled - property var addressType: Pay.identifyString(text); + property var addressType: Pay.identifyString(totalText); text: paymentDetail.address nextFocusTarget: priceInput enabled: paymentDetail.editable - onTextChanged: { - paymentDetail.address = text + onTotalTextChanged: { + paymentDetail.address = totalText addressInfo.createInfo(); } color: { - if (!activeFocus && text !== "" && !addressInfo.addressOk) + if (!activeFocus && totalText !== "" && !addressInfo.addressOk) return mainWindow.errorRed return palette.windowText } } + Rectangle { + id: pasteButton + anchors.verticalCenter: destination.bottom + anchors.horizontalCenter: destination.horizontalCenter + width: labelText.height + labelText.width + 20 + 5 + 20 + height: labelText.height + 10 + radius: 6 + color: palette.window + border.color: palette.midlight + border.width: 1 + visible: destination.totalText === "" && cbh.text !== "" + + ClipboardHelper { + id: cbh + filter: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses + } + + Image { + x: 20 + width: labelText.height + height: width + source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + anchors.verticalCenter: parent.verticalCenter + } + + Flowee.Label { + id: labelText + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 20 + text: qsTr("Paste") + } + + MouseArea { + anchors.fill: parent + onClicked: destination.text = cbh.text + } + } + Flowee.LabelWithClipboard { id: nativeLabel width: parent.width diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e2de775..80007e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,7 +71,8 @@ else () list(APPEND PAY_SOURCES main_utils.cpp) endif () -add_library(pay_lib STATIC ${PAY_SOURCES}) +add_library(pay_lib STATIC ${PAY_SOURCES} + QMLClipboardHelper.h QMLClipboardHelper.cpp) target_link_libraries(pay_lib flowee_apputils diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 55d0c2f..ca82069 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -576,7 +576,6 @@ bool CameraController::supportsPaste() const { if (d->scanRequest == nullptr) return false; - // return d->scanRequest->scanType() == QRScanner::PaymentDetails; } @@ -620,46 +619,24 @@ void CameraController::setTorchEnabled(bool on) emit torchEnabledChanged(); } -bool CameraController::importScanFromClipboard() +bool CameraController::importScanData(const QString &string) { if (d->scanRequest == nullptr) return false; if (d->scanRequest->scanType() != QRScanner::PaymentDetails) return false; - QString result; - const QString prefix = QString::fromStdString(chainPrefix()) + ":"; - auto text = QGuiApplication::clipboard()->text(); - auto index = text.indexOf(prefix); - if (index >= 0) { - auto end = text.indexOf(' ', index + 10); - result = text.mid(index, end); - } - else { - // find the address if it doesn't have the prefix. - for (auto &word : text.split(' ', Qt::SkipEmptyParts)) { - if (word.length() > 40 && word.length() < 50) { - auto id = FloweePay::instance()->identifyString(prefix + word); - if (id == WalletEnums::CashPKH || id == WalletEnums::CashSH) { - result = prefix + word; - break; - } - } - } - } - - if (!result.isEmpty()) { - logInfo() << "Processing clipboard segment for payment:" << result; - d->scanRequest->setScanResult(result, QRScanner::Clipboard); + if (!string.isEmpty()) { + d->scanRequest->setScanResult(string, QRScanner::Clipboard); // stop camera d->cameraStarted = false; emit cameraActiveChanged(); if (d->m_scanningThread == nullptr) { - // then the above would have no effect; + // then the above emit would have no effect; qrScanFinished(); } } - return !result.isEmpty(); + return !string.isEmpty(); } void CameraController::setCamera(QObject *object) diff --git a/src/CameraController.h b/src/CameraController.h index cd72081..b1c82ae 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -57,10 +57,9 @@ public: Q_INVOKABLE void abort(); /** - * Try to complete the current scan request by instead looking at the clipboard - * for some data that could be used to fulfill the scan. + * Try to complete the current scan request by instead taking the \a string. */ - Q_INVOKABLE bool importScanFromClipboard(); + Q_INVOKABLE bool importScanData(const QString &string); void setCamera(QObject *object); QObject *camera() const; diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp new file mode 100644 index 0000000..350706c --- /dev/null +++ b/src/QMLClipboardHelper.cpp @@ -0,0 +1,125 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "FloweePay.h" +#include "QMLClipboardHelper.h" +#include "WalletEnums.h" + +#include +#include +#include + +QMLClipboardHelper::QMLClipboardHelper(QObject *parent) + : QObject{parent} +{ + connect(QGuiApplication::clipboard(), &QClipboard::changed, this, + [=](QClipboard::Mode changed) { + if (changed == QClipboard::Clipboard) { + parseClipboard(); + } + }); + // parse it in the next event, so filters can be set by the user first. + delayedParse(); +} + +void QMLClipboardHelper::parseClipboard() +{ + m_delayedParseStarted = false; + QString result; + const QString prefix = QString::fromStdString(chainPrefix()) + ":"; + auto text = QGuiApplication::clipboard()->text(); + if (m_filter == NoFilter) { + setClipboardText(text); + return; + } + + auto type = FloweePay::instance()->identifyString(text); + if (type == WalletEnums::Unknown) { // try harder + auto index = text.indexOf(prefix); + if (index >= 0) { + auto end = text.indexOf(' ', index + 10); + result = text.mid(index, end); + } + else { + // find the address if it doesn't have the prefix. + for (auto &word : text.split(' ', Qt::SkipEmptyParts)) { + if (word.length() > 40 && word.length() < 50) { + auto id = FloweePay::instance()->identifyString(prefix + word); + if (id == WalletEnums::CashPKH || id == WalletEnums::CashSH) { + result = prefix + word; + break; + } + } + } + } + type = FloweePay::instance()->identifyString(result); + text = result; + } + + bool itsAHit = false; + if (type == WalletEnums::Unknown && m_filter.testAnyFlag(AddressUrl)) { // try finding AddressUrl (aka bip21) + const int endOfAddress = text.indexOf('?'); + if (endOfAddress > 0 && text.startsWith(prefix)) + itsAHit = true; + } + else if (m_filter.testAnyFlag(Addresses) + && (type == WalletEnums::CashPKH || type == WalletEnums::CashSH)) + itsAHit = true; + else if (m_filter.testAnyFlag(LegacyAddresses) + && (type == WalletEnums::LegacyPKH || type == WalletEnums::LegacySH)) + itsAHit = true; + else if (m_filter.testAnyFlag(Mnemonic) && type == WalletEnums::CorrectMnemonic) + itsAHit = true; + + if (itsAHit) + setClipboardText(text); +} + +void QMLClipboardHelper::setClipboardText(const QString &text) +{ + if (m_text == text) + return; + m_text = text; + emit textChanged(); +} + +void QMLClipboardHelper::delayedParse() +{ + if (m_delayedParseStarted) + return; + QTimer::singleShot(0, this, &QMLClipboardHelper::parseClipboard); + m_delayedParseStarted = true; +} + +QString QMLClipboardHelper::clipboardText() const +{ + return m_text; +} + +void QMLClipboardHelper::setFilter(QMLClipboardHelper::Types filter) +{ + if (m_filter == filter) + return; + m_filter = filter; + emit filterChanged(); + delayedParse(); +} + +QMLClipboardHelper::Types QMLClipboardHelper::filter() const +{ + return m_filter; +} diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h new file mode 100644 index 0000000..64bc621 --- /dev/null +++ b/src/QMLClipboardHelper.h @@ -0,0 +1,67 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QMLCLIPBOARDHELPER_H +#define QMLCLIPBOARDHELPER_H + +#include + +/** + * This is a QML component aimed to make usage of clipboard for UX purposes + * easier. + * + * This component reads the clipboard, it is not meant to write to the clipboard. + * Please see FloweePay::copyToClipboard(const QString &text); for that. + */ +class QMLClipboardHelper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString text READ clipboardText NOTIFY textChanged) + Q_PROPERTY(Types filter READ filter WRITE setFilter NOTIFY filterChanged) +public: + explicit QMLClipboardHelper(QObject *parent = nullptr); + + enum Type { + NoFilter = 0, + Addresses = 1 , + LegacyAddresses = 2, + AddressUrl = 4, + Mnemonic = 8 + }; + Q_ENUM(Type) + Q_DECLARE_FLAGS(Types, Type) + + QString clipboardText() const; + + void setFilter(Types filters); + Types filter() const; + +signals: + void textChanged(); + void filterChanged(); + +private: + void parseClipboard(); + void setClipboardText(const QString &text); + void delayedParse(); + + Types m_filter; + QString m_text; + bool m_delayedParseStarted = false; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index f658927..dbb4989 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,7 @@ #include "PaymentRequest.h" #include "PaymentBackend.h" #include "QRCreator.h" +#include "QMLClipboardHelper.h" #include "MenuModel.h" #include "ModuleManager.h" #ifdef NETWORK_LOGGER @@ -98,6 +99,7 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "Payment"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentBackend"); + qmlRegisterType("Flowee.org.pay", 1, 0, "ClipboardHelper"); auto cld = createCLD(qapp); auto *logger = Log::Manager::instance(); -- 2.54.0 From 38a9ad3a4edb5ecc5da00f09b660de644d03ae16 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 16 Oct 2023 20:27:48 +0200 Subject: [PATCH 0684/1428] Fixlets in colors. Make sure that the header-menu actually is visible on Pages. --- guis/Flowee/ScrollThumb.qml | 10 +++++++--- guis/mobile/AccountHistory.qml | 2 +- guis/mobile/Page.qml | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index c68e13a..5217fb2 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -71,14 +71,18 @@ QQC2.ScrollBar { Repeater { model: 3 delegate: Rectangle { - color: palette.light + color: palette.dark width: column.width height: 2 radius: 1 } } } - color: thumbInput.engaged ? palette.highlight : palette.dark + color: { + if (Pay.useDarkSkin) + return Qt.darker(palette.highlight, 1.6); + return Qt.lighter(palette.highlight, 1.6); + } Timer { running: thumbRect.open && !thumbRect.moving @@ -96,7 +100,7 @@ QQC2.ScrollBar { MouseArea { id: thumbInput - // make it easier to grab by having a bigger mouse area than the visial thumb + // make it easier to grab by having a bigger mouse area than the visual thumb width: thumbRect.width + 20 + root.width height: thumbRect.height + 50 anchors.right: parent.right diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 6b56ac3..e719ded 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -227,7 +227,7 @@ ListView { } radius: 20 - color: palette.alternateBase + color: palette.base border.width: 1 border.color: palette.midlight } diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 418d222..023107e 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -114,6 +114,7 @@ QQC2.Control { anchors.right: parent.right anchors.rightMargin: 15 anchors.verticalCenter: parent.verticalCenter + color: "white" MouseArea { anchors.fill: parent anchors.margins: -15 -- 2.54.0 From 2e71a162aa698d786f2308e1d65135dcdff056b8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Oct 2023 12:00:28 +0200 Subject: [PATCH 0685/1428] Tweak and fix clipboard paste based workflows. One surprise was that the main usecase of pasting is one where the user activates another app to go and copy data in order to come back to paste it. And the Qt clipboard didn't manage to get any notification of clipboard changes. Even copying data on becoming active had no effect. So now I just show the paste optimistically when the user comes back from another screen, assuming the main reason for that was to copy. --- guis/mobile/PriceInputWidget.qml | 12 +++-- guis/mobile/QRScannerOverlay.qml | 1 + modules/build-transaction/PayToOthers.qml | 32 +++++++++++- src/PaymentDetailOutput.cpp | 9 ++-- src/QMLClipboardHelper.cpp | 62 +++++++++++++++++------ src/QMLClipboardHelper.h | 17 ++++++- 6 files changed, 107 insertions(+), 26 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index 7c12c80..c2a765d 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -38,10 +38,7 @@ FocusScope { onFiatFollowsSatsChanged: { if (!activeFocus) return; - if (fiatFollowsSats) - priceBch.forceActiveFocus(); - else - priceFiat.forceActiveFocus(); + takeFocus(); } function shake() { @@ -54,6 +51,13 @@ FocusScope { implicitHeight: 140 height: implicitHeight + function takeFocus() { + if (fiatFollowsSats) + priceBch.forceActiveFocus(); + else + priceFiat.forceActiveFocus(); + } + Flowee.BitcoinValueField { id: priceBch y: root.fiatFollowsSats ? 5 : 68 diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 27c0682..4efaa4d 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -134,6 +134,7 @@ FocusScope { ClipboardHelper { id: cbh filter: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses | ClipboardHelper.AddressUrl + enabled: CameraController.cameraActive } Rectangle { diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 2963a7b..1f5a701 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -288,6 +288,7 @@ Page { } Rectangle { id: pasteButton + property bool appWasHidden: false anchors.verticalCenter: destination.bottom anchors.horizontalCenter: destination.horizontalCenter width: labelText.height + labelText.width + 20 + 5 + 20 @@ -296,7 +297,25 @@ Page { color: palette.window border.color: palette.midlight border.width: 1 - visible: destination.totalText === "" && cbh.text !== "" + visible: destination.totalText === "" && (cbh.text !== "" || appWasHidden) + Connections { + /* + * A usecase of pasting is that the user opens this screen, goes to another app and + * copies something they want to copy and then comes back here to paste it. + * This causes the 'appWasHidden' to be set to true and we then 'enable' the clipboardHelper + * in order to read the clipboard-data. + * + * Notice that this is needed because the clipboard doesn't notice something new being + * present after we just got unfrozen. + */ + target: Application + function onActiveChanged() { + if (Application.active !== Qt.ApplicationActive) { + pasteButton.appWasHidden = true; + cbh.enabled = false; + } + } + } ClipboardHelper { id: cbh @@ -321,7 +340,16 @@ Page { MouseArea { anchors.fill: parent - onClicked: destination.text = cbh.text + onClicked: { + if (pasteButton.appWasHidden) { + pasteButton.appWasHidden = false; + cbh.enabled = true; // fills text, if there is something to fill. + } + if (cbh.text !== "") { + destination.text = cbh.text + priceInput.takeFocus(); + } + } } } diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 05c95e6..0379410 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -96,16 +96,19 @@ const QString &PaymentDetailOutput::address() const return m_address; } -void PaymentDetailOutput::setAddress(const QString &address) +void PaymentDetailOutput::setAddress(const QString &address_) { + QString address = address_.trimmed(); if (m_address == address) return; if (address.indexOf('?') >= 12) { // this is a payment protocol, go via the Payment object to do the right thing. Payment *p = qobject_cast(parent()); assert(p); - p->setTargetAddress(address); - return; + if (p->paymentDetails().size() == 1) { // payment protocols only for sole-outputs + p->setTargetAddress(address); + return; + } } m_address = address; m_outputScript.clear(); diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index 350706c..0b6205b 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -22,36 +22,52 @@ #include #include #include +#include QMLClipboardHelper::QMLClipboardHelper(QObject *parent) - : QObject{parent} + : QObject{parent}, + m_filter(Addresses) { - connect(QGuiApplication::clipboard(), &QClipboard::changed, this, - [=](QClipboard::Mode changed) { - if (changed == QClipboard::Clipboard) { - parseClipboard(); - } + // when the app regains main-app status, check for changes in the clipboard. + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationActive) + delayedParse(); }); - // parse it in the next event, so filters can be set by the user first. + // parse it in the next event, when properties are fully set delayedParse(); } void QMLClipboardHelper::parseClipboard() { m_delayedParseStarted = false; - QString result; - const QString prefix = QString::fromStdString(chainPrefix()) + ":"; - auto text = QGuiApplication::clipboard()->text(); - if (m_filter == NoFilter) { + if (!m_enabled) + return; + auto *mimeData = QGuiApplication::clipboard()->mimeData(); + QString text; + if (mimeData->hasText()) + text = mimeData->text(); + else if (mimeData->hasHtml()) + text = mimeData->html(); + + if (text.isEmpty() || m_filter == NoFilter) { setClipboardText(text); return; } + // often found whitespace, make it all spaces. + text = text.replace(QLatin1String("
"), QLatin1String(" ")); + text = text.replace(QLatin1String("\n"), QLatin1String(" ")); + text = text.replace(QLatin1String("\r"), QLatin1String(" ")); + text = text.trimmed(); + const QString prefix = QString::fromStdString(chainPrefix()) + ":"; + QString result; auto type = FloweePay::instance()->identifyString(text); - if (type == WalletEnums::Unknown) { // try harder + if (type == WalletEnums::Unknown || type == WalletEnums::PartialMnemonicWithTypo || type == WalletEnums::PartialMnemonic) { // try harder auto index = text.indexOf(prefix); if (index >= 0) { - auto end = text.indexOf(' ', index + 10); + auto end = text.indexOf(' ', index + prefix.size()); result = text.mid(index, end); } else { @@ -71,7 +87,9 @@ void QMLClipboardHelper::parseClipboard() } bool itsAHit = false; - if (type == WalletEnums::Unknown && m_filter.testAnyFlag(AddressUrl)) { // try finding AddressUrl (aka bip21) + if ((type == WalletEnums::Unknown || type == WalletEnums::PartialMnemonicWithTypo + || type == WalletEnums::PartialMnemonic) + && m_filter.testAnyFlag(AddressUrl)) { // try finding AddressUrl (aka bip21) const int endOfAddress = text.indexOf('?'); if (endOfAddress > 0 && text.startsWith(prefix)) itsAHit = true; @@ -82,8 +100,6 @@ void QMLClipboardHelper::parseClipboard() else if (m_filter.testAnyFlag(LegacyAddresses) && (type == WalletEnums::LegacyPKH || type == WalletEnums::LegacySH)) itsAHit = true; - else if (m_filter.testAnyFlag(Mnemonic) && type == WalletEnums::CorrectMnemonic) - itsAHit = true; if (itsAHit) setClipboardText(text); @@ -105,6 +121,20 @@ void QMLClipboardHelper::delayedParse() m_delayedParseStarted = true; } +bool QMLClipboardHelper::enabled() const +{ + return m_enabled; +} + +void QMLClipboardHelper::setEnabled(bool newEnabled) +{ + if (m_enabled == newEnabled) + return; + m_enabled = newEnabled; + emit enabledChanged(); + parseClipboard(); +} + QString QMLClipboardHelper::clipboardText() const { return m_text; diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index 64bc621..43c1139 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -26,12 +26,23 @@ * * This component reads the clipboard, it is not meant to write to the clipboard. * Please see FloweePay::copyToClipboard(const QString &text); for that. + * + * The Clipboard is device-wide (or user-wide on desktop) and that makes it a + * precious resource and we should thus avoid reading it unless relevant. + * Please take care to only instantiate AND set 'enabled' to true when some paste + * functionality is actually directly on screen. + * + * This component allows setting of filters on what kind of content we are interested. + * When the filtered content is found on the clipboard, the text property will contain + * a copy of the content, where the not-relevant parts of the clipboard have already + * been removed. */ class QMLClipboardHelper : public QObject { Q_OBJECT Q_PROPERTY(QString text READ clipboardText NOTIFY textChanged) Q_PROPERTY(Types filter READ filter WRITE setFilter NOTIFY filterChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) public: explicit QMLClipboardHelper(QObject *parent = nullptr); @@ -40,7 +51,6 @@ public: Addresses = 1 , LegacyAddresses = 2, AddressUrl = 4, - Mnemonic = 8 }; Q_ENUM(Type) Q_DECLARE_FLAGS(Types, Type) @@ -50,9 +60,13 @@ public: void setFilter(Types filters); Types filter() const; + bool enabled() const; + void setEnabled(bool newEnabled); + signals: void textChanged(); void filterChanged(); + void enabledChanged(); private: void parseClipboard(); @@ -62,6 +76,7 @@ private: Types m_filter; QString m_text; bool m_delayedParseStarted = false; + bool m_enabled = true; }; #endif -- 2.54.0 From dbbbd79f0a2c108a5ef9b15200138204173d2382 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Oct 2023 12:00:42 +0200 Subject: [PATCH 0686/1428] Add comment explaining the warning. --- guis/mobile/ReceiveTab.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index e5cf2b4..183e4ff 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -64,7 +64,7 @@ FocusScope { onActiveChanged: { let a = active; if (a) // disable the other one - verticalTabs.children[(index + 1) % 2].active = false + verticalTabs.children[(index + 1) % 2].active = false // yes, this warns during construction ツ)_/¯ // update page-scope state variables if (index === 1) a = !a; -- 2.54.0 From 04bd9f51b718e349dff2a696e5c8fff517e6a6ab Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Oct 2023 12:32:09 +0200 Subject: [PATCH 0687/1428] Fix off-by-one in the assert. --- src/Wallet_spending.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index ed15cb3..4dbca6e 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -49,7 +49,7 @@ int Wallet::scoreForSolution(const OutputSet &set, int64_t change, size_t unspen */ assert(unspentOutputCount > 0); assert(set.outputs.size() > 0); - assert(change > 0); + assert(change >= 0); const int resultingOutputCount = unspentOutputCount - set.outputs.size(); int score = 0; -- 2.54.0 From c219dd5798de0086dc15a6962e1cf79c73e8061d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Oct 2023 18:07:43 +0200 Subject: [PATCH 0688/1428] Fixlet; the repo and default checkout is called pay --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee6aa9b..fe0c8e2 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ following workflow: make install ``` -The executables will be in `floweepay/build/bin/` and by adding the `local_qml` +The executables will be in `pay/build/bin/` and by adding the `local_qml` cmake option the build will bake in the path to your QML files. On your local harddrive. This allows you to change the QML files and simply restart the app without recompile. -- 2.54.0 From c3acf0c12fd04dd4be25d932a9b8fbb155734158 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Oct 2023 20:05:54 +0200 Subject: [PATCH 0689/1428] Fix cli 'connect' arg using wrong port on testnet4 --- src/main_utils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index d798a62..0534884 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -148,8 +148,10 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); if (!cld->parser.isSet(cld->offline) && cld->parser.isSet(cld->connect)) { - app->p2pNet()->connectionManager().peerAddressDb().addOne( // add it to the DB, making sure there is at least one. - EndPoint(cld->parser.value(cld->connect).toStdString(), 8333)); + const int port = cld->parser.isSet(cld->testnet4) ? 28333 : 8333; + // add it to the DB, making sure there is at least one. + app->p2pNet()->connectionManager().peerAddressDb().addOne( + EndPoint(cld->parser.value(cld->connect).toStdString(), port)); } app->startNet(); // lets go! -- 2.54.0 From ddeac5622f2eb800bd118273be67e91e77e4ca9f Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Tue, 17 Oct 2023 22:02:08 +0300 Subject: [PATCH 0690/1428] Fix build issues on macOS + clang --- CMakeLists.txt | 3 +++ src/PaymentDetailOutput.cpp | 4 ++-- src/PriceHistoryDataProvider.cpp | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6868bff..119473a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,9 @@ else () find_package(ZXing REQUIRED) endif() find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) +endif() function(download_file url path) if (NOT EXISTS "${path}") diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 0379410..ea38c11 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -66,7 +66,7 @@ double PaymentDetailOutput::paymentAmount() const if (detail->isInputs()) baseBalance = detail->toInputs()->selectedValue(); } - return static_cast(std::max(0l, baseBalance - outputs)); + return static_cast(std::max(int64_t{0}, baseBalance - outputs)); } return static_cast(m_paymentAmount); } @@ -284,7 +284,7 @@ int PaymentDetailOutput::paymentAmountFiat() const if (detail->isInputs()) baseBalance = detail->toInputs()->selectedValue(); } - amount = std::max(0l, baseBalance - outputs); + amount = std::max(int64_t{0}, baseBalance - outputs); } return (amount * p->fiatPrice() / 10000000 + 5) / 10; diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index 725a5cf..836cf37 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "PriceHistoryDataProvider.h" +#include "crypto/compat/endian.h" #include "streaming/BufferPools.h" #include -- 2.54.0 From 33ba861af2aa0748f5a42d54def3b6b147928477 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Oct 2023 11:38:45 +0200 Subject: [PATCH 0691/1428] Make sure the value is up-to-date. --- guis/mobile/QRScannerOverlay.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 4efaa4d..d77d7f2 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -205,6 +205,11 @@ FocusScope { text: { if (isLoading) return ""; + + // slight hack, make this field be re-calculated on camera active change in order to ensure + // we have the latest limit (which doesn't have a signal of its owo) + var dummy = CameraController.cameraActive; + let cur = portfolio.current; if (cur === null || !cur.allowInstaPay) return ""; -- 2.54.0 From 1deccd92266b257ccdd45f439321651072926d09 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Oct 2023 11:43:23 +0200 Subject: [PATCH 0692/1428] Make fiat mode an actual bool on BitcoinValue The CPP now does more of the (heavy) lifting and the UI layer can ignore most of the details with regards to there being digits behind the separator for fiat at all. The internal change is that the fiat based values are always processed in cents, even if the cents are not displayed. This solves incorrect display and generally removes special cases. --- guis/Flowee/FiatValueField.qml | 11 ++- guis/Flowee/MoneyValueField.qml | 2 +- guis/mobile/InstaPayConfigButton.qml | 6 ++ guis/mobile/PayWithQR.qml | 8 +- src/AccountConfig.cpp | 6 +- src/BitcoinValue.cpp | 114 ++++++++++++++++----------- src/BitcoinValue.h | 35 +++++--- src/PriceDataProvider.cpp | 2 +- src/PriceDataProvider.h | 5 +- testing/value/TestValue.cpp | 4 +- 10 files changed, 118 insertions(+), 75 deletions(-) diff --git a/guis/Flowee/FiatValueField.qml b/guis/Flowee/FiatValueField.qml index d218483..399ee7f 100644 --- a/guis/Flowee/FiatValueField.qml +++ b/guis/Flowee/FiatValueField.qml @@ -28,20 +28,23 @@ MoneyValueField { baselineOffset: fiat.baselineOffset implicitHeight: fiat.implicitHeight implicitWidth: Math.max(row.width, 70) - maxFractionalDigits: Fiat.displayCents ? 2 : 0 + fiatMode: true RowLayout { id: row anchors.right: parent.right height: parent.height spacing: 0 - property string amountString: Fiat.priceToStringSimple(root.value) + property string amountString: { + var displayCents = Fiat.displayCents; // forcably call the method again on displayCents change + return Fiat.priceToStringSimple(root.value) + } Label { text: Fiat.currencySymbolPrefix font.pixelSize: fiat.fontPixelSize color: fiat.color - visible: text != "" + visible: text !== "" } LabelWithCursor { id: fiat @@ -54,7 +57,7 @@ MoneyValueField { text: Fiat.currencySymbolPost font.pixelSize: fiat.fontPixelSize color: fiat.color - visible: text != "" + visible: text !== "" } } } diff --git a/guis/Flowee/MoneyValueField.qml b/guis/Flowee/MoneyValueField.qml index e5f976c..45b52b1 100644 --- a/guis/Flowee/MoneyValueField.qml +++ b/guis/Flowee/MoneyValueField.qml @@ -24,7 +24,7 @@ FocusScope { activeFocusOnTab: true property alias value: privValue.value property alias cursorPos: privValue.cursorPos - property alias maxFractionalDigits: privValue.maxFractionalDigits + property alias fiatMode: privValue.fiatMode signal valueEdited; // provide nested access to the BitcoinValue diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index b92db8e..54115e9 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -34,6 +34,12 @@ TextButton { updateLimit(); } } + Connections { + target: Fiat + function onCurrencyNameChanged() { + updateLimit(); + } + } subtext: { if (!root.account.allowInstaPay) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 01962cc..11aabf5 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -45,8 +45,8 @@ Page { root.closeHeaderMenu(); // this action can only ever be used to start editing. root.allowEditAmount = true; - priceInput.forceActiveFocus(); priceInput.fiatFollowsSats = false + priceInput.takeFocus() } checked: root.allowEditAmount } @@ -76,8 +76,8 @@ Page { thePile.pop(); return; } - // if the scanner got bypassed with a 'paste' instead then - // we don't allow instpay + // if the scanner got bypassed with a 'paste' then + // we don't allow instapay if (resultSource === QRScanner.Clipboard) payment.instaPay = false; @@ -89,6 +89,8 @@ Page { // should the price be included in the QR code, don't show editing widgets. root.allowEditAmount = payment.paymentAmount <= 0; + if (root.allowEditAmount) + priceInput.takeFocus(); } } Payment { diff --git a/src/AccountConfig.cpp b/src/AccountConfig.cpp index b3f37d4..2ef8b0c 100644 --- a/src/AccountConfig.cpp +++ b/src/AccountConfig.cpp @@ -71,7 +71,7 @@ void AccountConfig::setCountBalance(bool newCountBalance) return; i->countBalance = newCountBalance; fp->m_accountConfigs = configs; - fp->startSaveData_priv(); + emit fp->startSaveData_priv(); emit fp->totalBalanceConfigChanged(); } @@ -122,7 +122,7 @@ void AccountConfig::setFiatInstaPayLimit(const QString ¤cyCode, int limitI return; i->fiatInstaPayLimits[currencyCode] = limitInCent; fp->m_accountConfigs = configs; - fp->startSaveData_priv(); + emit fp->startSaveData_priv(); } bool AccountConfig::isPrivate() const @@ -143,5 +143,5 @@ void AccountConfig::setIsPrivate(bool newIsPrivate) return; i->privateWallet = newIsPrivate; fp->m_accountConfigs = configs; - fp->startSaveData_priv(); + emit fp->startSaveData_priv(); } diff --git a/src/BitcoinValue.cpp b/src/BitcoinValue.cpp index 802e6b3..276ae5c 100644 --- a/src/BitcoinValue.cpp +++ b/src/BitcoinValue.cpp @@ -17,17 +17,19 @@ */ #include "BitcoinValue.h" #include "FloweePay.h" +#include "PriceDataProvider.h" #include #include #include +#include BitcoinValue::BitcoinValue(QObject *parent) : QObject(parent), m_value(0) { connect (FloweePay::instance(), &FloweePay::unitChanged, this, [this]() { - if (m_maxFractionalDigits == -1) { + if (!m_fiatMode) { if (m_valueSource == UserInput) { // m_typedNumber is leading // we follow what the user actually typed. For crypto values anyway. setStringValue(m_typedNumber); @@ -36,6 +38,10 @@ BitcoinValue::BitcoinValue(QObject *parent) } } }); + connect (FloweePay::instance()->prices(), &PriceDataProvider::currencySymbolChanged, this, [this]() { + if (m_fiatMode) + setDisplayCents(FloweePay::instance()->prices()->displayCents()); + }); } qint64 BitcoinValue::value() const @@ -73,7 +79,8 @@ bool BitcoinValue::insertNumber(QChar number) throw std::runtime_error("Only numbers can be inserted in insertNumber"); auto backup(m_typedNumber); int pos = m_typedNumber.indexOf('.'); - const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; + const int unitConfigDecimals = m_fiatMode ? (m_displayCents ? 2 : 0) + : FloweePay::instance()->unitAllowedDecimals(); if (pos > -1 && m_cursorPos > pos && m_typedNumber.size() - pos - unitConfigDecimals > 0) return false; int cursorPosNow = m_cursorPos; @@ -95,7 +102,7 @@ bool BitcoinValue::insertNumber(QChar number) bool BitcoinValue::addSeparator() { - if (m_typedNumber.indexOf('.') != -1 || m_maxFractionalDigits == 0) + if (m_typedNumber.indexOf('.') != -1 || (m_fiatMode && !m_displayCents)) return false; m_typedNumber.insert(m_cursorPos, '.'); int movedPlaces = 1; @@ -111,10 +118,13 @@ bool BitcoinValue::addSeparator() void BitcoinValue::paste() { - QClipboard *clipboard = QGuiApplication::clipboard(); - assert(clipboard); + auto *mimeData = QGuiApplication::clipboard()->mimeData(); + QString newValue; + if (mimeData->hasText()) + newValue = mimeData->text(); + else if (mimeData->hasHtml()) // apps like telegram use this, even if they don't have the html tags wrapping it. + newValue = mimeData->html(); - auto newValue = clipboard->text().trimmed().replace(',', '.'); setStringValue(newValue); m_valueSource = FromNumber; QTimer::singleShot(10, this, SLOT(processNumberValue())); @@ -160,7 +170,7 @@ bool BitcoinValue::setStringValue(const QString &value) after = value.mid(separator + 1); } qint64 newVal = before.toLong(); - const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; + const int unitConfigDecimals = m_fiatMode ? 2 : FloweePay::instance()->unitAllowedDecimals(); for (int i = 0; i < unitConfigDecimals; ++i) { newVal *= 10; } @@ -169,50 +179,65 @@ bool BitcoinValue::setStringValue(const QString &value) newVal += QStringView(after).left(unitConfigDecimals).toInt(); - constexpr int64_t Coin = 100000000; // num satoshis in a single coin - constexpr int64_t MaxMoney = 21000000 * Coin; // number of maxim monies ever to be created - if (newVal > MaxMoney) - return false; + if (!m_fiatMode) { + constexpr int64_t Coin = 100000000; // num satoshis in a single coin + constexpr int64_t MaxMoney = 21000000 * Coin; // number of maxim monies ever to be created + if (newVal > MaxMoney) + return false; + } setValue(newVal, UserInput); return true; } +bool BitcoinValue::displayCents() const +{ + return m_displayCents; +} + +void BitcoinValue::setDisplayCents(bool newDisplayCents) +{ + if (m_displayCents == newDisplayCents) + return; + m_displayCents = newDisplayCents; + emit displayCentsChanged(); + + // a BitcoinValue instance does not change its 'fiatMode' in usage, just at construction time + // as a result we can do the action on displayCents changes here. + if (!m_fiatMode) + return; + + if (!m_displayCents) { + // special case this. Drop the separator if the new currency has none. + int index = m_typedNumber.indexOf('.'); + if (index > 0) { + m_typedNumber = m_typedNumber.left(index); + setCursorPos(std::min(index, m_cursorPos)); + } + } + setStringValue(m_typedNumber); +} + +bool BitcoinValue::fiatMode() const +{ + return m_fiatMode; +} + +void BitcoinValue::setFiatMode(bool newFiatMode) +{ + if (m_fiatMode == newFiatMode) + return; + m_fiatMode = newFiatMode; + emit fiatModeChanged(); + + if (m_fiatMode) + setDisplayCents(FloweePay::instance()->prices()->displayCents()); +} + QString BitcoinValue::enteredString() const { return m_typedNumber; } -int BitcoinValue::maxFractionalDigits() const -{ - return m_maxFractionalDigits; -} - -void BitcoinValue::setMaxFractionalDigits(int newMaxFractionalDigits) -{ - if (m_maxFractionalDigits == newMaxFractionalDigits) - return; - m_maxFractionalDigits = newMaxFractionalDigits; - emit maxFractionalDigitsChanged(); - - // recalc the integer baesd one from the user-typed string. - if (m_valueSource == UserInput) { - if (newMaxFractionalDigits == 0) { - // special case this. Drop the separator if the new currency has none. - int index = m_typedNumber.indexOf('.'); - if (index > 0) { - m_typedNumber = m_typedNumber.left(index); - setCursorPos(std::min(index, m_cursorPos)); - } - } - setStringValue(m_typedNumber); - } -} - -void BitcoinValue::resetMaxFractionalDigits() -{ - setMaxFractionalDigits(-1); -} - int BitcoinValue::cursorPos() const { return m_cursorPos; @@ -235,8 +260,9 @@ void BitcoinValue::processNumberValue() if (m_valueSource == UserInput) return; - const int unitConfigDecimals = m_maxFractionalDigits == -1 ? FloweePay::instance()->unitAllowedDecimals() : m_maxFractionalDigits; - m_typedNumber = QString::number(m_value / pow(10, unitConfigDecimals), 'f', unitConfigDecimals); + const int unitConfigDecimals = m_fiatMode ? (m_displayCents ? 2 : 0) + : FloweePay::instance()->unitAllowedDecimals(); + m_typedNumber = QString::number(m_value / pow(10, m_fiatMode ? 2 : unitConfigDecimals), 'f', unitConfigDecimals); if (unitConfigDecimals > 0) { // there is a comma separator in the string int length; if (m_typedNumber.startsWith("0.")) diff --git a/src/BitcoinValue.h b/src/BitcoinValue.h index 427551e..b0f32e6 100644 --- a/src/BitcoinValue.h +++ b/src/BitcoinValue.h @@ -26,8 +26,19 @@ class BitcoinValue : public QObject Q_OBJECT Q_PROPERTY(double value READ realValue WRITE setRealValue NOTIFY valueChanged) Q_PROPERTY(QString enteredString READ enteredString NOTIFY enteredStringChanged) - Q_PROPERTY(int maxFractionalDigits READ maxFractionalDigits WRITE setMaxFractionalDigits RESET resetMaxFractionalDigits NOTIFY maxFractionalDigitsChanged) Q_PROPERTY(int cursorPos READ cursorPos WRITE setCursorPos NOTIFY cursorPosChanged) + + /** + * In 'fiat' mode we always use 2 digits behind the separator and we no longer follow + * the application-wide setting for BCH-units. + * Notice that for **display** the fiatDigits property is still relevant. + */ + Q_PROPERTY(bool fiatMode READ fiatMode WRITE setFiatMode NOTIFY fiatModeChanged FINAL) + /** + * When fiatMode is true, this property is used to make the display and editing use + * either 2 or zero digits behind the separator. + */ + Q_PROPERTY(bool displayCents READ displayCents WRITE setDisplayCents NOTIFY displayCentsChanged FINAL) public: explicit BitcoinValue(QObject *parent = nullptr); @@ -56,24 +67,21 @@ public: QString enteredString() const; - /* - * For fiat prices we want to limit the number of digits after - * the unit separator. This allows us to do so. - * Notice that reset unsets this making the number of digits follow the - * Bitcoin Cash unit selected application-wide. - */ - int maxFractionalDigits() const; - void setMaxFractionalDigits(int newMaxFractionalDigits); - void resetMaxFractionalDigits(); - int cursorPos() const; void setCursorPos(int newCursorPos); + bool fiatMode() const; + void setFiatMode(bool newFiatMode); + + bool displayCents() const; + void setDisplayCents(bool newDisplayCents); + signals: void valueChanged(); void enteredStringChanged(); - void maxFractionalDigitsChanged(); void cursorPosChanged(); + void fiatModeChanged(); + void displayCentsChanged(); protected slots: void processNumberValue(); @@ -86,8 +94,9 @@ protected: qint64 m_value; QString m_typedNumber; int m_cursorPos = 0; - int m_maxFractionalDigits = -1; ValueSource m_valueSource = UserInput; + bool m_fiatMode = false; + bool m_displayCents = true; }; #endif diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 47d28d9..097505a 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -200,7 +200,7 @@ QString PriceDataProvider::priceToStringSimple(int64_t cents) const { auto value = QString::number(cents); if (!m_displayCents) - return value; + return value.left(value.size() - 2); const QChar comma = QLocale::system().decimalPoint().at(0); if (cents < 10) return "0" % comma % "0" % value; diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 463b0f4..a6d0d07 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -78,10 +78,7 @@ public: Q_INVOKABLE int historicalPriceAccurate(int days) const; /// return a string with the given price and needed decimal separator. - /// Notes: 1. the currency indicators are not included, unlike in formattedPrice() - /// 2. the current fiat properties are used and we expect the 'price' in the - /// smallest unit. Cents for things like Euro, whole coins for currencies - /// where they hae no separator. + /// Note: the currency indicators are not included, unlike in formattedPrice() /// \see currencySymbolPrefix() /// \see currencySymbolPost() Q_INVOKABLE QString priceToStringSimple(int64_t price) const; diff --git a/testing/value/TestValue.cpp b/testing/value/TestValue.cpp index c9c7905..e7fd7ce 100644 --- a/testing/value/TestValue.cpp +++ b/testing/value/TestValue.cpp @@ -195,8 +195,8 @@ void TestValue::insertAtCursor() void TestValue::fiatValues() { MockBitcoinValue testObject; - testObject.setMaxFractionalDigits(2); - // this sets it to a fiat mode, like the Euro + testObject.setFiatMode(true); + testObject.setDisplayCents(true); testObject.setEnteredString("4.5"); QCOMPARE(testObject.cursorPos(), 3); -- 2.54.0 From 7292769c36210861c8322d659ba9b3e1fbc4a307 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Wed, 18 Oct 2023 14:19:04 +0300 Subject: [PATCH 0693/1428] Fix compiler warnings on macOS clang Just some nits but here is what the compiler didn't like: - Apparently Qt's `qAsConst` is marked as deprecated (at least in Qt 6.6.0), so they recommend one uses `std::as_const` - Compiler feels happier with `#ifdef` rather than `#if` for `TARGET_OS_Android` in the not-defined case. --- src/CameraController.cpp | 2 +- src/FloweePay.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index ca82069..f4dd67a 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -33,7 +33,7 @@ #include #include -#if TARGET_OS_Android && QT_VERSION < QT_VERSION_CHECK(6, 5, 0) +#if defined(TARGET_OS_Android) && QT_VERSION < QT_VERSION_CHECK(6, 5, 0) # include #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index ded37dd..bc348ed 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -49,6 +49,7 @@ #include #include #include +#include constexpr const char *UNIT_TYPE = "unit"; constexpr const char *CREATE_START_WALLET = "create-start-wallet"; @@ -217,7 +218,7 @@ FloweePay::FloweePay() connect (timer, SIGNAL(timeout()), this, SIGNAL(expectedChainHeightChanged())); QString base; -#if TARGET_OS_Android +#ifdef TARGET_OS_Android base = QLatin1String("assets:/"); #else QDir baseDir(QCoreApplication::applicationDirPath() + "/../share/floweepay/"); @@ -1046,7 +1047,7 @@ QObject *FloweePay::researchAddress(const QString &address, QObject *parent) // if we don't know the address, return a nullptr AddressInfo *info = nullptr; - for (const auto *wallet : qAsConst(m_wallets)) { + for (const auto *wallet : std::as_const(m_wallets)) { int privKeyId = wallet->findPrivKeyId(key); if (privKeyId != -1) { info = new AddressInfo(address, parent); -- 2.54.0 From 66def83e3b50cc0e06fb25e424f39b73f1ba96ef Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Oct 2023 21:28:40 +0200 Subject: [PATCH 0694/1428] Fix swapped text. --- guis/desktop/WalletEncryptionStatus.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml index 3dcd6fe..ff72697 100644 --- a/guis/desktop/WalletEncryptionStatus.qml +++ b/guis/desktop/WalletEncryptionStatus.qml @@ -41,10 +41,10 @@ Item { anchors.leftMargin: 3 text: { var txt = ""; - if (root.account.needsPinToPay) - txt = qsTr("Pin to Pay"); - else if (root.account.needsPinToOpen) + if (root.account.needsPinToOpen) txt = qsTr("Pin to Open"); + else if (root.account.needsPinToPay) + txt = qsTr("Pin to Pay"); if (root.account.isDecrypted) txt += " " + qsTr("(Opened)", "Wallet is decrypted"); return txt; -- 2.54.0 From 69cc7bcd3bbb028120216186696144ca0cf4e702 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Thu, 19 Oct 2023 06:47:32 +0300 Subject: [PATCH 0695/1428] Removed this assertion as it was leading to a crash on my testnet4 It's possible for an output amount to be 0 on a real blockchain -- a miner can mine a txn paying 0 to any address! Such a thing happened on testnet4. --- src/Wallet.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index bf29ccd..d637bf9 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1914,7 +1914,6 @@ void Wallet::loadWallet() else if (parser.tag() == WalletPriv::KeyStoreIndex) { assert(outputIndex >= 0); - assert(output.value > 0); output.walletSecretId = parser.intData(); wtx.outputs.insert(std::make_pair(outputIndex, output)); -- 2.54.0 From d2550aee3f24678955453b349fe27fd97e507d49 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Wed, 18 Oct 2023 21:02:41 +0300 Subject: [PATCH 0696/1428] Add support for importing wallets using Electrum mnemonic phrases This is the format used by (older) Electron Cash wallets. It may be useful to users wishing to use their older EC wallets with Flowee Pay. Highlight of changes: - Import wallet UI now auto-detects whether it's Electrum or BIP39. Note that in some cases a valid BIP39 seed is also a valid Electrum seed. The user has a UI element checkbox for "forcing" Electrum interpretation in such cases. - Electrum seed phrases always use derivation path "m/" so this is forced on the user as the only path available in the Electrum case. - Wallet file format has a new tag to indicate whether the seed phrase is electrum or not. Only present in the electrum case, otherwise it's omitted from the serialized data. --- guis/desktop/AccountDetails.qml | 10 +++++++ guis/desktop/NewAccountImportAccount.qml | 38 +++++++++++++++++++++--- src/AccountInfo.cpp | 5 ++++ src/AccountInfo.h | 3 ++ src/FloweePay.cpp | 13 ++++---- src/FloweePay.h | 7 +++-- src/Wallet.cpp | 22 ++++++++++++-- src/Wallet.h | 9 ++++-- src/WalletEnums.h | 3 +- src/Wallet_encryption.cpp | 5 ++-- src/Wallet_p.h | 1 + 11 files changed, 97 insertions(+), 19 deletions(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index c72793a..44933bc 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -294,6 +294,16 @@ Item { wrapMode: TextEdit.WordWrap padding: 0 } + Label { + text: qsTr("Seed format") + ":" + visible: root.account.isElectrumMnemonic + } + Label { + id: seedPhraseFormat + font.bold: true + text: root.account.isElectrumMnemonic ? "Electrum" : "BIP39" + visible: root.account.isElectrumMnemonic + } Label { text: qsTr("Derivation") + ":" } diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 1be1035..605247b 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -27,8 +27,11 @@ GridLayout { rowSpacing: 10 property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || (typedData === Wallet.CorrectMnemonic && derivationPath.derivationOk); - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool finished: typedData === Wallet.PrivateKey || ((typedData === Wallet.CorrectMnemonic + || typedData === Wallet.ElectrumMnemonic) + && derivationPath.derivationOk); + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo || typedData === Wallet.ElectrumMnemonic; + property bool isElectrumMnemonic: typedData === Wallet.ElectrumMnemonic property bool isPrivateKey: typedData === Wallet.PrivateKey Label { @@ -73,8 +76,13 @@ GridLayout { var typedData = importAccount.typedData if (typedData === Wallet.PrivateKey) return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) + if (typedData === Wallet.CorrectMnemonic) { + if (forceElectrum.checked) + return qsTr("BIP 39 seed-phrase (interpreted as Electrum format)", "description of type") return qsTr("BIP 39 seed-phrase", "description of type") + } + if (typedData === Wallet.ElectrumMnemonic) + return qsTr("Electrum seed-phrase", "description of type") if (typedData === Wallet.PartialMnemonicWithTypo) return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") if (typedData === Wallet.MissingLexicon) @@ -93,7 +101,7 @@ GridLayout { if (sh === 0) // the genesis was block 1, zero doesn't exist sh = 1; if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); else options = Pay.createImportedWallet(secrets.text, accountName.text, sh) @@ -134,6 +142,27 @@ GridLayout { visible: importAccount.isPrivateKey Layout.columnSpan: 2 } + Flowee.CheckBox { + id: forceElectrum + text: qsTr("Force Electrum"); + toolTipText: qsTr("When enabled, the seed phrase specified will be force-interpreted as an Electrum and/or Electron Cash phrase.") + checked: importAccount.isElectrumMnemonic + visible: importAccount.isMnemonic + enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic + property string prevDerPath: "" + onCheckedChanged: { + derivationLabel.enabled = !checked; + derivationPath.enabled = !checked; + if (checked) { + // Electrum mnemonics always use derivation path: "m/" and never anything else. + prevDerPath = derivationPath.text; + derivationPath.text = "m/"; + } else if (prevDerPath) { + derivationPath.text = prevDerPath; + } + } + Layout.columnSpan: 2 + } Label { text: qsTr("Start Height") + ":" } @@ -142,6 +171,7 @@ GridLayout { validator: IntValidator{bottom: 0; top: 999999} } Label { + id: derivationLabel text: qsTr("Derivation") + ":" visible: !importAccount.isPrivateKey } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 64aa13c..4ebc82c 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -404,6 +404,11 @@ QString AccountInfo::hdWalletMnemonic() const return m_wallet->hdWalletMnemonic(); } +bool AccountInfo::isElectrumMnemonic() const +{ + return m_wallet->isElectrumMnemonic(); +} + QString AccountInfo::hdDerivationPath() const { return m_wallet->derivationPath(); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index f204f51..010fc34 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -54,6 +54,7 @@ class AccountInfo : public QObject Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) Q_PROPERTY(QString mnemonic READ hdWalletMnemonic NOTIFY encryptionChanged) + Q_PROPERTY(bool isElectrumMnemonic READ isElectrumMnemonic CONSTANT) Q_PROPERTY(QString hdDerivationPath READ hdDerivationPath NOTIFY encryptionChanged) Q_PROPERTY(QString xpub READ xpub NOTIFY encryptionChanged) Q_PROPERTY(QDateTime lastMinedTransaction READ lastMinedTransaction NOTIFY balanceChanged) @@ -124,6 +125,8 @@ public: bool isHDWallet() const; /// Return the mnemonic seed that is the basis of this wallet. QString hdWalletMnemonic() const; + /// Return true if the mnemonic seed is in Electrum format + bool isElectrumMnemonic() const; /// Return the derivation base path that is the basis of this wallet. QString hdDerivationPath() const; /// Return the XPub for this wallet (or empty if its not a HD wallet) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index bc348ed..b4a763b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1102,13 +1102,13 @@ void FloweePay::setAppPassword(const QString &password) hasher.finalize(m_appProtectionHash.begin()); } -NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, const QDateTime &date) +NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, const QDateTime &date, bool electrumFormat) { const int height = p2pNet()->blockchain().blockHeightAtTime(date.toSecsSinceEpoch()); - return createImportedHDWallet(mnemonic, password, derivationPathStr, walletName, height); + return createImportedHDWallet(mnemonic, password, derivationPathStr, walletName, height, electrumFormat); } -NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, int startHeight) +NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, int startHeight, bool electrumFormat) { auto wallet = createWallet(walletName); try { @@ -1117,7 +1117,7 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; auto seedWords = splitString(mnemonic); // this is great to remove any type of whitespace wallet->createHDMasterKey(joinWords(seedWords, true), password.trimmed().remove('\n'), - derivationPath, startHeight); + derivationPath, startHeight, electrumFormat); emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -1166,9 +1166,12 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const firstWord = index; if (index != -1) { QString mnemonic = joinWords(words, lowerCased); - auto validity = m_hdSeedValidator.validateMnemonic(mnemonic, index); + bool maybeElectrum; + auto validity = m_hdSeedValidator.validateMnemonic(mnemonic, index, &maybeElectrum); if (validity == Mnemonic::Valid) return WalletEnums::CorrectMnemonic; + else if (maybeElectrum) + return WalletEnums::ElectrumMnemonic; } else { // not a recognized word break; diff --git a/src/FloweePay.h b/src/FloweePay.h index cbc64ae..8461307 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -179,12 +179,15 @@ public: * @param walletName the name the user knows this wallet as. * @param startHeight the first block we should check for transactions, or zero for "future blocks" * If you set this to 1 then we set it to a more sane value of when Bitcoin became more well known. + * @param electrumFormat if true, interpret the mnemonic phrase using the Electrum format rather than BIP39. */ Q_INVOKABLE NewWalletConfig *createImportedHDWallet(const QString &mnemonic, const QString &password, - const QString &derivationPath, const QString &walletName, int startHeight = 0); + const QString &derivationPath, const QString &walletName, int startHeight = 0, + bool electrumFormat = false); /// Alternative arguments version. With date instead of block-height Q_INVOKABLE NewWalletConfig *createImportedHDWallet(const QString &mnemonic, const QString &password, - const QString &derivationPath, const QString &walletName, const QDateTime &date); + const QString &derivationPath, const QString &walletName, const QDateTime &date, + bool electrumFormat = false); Q_INVOKABLE bool checkDerivation(const QString &path) const; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d637bf9..012728e 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -781,6 +781,13 @@ QString Wallet::hdWalletMnemonic() const return QString(); } +bool Wallet::isElectrumMnemonic() const +{ + if (m_hdData.get()) + return m_hdData->electrumFormat; + return false; +} + QString Wallet::hdWalletMnemonicPwd() const { if (m_hdData.get()) @@ -813,7 +820,7 @@ QString Wallet::xpub() const return QString(); } -void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight) +void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight, bool electrumFormat) { assert(m_hdData.get() == nullptr); if (m_hdData.get()) { @@ -833,7 +840,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons pool.write(pwdBytes.constData(), pwdBytes.size()); auto pwdBuf = pool.commit(); - m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf)); + m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, electrumFormat)); // append two random numbers, to make clear the full length m_hdData->derivationPath.push_back(0); m_hdData->derivationPath.push_back(0); @@ -1569,6 +1576,7 @@ void Wallet::loadSecrets() int derivationPathChangeIndex = -1; int derivationPathMainIndex = -1; int index = 0; + bool electrumFormat = false; while (parser.next() == Streaming::FoundTag) { if (parser.tag() == WalletPriv::Separator) { if (index > 0 && secret.address.size() > 0) { @@ -1638,6 +1646,9 @@ void Wallet::loadSecrets() else if (parser.tag() == WalletPriv::HDWalletMnemonicPassword) { mnemonicPwd = parser.bytesDataBuffer(); } + else if (parser.tag() == WalletPriv::ElectrumMnemonicFormat) { + electrumFormat = parser.boolData(); + } else if (parser.tag() == WalletPriv::HDWalletMnemonicPasswordEncrypted) { encryptedMnemonicPwd = parser.bytesData(); m_encryptionLevel = SecretsEncrypted; @@ -1663,7 +1674,7 @@ void Wallet::loadSecrets() if ((xpub.empty() && mnemonic.isEmpty()) != derivationPath.empty()) logFatal(LOG_WALLET) << "Found incomplete data for HD wallet"; else if (!mnemonic.isEmpty()) - m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonic, derivationPath, mnemonicPwd)); + m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonic, derivationPath, mnemonicPwd, electrumFormat)); else if (!xpub.empty()) m_hdData.reset(new HierarchicallyDeterministicWalletData(xpub, derivationPath)); if (m_hdData) { @@ -1699,17 +1710,20 @@ void Wallet::saveSecrets() builder.add(WalletPriv::CryptoChecksum, (uint64_t) m_encryptionChecksum); } builder.add(WalletPriv::WalletVersion, m_walletVersion); + bool hasMnemonic = false; if (m_hdData.get()) { if (m_encryptionLevel == SecretsEncrypted) { // Save encrypted mnemonic and 'password' here, with the unencrypted xpub. assert(!m_hdData->encryptedWalletMnemonic.empty()); builder.add(WalletPriv::HDWalletMnemonicEncrypted, m_hdData->encryptedWalletMnemonic); + hasMnemonic = true; if (!m_hdData->encryptedWalletMnemonicPwd.empty()) builder.add(WalletPriv::HDWalletMnemonicPasswordEncrypted, m_hdData->encryptedWalletMnemonicPwd); builder.add(WalletPriv::HDXPub, m_hdData->masterPubkey.toString()); } else { builder.add(WalletPriv::HDWalletMnemonic, std::string(m_hdData->walletMnemonic.data(), m_hdData->walletMnemonic.size())); + hasMnemonic = true; if (!m_hdData->walletMnemonicPwd.empty()) builder.add(WalletPriv::HDWalletMnemonicPassword, std::string(m_hdData->walletMnemonicPwd.data(), m_hdData->walletMnemonicPwd.size())); @@ -1721,6 +1735,8 @@ void Wallet::saveSecrets() builder.add(WalletPriv::HDWalletLastChangeIndex, m_hdData->lastChangeKey); if (m_hdData->lastMainKey >= 0) builder.add(WalletPriv::HDWalletLastReceiveIndex, m_hdData->lastMainKey); + if (hasMnemonic && m_hdData->electrumFormat) + builder.add(WalletPriv::ElectrumMnemonicFormat, true); } for (const auto &item : m_walletSecrets) { const auto &secret = item.second; diff --git a/src/Wallet.h b/src/Wallet.h index 7cb167b..b7679b8 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -324,6 +324,8 @@ public: bool isHDWallet() const; /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) QString hdWalletMnemonic() const; + /// Returns true if the wallet mnemonic is in Electrum format + bool isElectrumMnemonic() const; /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) passphrase QString hdWalletMnemonicPwd() const; /// Provided that this is a HD wallet, return the derivation path used for this wallet. @@ -339,8 +341,9 @@ public: * @param pwd the password that was created with the seed-phrase. Can be empty. * @param derivationPath the derivation steps. We will add 2 ints to this internally, so typically this vector has 3 fields. * @param initialBlockHeight either a timestamp or blockheight, or zero if unsure. + * @param electrumFormat Set to true to interpret this mnemonic as an Electrum-style phrase. */ - void createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight = 0); + void createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight = 0, bool electrumFormat = false); /// return the height of the last seen transaction that is mined int lastTransactionTimestamp() const; @@ -449,12 +452,14 @@ private: struct HierarchicallyDeterministicWalletData { /// the strings should have utf8 encoded text. - HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd); + HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd, + bool electrumFormat = false); HierarchicallyDeterministicWalletData(const std::string &xpub, const std::vector &derivationPath); HDMasterKey masterKey; HDMasterPubkey masterPubkey; std::vector > walletMnemonic; // utf8-encoding std::vector > walletMnemonicPwd;// utf8-encoding + bool electrumFormat = false; std::vector encryptedWalletMnemonic; std::vector encryptedWalletMnemonicPwd; diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 09e1d19..9bcd2b3 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -36,7 +36,8 @@ public: PartialMnemonic, PartialMnemonicWithTypo, CorrectMnemonic, - MissingLexicon + MissingLexicon, + ElectrumMnemonic, }; Q_ENUM(StringType) diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 828150a..09d4723 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -352,11 +352,12 @@ void Wallet::forgetEncryptedSecrets() // ////////////////////////////////////////////////// -Wallet::HierarchicallyDeterministicWalletData::HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd) - : masterKey(HDMasterKey::fromMnemonic(std::string(seedWords.begin(), seedWords.end()), std::string(pwd.begin(), pwd.end()))), +Wallet::HierarchicallyDeterministicWalletData::HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd, bool electrum) + : masterKey(HDMasterKey::fromMnemonic(std::string(seedWords.begin(), seedWords.end()), std::string(pwd.begin(), pwd.end()), electrum)), masterPubkey(HDMasterPubkey::fromHDMaster(masterKey, derivationPath)), walletMnemonic(seedWords.begin(), seedWords.end()), walletMnemonicPwd(pwd.begin(), pwd.end()), + electrumFormat(electrum), derivationPath(derivationPath) { } diff --git a/src/Wallet_p.h b/src/Wallet_p.h index ab0b921..f90f939 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -78,6 +78,7 @@ enum PrivateSaveTags { HDWalletMnemonicPasswordEncrypted, HDXPub, + ElectrumMnemonicFormat, // bool END_OF_PRIV_SAVE_TAGS }; -- 2.54.0 From 5b5a9f9c960b958e5b978a6f7a6eebe9fba271b2 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Wed, 18 Oct 2023 21:35:07 +0300 Subject: [PATCH 0697/1428] Added Electrum mnemonic format changes to mobile app UI This works just like the desktop app. --- guis/mobile/AccountPageListItem.qml | 16 ++++++++++++ guis/mobile/ImportWalletPage.qml | 38 ++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index a91ed22..711fe7e 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -144,6 +144,22 @@ QQC2.Control { } } + PageTitledBox { + title: qsTr("Seed format") + visible: root.account.isElectrumMnemonic + Item { + implicitHeight: electrumLabel.implicitHeight + width: parent.width + Flowee.Label { + id: electrumLabel + text: "Electrum" + width: parent.width - 36 + font.italic: true + font.wordSpacing: 1 + } + } + } + PageTitledBox { title: qsTr("Starting Height", "height refers to block-height") Flowee.LabelWithClipboard { text: root.account.initialBlockHeight } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index bfdc294..26207d8 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -26,8 +26,11 @@ Page { headerText: qsTr("Import Wallet") property var typedData: Pay.identifyString(secrets.totalText) - property bool finished: typedData === Wallet.PrivateKey || (typedData === Wallet.CorrectMnemonic && derivationPath.derivationOk); - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo; + property bool finished: typedData === Wallet.PrivateKey || ((typedData === Wallet.CorrectMnemonic + || typedData === Wallet.ElectrumMnemonic) + && derivationPath.derivationOk); + property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo || typedData === Wallet.ElectrumMnemonic; + property bool isElectrumMnemonic: typedData === Wallet.ElectrumMnemonic property bool isPrivateKey: typedData === Wallet.PrivateKey Flickable { @@ -91,8 +94,13 @@ Page { var typedData = importAccount.typedData if (typedData === Wallet.PrivateKey) return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) + if (typedData === Wallet.CorrectMnemonic) { + if (forceElectrum.checked) + return qsTr("BIP 39 seed-phrase (interpreted as Electrum)", "description of type") return qsTr("BIP 39 seed-phrase", "description of type") + } + if (typedData === Wallet.ElectrumMnemonic) + return qsTr("Electrum seed-phrase", "description of type") if (typedData === Wallet.PartialMnemonicWithTypo) return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") if (typedData === Wallet.MissingLexicon) @@ -116,6 +124,27 @@ Page { checked: true visible: importAccount.isPrivateKey } + Flowee.CheckBox { + id: forceElectrum + text: qsTr("Force Electrum"); + toolTipText: qsTr("When enabled, the seed phrase specified will be force-interpreted as an Electrum and/or Electron Cash phrase.") + checked: importAccount.isElectrumMnemonic + visible: importAccount.isMnemonic + enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic + property string prevDerPath: "" + onCheckedChanged: { + derivationLabel.enabled = !checked; + derivationPath.enabled = !checked; + if (checked) { + // Electrum mnemonics always use derivation path: "m/" and never anything else. + prevDerPath = derivationPath.text; + derivationPath.text = "m/"; + } else if (prevDerPath) { + derivationPath.text = prevDerPath; + } + } + } + PageTitledBox { title: qsTr("Oldest Transaction") Flow { @@ -149,6 +178,7 @@ Page { } PageTitledBox { + id: derivationLabel title: qsTr("Derivation") visible: !importAccount.isPrivateKey Flowee.TextField { @@ -181,7 +211,7 @@ Page { onClicked: { var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh); + var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); else options = Pay.createImportedWallet(secrets.text, accountName.text, sh) -- 2.54.0 From 95ff48c31440af47ecbf9856b73858d3abc40067 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Thu, 19 Oct 2023 18:13:32 +0300 Subject: [PATCH 0698/1428] Implement reviewer suggestion plus fixes In addition: - Made it work with latest commit to Flowee/thehub/#5. - Works better now when decrypting the wallet (accountInfo.isElectrum property should not be CONSTANT but instead notify on change) - Made the actual wallet seed phrase type get saved to the wallet rather than a bool. This type comes from enum HDMasterKey::MnemonicType in thehub libs. --- guis/desktop/AccountDetails.qml | 2 +- guis/mobile/AccountPageListItem.qml | 13 +++---------- src/AccountInfo.h | 2 +- src/Wallet.cpp | 25 +++++++++++++------------ src/Wallet.h | 4 ++-- src/Wallet_encryption.cpp | 8 ++++---- src/Wallet_p.h | 2 +- 7 files changed, 25 insertions(+), 31 deletions(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 44933bc..79b4a72 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -301,7 +301,7 @@ Item { Label { id: seedPhraseFormat font.bold: true - text: root.account.isElectrumMnemonic ? "Electrum" : "BIP39" + text: "Electrum" visible: root.account.isElectrumMnemonic } Label { diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 711fe7e..fef104e 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -147,16 +147,9 @@ QQC2.Control { PageTitledBox { title: qsTr("Seed format") visible: root.account.isElectrumMnemonic - Item { - implicitHeight: electrumLabel.implicitHeight - width: parent.width - Flowee.Label { - id: electrumLabel - text: "Electrum" - width: parent.width - 36 - font.italic: true - font.wordSpacing: 1 - } + Flowee.Label { + text: "Electrum" + font.italic: true } } diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 010fc34..95cd6f6 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -54,7 +54,7 @@ class AccountInfo : public QObject Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) Q_PROPERTY(QString mnemonic READ hdWalletMnemonic NOTIFY encryptionChanged) - Q_PROPERTY(bool isElectrumMnemonic READ isElectrumMnemonic CONSTANT) + Q_PROPERTY(bool isElectrumMnemonic READ isElectrumMnemonic NOTIFY encryptionChanged) Q_PROPERTY(QString hdDerivationPath READ hdDerivationPath NOTIFY encryptionChanged) Q_PROPERTY(QString xpub READ xpub NOTIFY encryptionChanged) Q_PROPERTY(QDateTime lastMinedTransaction READ lastMinedTransaction NOTIFY balanceChanged) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 012728e..03a2713 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -784,7 +784,7 @@ QString Wallet::hdWalletMnemonic() const bool Wallet::isElectrumMnemonic() const { if (m_hdData.get()) - return m_hdData->electrumFormat; + return m_hdData->mnemonicFormat == HDMasterKey::ElectrumMnemonic; return false; } @@ -840,7 +840,9 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons pool.write(pwdBytes.constData(), pwdBytes.size()); auto pwdBuf = pool.commit(); - m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, electrumFormat)); + m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, + electrumFormat ? HDMasterKey::ElectrumMnemonic + : HDMasterKey::BIP39Mnemonic)); // append two random numbers, to make clear the full length m_hdData->derivationPath.push_back(0); m_hdData->derivationPath.push_back(0); @@ -1576,7 +1578,7 @@ void Wallet::loadSecrets() int derivationPathChangeIndex = -1; int derivationPathMainIndex = -1; int index = 0; - bool electrumFormat = false; + HDMasterKey::MnemonicType mnemonicFormat = HDMasterKey::BIP39Mnemonic; while (parser.next() == Streaming::FoundTag) { if (parser.tag() == WalletPriv::Separator) { if (index > 0 && secret.address.size() > 0) { @@ -1646,8 +1648,8 @@ void Wallet::loadSecrets() else if (parser.tag() == WalletPriv::HDWalletMnemonicPassword) { mnemonicPwd = parser.bytesDataBuffer(); } - else if (parser.tag() == WalletPriv::ElectrumMnemonicFormat) { - electrumFormat = parser.boolData(); + else if (parser.tag() == WalletPriv::HDWalletMnemonicFormat) { + mnemonicFormat = static_cast(parser.intData()); } else if (parser.tag() == WalletPriv::HDWalletMnemonicPasswordEncrypted) { encryptedMnemonicPwd = parser.bytesData(); @@ -1671,10 +1673,12 @@ void Wallet::loadSecrets() } assert(m_hdData.get() == nullptr); + if (mnemonicFormat != HDMasterKey::BIP39Mnemonic && mnemonicFormat != HDMasterKey::ElectrumMnemonic) + logFatal(LOG_WALLET) << "Found unknown mnemonic type for HD wallet:" << int(mnemonicFormat); if ((xpub.empty() && mnemonic.isEmpty()) != derivationPath.empty()) logFatal(LOG_WALLET) << "Found incomplete data for HD wallet"; else if (!mnemonic.isEmpty()) - m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonic, derivationPath, mnemonicPwd, electrumFormat)); + m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonic, derivationPath, mnemonicPwd, mnemonicFormat)); else if (!xpub.empty()) m_hdData.reset(new HierarchicallyDeterministicWalletData(xpub, derivationPath)); if (m_hdData) { @@ -1682,6 +1686,7 @@ void Wallet::loadSecrets() m_hdData->lastMainKey = derivationPathMainIndex; m_hdData->encryptedWalletMnemonic = encryptedMnemonic; m_hdData->encryptedWalletMnemonicPwd = encryptedMnemonicPwd; + m_hdData->mnemonicFormat = mnemonicFormat; } m_secretsChanged = false; @@ -1701,7 +1706,7 @@ void Wallet::saveSecrets() hdDataSize += m_hdData->encryptedWalletMnemonicPwd.size(); hdDataSize += m_hdData->encryptedWalletMnemonic.size(); hdDataSize += m_hdData->derivationPath.size() * 6; - hdDataSize += 100; // for the xpub, lastChangeIndex, lastReceiveIndex + hdDataSize += 104; // for the xpub, lastChangeIndex, lastReceiveIndex, mnemonicFormat } Streaming::MessageBuilder builder(Streaming::pool(100 + m_walletSecrets.size() * 90 + hdDataSize)); @@ -1710,20 +1715,17 @@ void Wallet::saveSecrets() builder.add(WalletPriv::CryptoChecksum, (uint64_t) m_encryptionChecksum); } builder.add(WalletPriv::WalletVersion, m_walletVersion); - bool hasMnemonic = false; if (m_hdData.get()) { if (m_encryptionLevel == SecretsEncrypted) { // Save encrypted mnemonic and 'password' here, with the unencrypted xpub. assert(!m_hdData->encryptedWalletMnemonic.empty()); builder.add(WalletPriv::HDWalletMnemonicEncrypted, m_hdData->encryptedWalletMnemonic); - hasMnemonic = true; if (!m_hdData->encryptedWalletMnemonicPwd.empty()) builder.add(WalletPriv::HDWalletMnemonicPasswordEncrypted, m_hdData->encryptedWalletMnemonicPwd); builder.add(WalletPriv::HDXPub, m_hdData->masterPubkey.toString()); } else { builder.add(WalletPriv::HDWalletMnemonic, std::string(m_hdData->walletMnemonic.data(), m_hdData->walletMnemonic.size())); - hasMnemonic = true; if (!m_hdData->walletMnemonicPwd.empty()) builder.add(WalletPriv::HDWalletMnemonicPassword, std::string(m_hdData->walletMnemonicPwd.data(), m_hdData->walletMnemonicPwd.size())); @@ -1735,8 +1737,7 @@ void Wallet::saveSecrets() builder.add(WalletPriv::HDWalletLastChangeIndex, m_hdData->lastChangeKey); if (m_hdData->lastMainKey >= 0) builder.add(WalletPriv::HDWalletLastReceiveIndex, m_hdData->lastMainKey); - if (hasMnemonic && m_hdData->electrumFormat) - builder.add(WalletPriv::ElectrumMnemonicFormat, true); + builder.add(WalletPriv::HDWalletMnemonicFormat, static_cast(m_hdData->mnemonicFormat)); } for (const auto &item : m_walletSecrets) { const auto &secret = item.second; diff --git a/src/Wallet.h b/src/Wallet.h index b7679b8..3d07165 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -453,13 +453,13 @@ private: struct HierarchicallyDeterministicWalletData { /// the strings should have utf8 encoded text. HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd, - bool electrumFormat = false); + HDMasterKey::MnemonicType mnemonicFormat); HierarchicallyDeterministicWalletData(const std::string &xpub, const std::vector &derivationPath); HDMasterKey masterKey; HDMasterPubkey masterPubkey; std::vector > walletMnemonic; // utf8-encoding std::vector > walletMnemonicPwd;// utf8-encoding - bool electrumFormat = false; + HDMasterKey::MnemonicType mnemonicFormat = HDMasterKey::BIP39Mnemonic; std::vector encryptedWalletMnemonic; std::vector encryptedWalletMnemonicPwd; diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 09d4723..8e756e4 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -247,7 +247,7 @@ bool Wallet::decrypt(const QString &password) } std::string seedWords(m_hdData->walletMnemonic.begin(), m_hdData->walletMnemonic.end()); std::string pwd(m_hdData->walletMnemonicPwd.begin(), m_hdData->walletMnemonicPwd.end()); - m_hdData->masterKey = HDMasterKey::fromMnemonic(seedWords, pwd); + m_hdData->masterKey = HDMasterKey::fromMnemonic(seedWords, m_hdData->mnemonicFormat, pwd); assert(m_hdData->masterKey.isValid()); } @@ -352,12 +352,12 @@ void Wallet::forgetEncryptedSecrets() // ////////////////////////////////////////////////// -Wallet::HierarchicallyDeterministicWalletData::HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd, bool electrum) - : masterKey(HDMasterKey::fromMnemonic(std::string(seedWords.begin(), seedWords.end()), std::string(pwd.begin(), pwd.end()), electrum)), +Wallet::HierarchicallyDeterministicWalletData::HierarchicallyDeterministicWalletData(const Streaming::ConstBuffer &seedWords, const std::vector &derivationPath, const Streaming::ConstBuffer &pwd, HDMasterKey::MnemonicType mnemonicFmt) + : masterKey(HDMasterKey::fromMnemonic(std::string(seedWords.begin(), seedWords.end()), mnemonicFmt, std::string(pwd.begin(), pwd.end()))), masterPubkey(HDMasterPubkey::fromHDMaster(masterKey, derivationPath)), walletMnemonic(seedWords.begin(), seedWords.end()), walletMnemonicPwd(pwd.begin(), pwd.end()), - electrumFormat(electrum), + mnemonicFormat(mnemonicFmt), derivationPath(derivationPath) { } diff --git a/src/Wallet_p.h b/src/Wallet_p.h index f90f939..e3a6b8e 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -78,7 +78,7 @@ enum PrivateSaveTags { HDWalletMnemonicPasswordEncrypted, HDXPub, - ElectrumMnemonicFormat, // bool + HDWalletMnemonicFormat, // int, one of the HDMasterKey::MnemonicType enums values, if missing default to BIP39 END_OF_PRIV_SAVE_TAGS }; -- 2.54.0 From 7ba2740986cf37940064efefc36fabb0e3e7ce0b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Oct 2023 16:50:14 +0200 Subject: [PATCH 0699/1428] Work on basic blockheaders changes The flowee lib added support for various blockheader improvements. An important one is the ability to start from a checkpoint and thus lower the requirements at the cost of a slightly lower security. This adds support for that. The local part is that we stop shipping the 'info' file (some 25MB for the full chain) in the APK / deb files, instead buiding it on first start. --- src/WalletHistoryModel.cpp | 10 ++++------ src/main_utils.cpp | 16 ++++++++++++++++ src/main_utils_android.cpp | 26 +++++++++++--------------- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index f9afbde..51d69ba 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -375,11 +375,6 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) } else { timestamp = secsSinceEpochFor(blockheight); } - assert(timestamp > 0); - if (timestamp == 0) { - // some inconsistency between wallet and libp2p. - return; - } if (!m_groups.back().add(txIndex, timestamp, m_today)) { // didn't fit, make a new group and add it there. @@ -446,7 +441,10 @@ bool WalletHistoryModel::isModelFrozen() const uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const { - return FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime; + // wrap this for convenience and also ensure that we never return an insanely old + // date (1970) just because we lack blockheader data. + return std::max(1250000000, + FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime); } QDate WalletHistoryModel::today() const diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 0534884..37005af 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -126,6 +126,22 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) } else { QString infoFilePath = blockheaders->fileName() + ".info"; + bool needsCreation = false; + if (!QFile::exists(infoFilePath)) { + // early versions of Flowee Pay shipped the info file together with the + // static file in the original install, lets check if we should instead + // create one here on first run. + infoFilePath = FloweePay::instance()->basedir() + "/staticHeaders.info"; + QFileInfo meta(infoFilePath); + QFileInfo data(blockheaders->fileName()); + if (!meta.exists()) + needsCreation = true; + else if (data.lastModified() > meta.lastModified()) + needsCreation = true; + } + if (needsCreation) // create a user-local info file. + Blockchain::createStaticHeaders(blockheaders->fileName().toStdString(), + infoFilePath.toStdString()); Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), infoFilePath.toStdString()); blockheaders->close(); diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 3dd422b..e339036 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -63,39 +63,35 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) * copy of those files to our private homedir. */ QFileInfo target("staticHeaders"); - QFileInfo infTarget("staticHeaders.info"); QFileInfo orig("assets:/blockheaders"); - QFileInfo infOrig("assets:/blockheaders.info"); + const QString infoFilePath = target.absoluteFilePath() + ".info"; - bool install = orig.exists() && infOrig.exists() - && (!target.exists() || !infTarget.exists() || orig.size() != target.size() - || infOrig.size() != infTarget.size()); + bool install = orig.exists() && (!target.exists() || orig.size() > target.size()); if (install) { - // make sure we have a local up-to-date copy + // make sure we have a local copy + QFile::remove(infoFilePath); QFile::remove(target.absoluteFilePath()); bool ok = QFile::copy(orig.filePath(), target.absoluteFilePath()); if (!ok) { logFatal() << "Failed copying blockheaders"; abort(); } - QFile::remove(infTarget.absoluteFilePath()); - ok = QFile::copy(orig.filePath() + ".info", infTarget.absoluteFilePath()); - if (!ok) { - logFatal() << "Failed copying blockheaders.info"; - abort(); - } } - if (!target.exists() || !infTarget.exists()) // We didn't find a source for the headers + if (!target.exists()) // We didn't find a source for the headers abort(); blockheaders.reset(new QFile(target.absoluteFilePath())); - if (!blockheaders->open(QIODevice::ReadOnly)) { // can't be opened for reading. + if (!blockheaders->open(QIODevice::ReadOnly)) { // if can't be opened for reading. blockheaders.reset(); return blockheaders; } + // if not previously created, create metadata file now. + if (!QFile::exists(infoFilePath)) + Blockchain::createStaticHeaders(blockheaders->fileName().toStdString(), + infoFilePath.toStdString()); Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), - infTarget.absoluteFilePath().toStdString()); + infoFilePath.toStdString()); blockheaders->close(); return blockheaders; } -- 2.54.0 From f99fa5d10909eb5a7dc85d9ad9fc22cc93b9948a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Oct 2023 19:10:38 +0200 Subject: [PATCH 0700/1428] new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index b958bed..1fc52a1 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="18" android:versionName="2023.10.0"> diff --git a/src/main.cpp b/src/main.cpp index dbb4989..7361f02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.09.0"); + qapp.setApplicationVersion("2023.10.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 1cdd10ac6699e84272fb98b9c4e20a3e4149786d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Oct 2023 20:32:31 +0200 Subject: [PATCH 0701/1428] Ensure file-format stays the same. These enum values are defined in an external lib, as such we make sure that any changes in them are instantly detected here. --- src/Wallet.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 03a2713..1b87575 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1649,6 +1649,8 @@ void Wallet::loadSecrets() mnemonicPwd = parser.bytesDataBuffer(); } else if (parser.tag() == WalletPriv::HDWalletMnemonicFormat) { + static_assert(HDMasterKey::BIP39Mnemonic == 0); + static_assert(HDMasterKey::ElectrumMnemonic == 1); mnemonicFormat = static_cast(parser.intData()); } else if (parser.tag() == WalletPriv::HDWalletMnemonicPasswordEncrypted) { -- 2.54.0 From b83202cd0aa62fe3939d2ae43126fb8a38dedc2b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Oct 2023 13:23:12 +0200 Subject: [PATCH 0702/1428] Stop including the info file in the APK --- android/build-pay.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 43b7e16..4ded814 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -58,7 +58,6 @@ fi mkdir -p imports cp -f "$_pay_native_name_"/*qm imports/ -cp -f "$_pay_native_name_"/blockheaders-meta-extractor imports/ floweePaySrcDir=`dirname $0`/.. if test -f $floweePaySrcDir/android/netlog.conf; then @@ -155,9 +154,6 @@ if test ! -f build.ninja; then \$execInDocker build/.config fi -if test -f android-build/assets/blockheaders -a ! -f android-build/assets/blockheaders.info; then - imports/blockheaders-meta-extractor android-build/assets -fi if test -f $floweePaySrcDir/android/netlog.conf; then cp $floweePaySrcDir/android/netlog.conf android-build/assets/ fi -- 2.54.0 From e67d5a1453e6611279d98380b0b7655bab6c0d55 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 26 Oct 2023 23:34:51 +0200 Subject: [PATCH 0703/1428] {Android} fetch phone dark-theme setting. This calls Java code on Android through the Qt JNI bridge in order to learn the phone-wide setting of dark-theme. For new installs this will now follow the phone setting by default. Add GuiSettings: dark mode option. --- guis/Flowee/RadioButton.qml | 56 +++++++++++++++++++++++++++++++++++++ guis/mobile/GuiSettings.qml | 25 +++++++++++++++++ guis/mobile/MenuOverlay.qml | 6 +--- guis/mobile/main.qml | 3 +- guis/widgets.qrc | 1 + src/FloweePay.cpp | 50 +++++++++++++++++++++++++++++++++ src/FloweePay.h | 7 +++++ src/main_utils_android.cpp | 5 ++-- 8 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 guis/Flowee/RadioButton.qml diff --git a/guis/Flowee/RadioButton.qml b/guis/Flowee/RadioButton.qml new file mode 100644 index 0000000..753ed3a --- /dev/null +++ b/guis/Flowee/RadioButton.qml @@ -0,0 +1,56 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Templates as T + +T.RadioButton { + id: control + + implicitWidth: 140 + implicitHeight: contentItem.contentHeight + 10 + spacing: 6 + + contentItem: Label { + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.down ? palette.midlight : palette.text + verticalAlignment: Text.AlignVCenter + leftPadding: control.indicator.width + control.spacing + } + + indicator: Rectangle { + implicitWidth: control.implicitHeight * 0.6 // effectively based on font size + implicitHeight: implicitWidth + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: implicitWidth / 2 + color: "#00000000" // transparant background + border.color: palette.button + + Rectangle { + width: parent.width * 0.8 + height: parent.height * 0.8 + anchors.centerIn: parent + radius: width / 2 + color: palette.highlight + visible: control.checked + } + } +} diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index cf82e86..1447617 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -64,6 +64,31 @@ Page { } } + PageTitledBox { + title: qsTr("Dark Theme") + Flowee.RadioButton { + text: "Follow System" + checked: Pay.skinFollowsPlatform + onClicked: Pay.skinFollowsPlatform = true; + } + Flowee.RadioButton { + text: "Dark" + checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = true; + } + } + Flowee.RadioButton { + text: "Light" + checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = false; + } + } + } + PageTitledBox { title: qsTr("Unit") diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 8668eac..95b98b6 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -18,7 +18,6 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts -import "../ControlColors.js" as ControlColors import "../Flowee" as Flowee Item { @@ -98,10 +97,7 @@ Item { MouseArea { anchors.fill: parent anchors.margins: -10 - onClicked: { - Pay.useDarkSkin = !Pay.useDarkSkin - ControlColors.applySkin(mainWindow) - } + onClicked: Pay.useDarkSkin = !Pay.useDarkSkin } } Flowee.Label { diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index a02fba2..cf7aa3e 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -32,7 +32,7 @@ ApplicationWindow { minimumWidth: 300 minimumHeight: 400 visible: true - onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow) + onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow); property bool isLoading: typeof portfolio === "undefined"; onIsLoadingChanged: { @@ -54,6 +54,7 @@ ApplicationWindow { Connections { target: Pay function onFontScalingChanged() { updateFontSize(); } + function onUseDarkSkinChanged() { ControlColors.applySkin(mainWindow); } } property color floweeSalmon: "#ff9d94" diff --git a/guis/widgets.qrc b/guis/widgets.qrc index fcf7725..82b4ddc 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -22,6 +22,7 @@ Flowee/LabelWithClipboard.qml Flowee/ListViewKeyHandler.qml Flowee/MultilineTextField.qml + Flowee/RadioButton.qml Flowee/ScrollThumb.qml Flowee/TabBar.qml Flowee/TextField.qml diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b4a763b..4535b40 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -46,6 +46,10 @@ #include #include +#ifdef TARGET_OS_Android +# include +#endif + #include #include #include @@ -57,6 +61,7 @@ constexpr const char *WINDOW_WIDTH = "window/width"; constexpr const char *WINDOW_HEIGHT = "window/height"; constexpr const char *FONTSCALING = "window/font-scaling"; constexpr const char *DARKSKIN = "darkSkin"; +constexpr const char *DARKSKIN_FROM_PLATFORM = "darkSkin-from-platform"; constexpr const char *ACTIVITYSHOWBCH = "activity-show-bch"; constexpr const char *HIDEBALANCE = "hide-balance"; constexpr const char *USERAGENT = "net/useragent"; @@ -203,7 +208,20 @@ FloweePay::FloweePay() m_unit = static_cast(appConfig.value(UNIT_TYPE, m_unit).toInt()); m_windowHeight = appConfig.value(WINDOW_HEIGHT, m_windowHeight).toInt(); m_windowWidth = appConfig.value(WINDOW_WIDTH, m_windowWidth).toInt(); + + /* + * Darkskin and darkskinFromSystem were introduced one after the other. + * People that have darkskin stored, but not darkskinFromSystem thus need to + * be migrated from the old setup to the new. + * For migrating users, the darkSkinFromSystem is set to false. For + * new users (i.e. everyone else), it defaults to true. + */ + bool defaultPlatform = true; + if (!appConfig.value(DARKSKIN_FROM_PLATFORM).isValid() && appConfig.value(DARKSKIN).isValid()) + defaultPlatform = false; + m_darkSkin = appConfig.value(DARKSKIN, m_darkSkin).toBool(); + setSkinFollowsPlatform(appConfig.value(DARKSKIN_FROM_PLATFORM, defaultPlatform).toBool()); m_activityShowsBch = appConfig.value(ACTIVITYSHOWBCH, m_activityShowsBch).toBool(); m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); @@ -871,6 +889,38 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +bool FloweePay::skinFollowsPlatform() const +{ + return m_skinFollowsPlatform; +} + +void FloweePay::setSkinFollowsPlatform(bool newSkinFollowsPlatform) +{ + if (m_skinFollowsPlatform == newSkinFollowsPlatform) + return; + m_skinFollowsPlatform = newSkinFollowsPlatform; + emit skinFollowsPlatformChanged(); + QSettings appConfig; + appConfig.setValue(DARKSKIN_FROM_PLATFORM, m_skinFollowsPlatform); + +#ifdef TARGET_OS_Android + if (newSkinFollowsPlatform) { + auto context = QJniObject(QNativeInterface::QAndroidApplication::context()); + // In Java: + // context.getResources().getConfiguration().uiMode + auto resources = context.callObjectMethod("getResources", + "()Landroid/content/res/Resources;"); + auto config = resources.callObjectMethod("getConfiguration", + "()Landroid/content/res/Configuration;"); + auto uiMode = config.getField("uiMode"); + constexpr int UI_MODE_NIGHT_MASK = 0x30; + constexpr int UI_MODE_NIGHT_YES = 0x20; + const bool dark = (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; + setDarkSkin(dark); + } +#endif +} + QString FloweePay::paymentProtocolRequest() const { return m_paymentProtocolRequest; diff --git a/src/FloweePay.h b/src/FloweePay.h index 8461307..183a704 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -58,6 +58,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(int windowWidth READ windowWidth WRITE setWindowWidth NOTIFY windowWidthChanged) Q_PROPERTY(int windowHeight READ windowHeight WRITE setWindowHeight NOTIFY windowHeightChanged) Q_PROPERTY(bool useDarkSkin READ darkSkin WRITE setDarkSkin NOTIFY darkSkinChanged) + Q_PROPERTY(bool skinFollowsPlatform READ skinFollowsPlatform WRITE setSkinFollowsPlatform NOTIFY skinFollowsPlatformChanged FINAL) Q_PROPERTY(bool hideBalance READ hideBalance WRITE setHideBalance NOTIFY hideBalanceChanged) Q_PROPERTY(bool activityShowsBch READ activityShowsBch WRITE setActivityShowsBch NOTIFY activityShowsBchChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) @@ -222,6 +223,9 @@ public: bool darkSkin() const; void setDarkSkin(bool darkSkin); + bool skinFollowsPlatform() const; + void setSkinFollowsPlatform(bool newSkinFollowsPlatform); + UnitOfBitcoin unit() const; void setUnit(const UnitOfBitcoin &unit); @@ -343,6 +347,8 @@ signals: void appProtectionChanged(); void paymentProtocolRequestChanged(); + void skinFollowsPlatformChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -387,6 +393,7 @@ private: bool m_loadCompletEmitted = false; // ensure we only emit this once. bool m_appUnlocked = false; bool m_darkSkin = true; + bool m_skinFollowsPlatform = false; bool m_createStartWallet = false; bool m_hideBalance = false; /// Show the BCH amount involved in a transaction on the activity screen. diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index e339036..41ba61d 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -19,12 +19,13 @@ #include "PortfolioDataProvider.h" #include "NetDataProvider.h" +#include + #include #include #include -#include +#include -#include struct CommandLineParserData { -- 2.54.0 From d5f53acc39dc49a8a9676394c5064320cd5ad26e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Oct 2023 11:34:29 +0100 Subject: [PATCH 0704/1428] Fix display bug, missing texts. --- guis/mobile/AccountPageListItem.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index fef104e..3eb46cc 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -214,14 +214,12 @@ QQC2.Control { PageTitledBox { id: optionsBox Flowee.CheckBox { - width: parent.width text: qsTr("Change Addresses") visible: root.account.isHDWallet onClicked: root.account.secrets.showChangeChain = checked toolTipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.") } Flowee.CheckBox { - width: parent.width text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked -- 2.54.0 From 2600dd960ca8a6ff2da7e8be4a3ac2bd70f43239 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Oct 2023 15:59:12 +0100 Subject: [PATCH 0705/1428] Remove stop --- guis/mobile/main.qml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index cf7aa3e..21cfa7e 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -69,14 +69,6 @@ ApplicationWindow { initialItem: "./Loading.qml" onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); enabled: !menuOverlay.open - - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { - // We eat this and have nothing happen. - // This is useful to avoid the app closing on pressing 'back' one time too many on Android - event.accepted = true; - } - } } MenuOverlay { id: menuOverlay -- 2.54.0 From 896854bc2d85389eb9ab3c26f80ecad8672ebeaa Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Oct 2023 22:46:22 +0100 Subject: [PATCH 0706/1428] Import translations from crowdin --- CMakeLists.txt | 5 + translations/desktop-i18n.qrc | 4 + translations/floweepay-common_de.ts | 60 +- translations/floweepay-common_en.ts | 60 +- translations/floweepay-common_es.ts | 60 +- translations/floweepay-common_nl.ts | 60 +- translations/floweepay-common_pl.ts | 98 +- translations/floweepay-common_pt.ts | 451 ++++++++ translations/floweepay-desktop_de.ts | 138 +-- translations/floweepay-desktop_en.ts | 138 +-- translations/floweepay-desktop_es.ts | 270 ++--- translations/floweepay-desktop_nl.ts | 138 +-- translations/floweepay-desktop_pl.ts | 172 +-- translations/floweepay-desktop_pt.ts | 1152 ++++++++++++++++++ translations/floweepay-mobile_de.ts | 38 +- translations/floweepay-mobile_en.ts | 38 +- translations/floweepay-mobile_es.ts | 38 +- translations/floweepay-mobile_nl.ts | 38 +- translations/floweepay-mobile_pl.ts | 98 +- translations/floweepay-mobile_pt.ts | 1155 +++++++++++++++++++ translations/mobile-i18n.qrc | 4 + translations/module-build-transaction_pl.ts | 60 +- translations/module-build-transaction_pt.ts | 169 +++ translations/module-peers-view_pl.ts | 6 +- translations/module-peers-view_pt.ts | 66 ++ 25 files changed, 3886 insertions(+), 630 deletions(-) create mode 100644 translations/floweepay-common_pt.ts create mode 100644 translations/floweepay-desktop_pt.ts create mode 100644 translations/floweepay-mobile_pt.ts create mode 100644 translations/module-build-transaction_pt.ts create mode 100644 translations/module-peers-view_pt.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 119473a..98bcaa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,30 +84,35 @@ if(NOT ANDROID) translations/floweepay-desktop_pl.ts translations/floweepay-desktop_de.ts translations/floweepay-desktop_es.ts + translations/floweepay-desktop_pt.ts translations/floweepay-common_en.ts translations/floweepay-common_nl.ts translations/floweepay-common_pl.ts translations/floweepay-common_de.ts translations/floweepay-common_es.ts + translations/floweepay-common_pt.ts translations/floweepay-mobile_en.ts translations/floweepay-mobile_nl.ts translations/floweepay-mobile_pl.ts translations/floweepay-mobile_de.ts translations/floweepay-mobile_es.ts + translations/floweepay-mobile_pt.ts translations/module-build-transaction_en.ts translations/module-build-transaction_nl.ts translations/module-build-transaction_pl.ts translations/module-build-transaction_de.ts translations/module-build-transaction_es.ts + translations/module-build-transaction_pt.ts translations/module-peers-view_en.ts translations/module-peers-view_nl.ts translations/module-peers-view_pl.ts translations/module-peers-view_de.ts translations/module-peers-view_es.ts + translations/module-peers-view_pt.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/desktop-i18n.qrc b/translations/desktop-i18n.qrc index c7eba4b..d8a6877 100644 --- a/translations/desktop-i18n.qrc +++ b/translations/desktop-i18n.qrc @@ -4,9 +4,13 @@ floweepay-desktop_pl.qm floweepay-desktop_en.qm floweepay-desktop_de.qm + floweepay-desktop_es.qm + floweepay-desktop_pt.qm floweepay-common_nl.qm floweepay-common_pl.qm floweepay-common_en.qm floweepay-common_de.qm + floweepay-common_pt.qm + floweepay-common_es.qm diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index ac1e06f..bf0d477 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -79,37 +79,47 @@ BroadcastFeedback - + Sending Payment Sende Zahlung - + Payment Sent Zahlung gesendet + + + Failed + Failed + Transaction rejected by network Transaktion wurde vom Netzwerk abgelehnt - - Add a personal note - Eine persönliche Notiz hinzufügen + + Payment has been sent to: + Payment has been sent to: - - Copied TXID to clipboard - TXID in die Zwischenablage kopiert + + Copied address to clipboard + Copied address to clipboard - + Opening Website Öffne Website - + + Add a personal note + Eine persönliche Notiz hinzufügen + + + Close Schließen @@ -256,12 +266,12 @@ Payment - + Invalid PIN Ungültige PIN - + Not enough funds selected for fees Nicht genug Guthaben für Gebühren ausgewählt @@ -271,10 +281,34 @@ Nicht genügend Guthaben in der Geldbörse, um eine Zahlung zu machen! - + Transaction too large. Amount selected needs too many coins. Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. + + + Request received over insecure channel. Anyone could have altered it! + Request received over insecure channel. Anyone could have altered it! + + + + Download of payment request failed. + Download of payment request failed. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Payment request unreadable + + + + + Payment request expired + Payment request expired + QRWidget diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index 06275be..19662ae 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -79,37 +79,47 @@ BroadcastFeedback - + Sending Payment Sending Payment - + Payment Sent Payment Sent + + + Failed + Failed + Transaction rejected by network Transaction rejected by network - - Add a personal note - Add a personal note + + Payment has been sent to: + Payment has been sent to: - - Copied TXID to clipboard - Copied TXID to clipboard + + Copied address to clipboard + Copied address to clipboard - + Opening Website Opening Website - + + Add a personal note + Add a personal note + + + Close Close @@ -256,12 +266,12 @@ Payment - + Invalid PIN Invalid PIN - + Not enough funds selected for fees Not enough funds selected for fees @@ -271,10 +281,34 @@ Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. + + + Request received over insecure channel. Anyone could have altered it! + Request received over insecure channel. Anyone could have altered it! + + + + Download of payment request failed. + Download of payment request failed. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Payment request unreadable + + + + + Payment request expired + Payment request expired + QRWidget diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index e6a6850..3f75a59 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -79,37 +79,47 @@ BroadcastFeedback - + Sending Payment Enviando Pago - + Payment Sent Pago Enviado + + + Failed + Failed + Transaction rejected by network Transacción rechazada por la red - - Add a personal note - Añadir una nota personal + + Payment has been sent to: + Payment has been sent to: - - Copied TXID to clipboard - ID de transacción copiada al portapapeles + + Copied address to clipboard + Copied address to clipboard - + Opening Website Abriendo Sitio Web - + + Add a personal note + Añadir una nota personal + + + Close Cerrar @@ -256,12 +266,12 @@ Payment - + Invalid PIN PIN inválido - + Not enough funds selected for fees No se han seleccionado suficientes fondos para cubrir la comisión @@ -271,10 +281,34 @@ ¡El monedero no posee suficientes fondos para procesar el pago! - + Transaction too large. Amount selected needs too many coins. Transacción demasiado larga. La cantidad seleccionada necesita demasiadas monedas. + + + Request received over insecure channel. Anyone could have altered it! + Request received over insecure channel. Anyone could have altered it! + + + + Download of payment request failed. + Download of payment request failed. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Payment request unreadable + + + + + Payment request expired + Payment request expired + QRWidget diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index df9cd72..3c69baa 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -79,37 +79,47 @@ BroadcastFeedback - + Sending Payment Betaling wordt verzonden - + Payment Sent Betaling Verzonden + + + Failed + Mislukt + Transaction rejected by network Transactie afgewezen door het netwerk - - Add a personal note - Voeg een persoonlijke notitie toe + + Payment has been sent to: + Betaling is verzonden naar: - - Copied TXID to clipboard - Gekopieerd TXID naar klipbord + + Copied address to clipboard + Adres gekopieerd naar klembord - + Opening Website Open Website - + + Add a personal note + Voeg een persoonlijke notitie toe + + + Close Sluiten @@ -256,12 +266,12 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten @@ -271,10 +281,34 @@ Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. + + + Request received over insecure channel. Anyone could have altered it! + Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! + + + + Download of payment request failed. + Downloaden van betalingsverzoek mislukt. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Betalingsverzoek onleesbaar + + + + + Payment request expired + Betalingsverzoek verlopen + QRWidget diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index fdb5309..7497791 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -17,9 +17,9 @@ Behind: %1 weeks, %2 days counter on weeks - + W tyle: %1 tyg., %2 d. - Behind: %1 weeks, %2 days + W tyle: %1 tydzień, %2 dni W tyle: %1 tygodni, %2 dzień W tyle: %1 tyg., %2 d. @@ -27,10 +27,10 @@ Behind: %1 days - + W tyle: %1 dzień W tyle: %1 dni - Behind: %1 days + W tyle: %1 dni W tyle: %1 dnia @@ -47,8 +47,8 @@ Still %1 hours behind - - Still %1 hours behind + + Nadal %1 godzinę w tyle Nadal %1 godzin w tyle Nadal %1 godziny w tyle Nadal %1 godzin w tyle @@ -85,37 +85,47 @@ BroadcastFeedback - + Sending Payment Wysyłanie Płatności - + Payment Sent Płatność Wysłana + + + Failed + Niepowodzenie + Transaction rejected by network Transakcja odrzucona przez sieć - - Add a personal note - Dodaj osobistą notatkę + + Payment has been sent to: + Płatność została wysłana do: - - Copied TXID to clipboard - ID transakcji skopiowane do schowka + + Copied address to clipboard + Adres skopiowany do schowka - + Opening Website Otwieranie Strony - + + Add a personal note + Dodaj osobistą notatkę + + + Close Zamknij @@ -174,11 +184,11 @@ %1 hours ago timestamp - - %1 hours ago - %1 hours ago + + %1 godzinę temu + %1 godzin temu %1 godziy temu - %1 hours ago + %1 godziny temu @@ -239,11 +249,11 @@ New Transactions dialog-title - + Nowa Transakcja Nowe Transakcje Nowych transakcji - New Transactions + Nowych transakcji @@ -270,12 +280,12 @@ Payment - + Invalid PIN Nieprawidłowy PIN - + Not enough funds selected for fees Brak środków, by pokryć koszt transakcji @@ -285,10 +295,34 @@ Niewystarczająca ilość środków w portfelu, aby dokonać płatności! - + Transaction too large. Amount selected needs too many coins. Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. + + + Request received over insecure channel. Anyone could have altered it! + Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł go zmodyfikować! + + + + Download of payment request failed. + Pobieranie żądania płatności nie powiodło się. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Żądanie płatności nieczytelne + + + + + Payment request expired + Żądanie płatności wygasło + QRWidget @@ -335,33 +369,33 @@ %1 days age, like: days old - - %1 days + + %1 dzień %1 dni %1 dni - %1 days + %1 dnia %1 weeks age, like: weeks old - + %1 tydzień %1 tygodnie %1 tygodni - %1 weeks + %1 tygodnia %1 months age, like: months old - + %1 miesiąc %1 miesiące %1 miesięcy - %1 months + %1 miesiąca diff --git a/translations/floweepay-common_pt.ts b/translations/floweepay-common_pt.ts new file mode 100644 index 0000000..86c6e59 --- /dev/null +++ b/translations/floweepay-common_pt.ts @@ -0,0 +1,451 @@ + + + + + AccountInfo + + + Offline + Desconectado + + + + Wallet: Up to date + Carteira: atualizada + + + + Behind: %1 weeks, %2 days + counter on weeks + + Atrasado: %1 semanas, %2 dias + Behind: %1 weeks, %2 days + + + + + Behind: %1 days + + Atrasado: %1 dias + Behind: %1 days + + + + + Up to date + Atualizado + + + + Updating + Atualizando + + + + Still %1 hours behind + + Ainda %1 horas atrasado + Still %1 hours behind + + + + + AccountTypeLabel + + + This wallet is a single-address wallet. + Carteira com endereço único. + + + + This wallet is based on a HD seed-phrase + Carteira baseada numa senha HD + + + + This wallet is a simple multiple-address wallet. + Carteira com múltiplos endereços. + + + + AddressInfoWidget + + + self + payment to self + próprio + + + + BroadcastFeedback + + + Sending Payment + Enviando pagamento + + + + Payment Sent + Pagamento enviado + + + + Failed + Failed + + + + Transaction rejected by network + Transação rejeitada pela rede + + + + Payment has been sent to: + Payment has been sent to: + + + + Copied address to clipboard + Copied address to clipboard + + + + Opening Website + Abrindo página + + + + Add a personal note + Adicionar nota pessoal + + + + Close + Fechar + + + + CashFusionIcon + + + Coin has been fused for increased anonymity + Moeda fundida para maior anonimato + + + + FloweePay + + + Initial Wallet + Carteira inicial + + + + + Today + Hoje + + + + + Yesterday + Ontem + + + + Now + timestamp + Agora + + + + %1 minutes ago + relative time stamp + + %1 minuto(s) atrás + %1 minutes ago + + + + + ½ hour ago + timestamp + Meia hora atrás + + + + %1 hours ago + timestamp + + %1 hora(s) atrás + %1 hours ago + + + + + LabelWithClipboard + + + Copy + Copiar + + + + MenuModel + + + Explore + Explorar + + + + Settings + Configurações + + + + Security + Segurança + + + + About + Sobre + + + + Wallets + Carteiras + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bloco Bitcoin Cash minerado. Último bloco: %1 + + + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) bloco minerado: %1 + + + + Mute + Silenciar + + + + New Transactions + dialog-title + + Novas transações + New Transactions + + + + + %1 new transactions across %2 wallets found (%3) + %1Novas transações de %2 carteiras encontradas (%3) + + + + A payment of %1 has been sent + Um pagamento de %1 foi enviado + + + + %1 new transactions found (%2) + + %1 novas transações encontradas (%2) + %1 new transactions found (%2) + + + + + Payment + + + Invalid PIN + PIN inválido + + + + Not enough funds selected for fees + Saldo insuficiente para taxas + + + + Not enough funds in wallet to make payment! + Saldo insuficiente para realizar pagamento! + + + + Transaction too large. Amount selected needs too many coins. + Valor muito alto. A quantia selecionada requer moedas demais. + + + + Request received over insecure channel. Anyone could have altered it! + Request received over insecure channel. Anyone could have altered it! + + + + Download of payment request failed. + Download of payment request failed. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Payment request unreadable + + + + + Payment request expired + Payment request expired + + + + QRWidget + + + Copied to clipboard + Copiado para área de transferência + + + + Wallet + + + + Change #%1 + Alterar %1 + + + + + Main #%1 + Main #%1 + + + + WalletCoinsModel + + + Unconfirmed + Unconfirmed + + + + %1 hours + age, like: hours old + + %1 hora(s) + %1 hours + + + + + %1 days + age, like: days old + + %1 dia(s) + %1 days + + + + + %1 weeks + age, like: weeks old + + %1 semanas + %1 weeks + + + + + %1 months + age, like: months old + + %1 months + %1 months + + + + + Change #%1 + Change #%1 + + + + WalletHistoryModel + + + Today + Today + + + + Yesterday + Yesterday + + + + Earlier this week + Earlier this week + + + + This week + This week + + + + Earlier this month + Earlier this month + + + + WalletSecretsView + + + Explanation + Explanation + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Coins a / b + a) active coin-count. + b) historical coin-count. + + + + + Copy Address + Copy Address + + + + Copy Private Key + Copy Private Key + + + + Coins: %1 / %2 + Coins: %1 / %2 + + + + Signed with Schnorr signatures in the past + Signed with Schnorr signatures in the past + + + diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index cbae65b..2947b8c 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -530,140 +530,146 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. SendTransactionPane - + Confirm delete Löschen bestätigen - + Do you really want to delete this detail? Möchten Sie dieses Detail wirklich löschen? - + Add Destination Ziel hinzufügen - + Prepare Vorbereiten - + Enter your PIN Geben Sie Ihre PIN ein - + + + Warning + Warnung + + + + Payment request warnings: + Payment request warnings: + + + Transaction Details Transaktionsdetails - + Not prepared yet Noch nicht vorbereitet - + Copy transaction-ID Transaktions-ID kopieren - + Fee Gebühr - + Transaction size Transaktionsgröße - + %1 bytes %1 Bytes - + Fee per byte Gebühr pro Byte - + %1 sat/byte fee %1 Sat/Byte - + Send Senden - + Destination Ziel - + Max available The maximum balance available Max. verfügbar - + %1 to %2 summary text to pay X-euro to address M %1 zu %2 - + Enter Bitcoin Cash Address Geben Sie eine Bitcoin Cash Adresse ein - + Copy Address Adresse kopieren - + Amount Betrag - + Max Max - - Warning - Warnung - - - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + Continue Fortfahren - + Cancel Abbrechen - + Coin Selector Coin Selektor - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -672,53 +678,53 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Total Number of coins Gesamt - + Needed Benötigt - + Selected Ausgewählt - + Value Wert - + Locked coins will never be used for payments. Right-click for menu. Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin Coin entsperren - + Lock coin Coin sperren @@ -851,15 +857,15 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - Wallet already has pin to pay applied - Geldbörse hat bereits eine PIN zum Bezahlen - - - Wallet already has pin to open applied Geldbörse hat bereits eine PIN zum Öffnen an + + + Wallet already has pin to pay applied + Geldbörse hat bereits eine PIN zum Bezahlen + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. @@ -1028,7 +1034,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Copy Address Adresse kopieren @@ -1041,95 +1047,95 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. main - + Activity Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1138,7 +1144,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index cc68fad..29923f8 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -530,140 +530,146 @@ Change will come back to the imported key. SendTransactionPane - + Confirm delete Confirm delete - + Do you really want to delete this detail? Do you really want to delete this detail? - + Add Destination Add Destination - + Prepare Prepare - + Enter your PIN Enter your PIN - + + + Warning + Warning + + + + Payment request warnings: + Payment request warnings: + + + Transaction Details Transaction Details - + Not prepared yet Not prepared yet - + Copy transaction-ID Copy transaction-ID - + Fee Fee - + Transaction size Transaction size - + %1 bytes %1 bytes - + Fee per byte Fee per byte - + %1 sat/byte fee %1 sat/byte - + Send Send - + Destination Destination - + Max available The maximum balance available Max available - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Enter Bitcoin Cash Address - + Copy Address Copy Address - + Amount Amount - + Max Max - - Warning - Warning - - - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + Cancel Cancel - + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -672,53 +678,53 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin @@ -851,15 +857,15 @@ Change will come back to the imported key. - Wallet already has pin to pay applied - Wallet already has pin to pay applied - - - Wallet already has pin to open applied Wallet already has pin to open applied + + + Wallet already has pin to pay applied + Wallet already has pin to pay applied + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. @@ -1028,7 +1034,7 @@ Change will come back to the imported key. - + Copy Address Copy Address @@ -1041,95 +1047,95 @@ Change will come back to the imported key. main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + Offline Offline - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1138,7 +1144,7 @@ Change will come back to the imported key. - + Preparing... Preparing... diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index 5ccc4be..dc7b562 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -530,195 +530,201 @@ El cambio volverá a la clave importada. SendTransactionPane - + Confirm delete Confirmar eliminación - + Do you really want to delete this detail? - Do you really want to delete this detail? + ¿Realmente quieres borrar esta especificación? - + Add Destination Añadir destino - + Prepare Preparar - + Enter your PIN Introduce tu código PIN - + + + Warning + Advertencia + + + + Payment request warnings: + Payment request warnings: + + + Transaction Details Detalles de la transacción - + Not prepared yet Aún no preparado - + Copy transaction-ID Copiar ID de la transacción - + Fee Comisión - + Transaction size Tamaño de la transacción - + %1 bytes %1 bytes - + Fee per byte Comisión por byte - + %1 sat/byte fee %1 sat/byte - + Send Enviar - + Destination Destino - + Max available The maximum balance available Máximo disponible - + %1 to %2 summary text to pay X-euro to address M %1 a %2 - + Enter Bitcoin Cash Address Introduzca la dirección de Bitcoin Cash - + Copy Address Copiar dirección - + Amount Monto - + Max Máx - - Warning - Advertencia - - - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + Continue Continuar - + Cancel Cancelar - + Coin Selector Selector de monedas - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins - Selected %1 %2 in %3 coins + Seleccionadas %1 %2 en %3 monedas Selected %1 %2 in %3 coins - + Total Number of coins Total - + Needed Necesario - + Selected Seleccionado - + Value Valor - + Locked coins will never be used for payments. Right-click for menu. - Locked coins will never be used for payments. Right-click for menu. + Las monedas bloqueadas nunca se utilizarán para los pagos. Clic derecho para desplegar el menú. - + Age Edad - + Unselect All Deseleccionar Todo - + Select All Seleccionar Todo - + Unlock coin Desbloquear moneda - + Lock coin Bloquear moneda @@ -743,12 +749,12 @@ El cambio volverá a la clave importada. Show Block Notifications - Show Block Notifications + Mostrar notificaciones bloqueadas When a new block is mined, Flowee Pay shows a desktop notification - When a new block is mined, Flowee Pay shows a desktop notification + Cuando un nuevo bloque es minado, Flowee Pay muestra una notificación de escritorio @@ -763,7 +769,7 @@ El cambio volverá a la clave importada. Hides private wallets while enabled - Hides private wallets while enabled + Oculta monederos privados mientras está habilitado @@ -773,7 +779,7 @@ El cambio volverá a la clave importada. Library Version - Library Version + Versión de la biblioteca @@ -796,7 +802,7 @@ El cambio volverá a la clave importada. Pin to Pay - Pin to Pay + PIN para pagar @@ -808,18 +814,18 @@ El cambio volverá a la clave importada. Fully open, except for sending funds pin to pay - Fully open, except for sending funds + Totalmente abierto, excepto para el envío de fondos Keeps in sync pin to pay - Keeps in sync + Mantener sincronizado Pin to Open - Pin to Open + PIN para abrir @@ -831,84 +837,84 @@ El cambio volverá a la clave importada. Balance and history protected pin to open - Balance and history protected + Balance e historial protegidos Requires Pin to view, sync or pay pin to open - Requires Pin to view, sync or pay + Requiere Pin para ver, sincronizar o pagar Make "%1" wallet require a pin to pay - Make "%1" wallet require a pin to pay + Hacer "%1" monedero requiere un pin para pagar Make "%1" wallet require a pin to open - Make "%1" wallet require a pin to open + Hacer "%1" monedero requiere un pin para abrir - Wallet already has pin to pay applied - Wallet already has pin to pay applied + + Wallet already has pin to open applied + El monedero ya tiene un pin para abrir aplicado - - Wallet already has pin to open applied - Wallet already has pin to open applied + Wallet already has pin to pay applied + El monedero ya tiene pin para pagar aplicado Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. - Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Su monedero se cifrará parcialmente y los pagos serán imposibles sin una contraseña. Si no tienes una copia de seguridad de esta cartera, haz una primero. Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. - Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Tu monedero completo se cifra, para abrirla necesitará una contraseña. Si no tienes una copia de seguridad de esta cartera, haz una primero. Password - Password + Contraseña Wallet - Wallet + Monedero Encrypt - Encrypt + Encriptar Invalid password to open this wallet - Invalid password to open this wallet + Contraseña no válida para abrir este monedero Close - Close + Cerrar Repeat password - Repeat password + Repetir contraseña Please confirm the password by entering it again - Please confirm the password by entering it again + Por favor confirme su contraseña introduciéndola nuevamente Typed passwords do not match - Typed passwords do not match + Las contraseñas escritas no coinciden @@ -916,18 +922,18 @@ El cambio volverá a la clave importada. Pin to Pay - Pin to Pay + PIN para pagar Pin to Open - Pin to Open + PIN para abrir (Opened) Wallet is decrypted - (Opened) + (Abierto) @@ -935,37 +941,37 @@ El cambio volverá a la clave importada. Miner Reward - Miner Reward + Recompensa del minero Cash Fusion - Cash Fusion + Fusión de Monedas Received - Received + Recibido Moved - Moved + Movido Sent - Sent + Enviado rejected - rejected + rechazada unconfirmed - unconfirmed + sin confirmar @@ -973,50 +979,50 @@ El cambio volverá a la clave importada. Copy transaction-ID - Copy transaction-ID + Copiar ID de la transacción Status - Status + Estado rejected - rejected + rechazada unconfirmed - unconfirmed + sin confirmar %1 confirmations (mined in block %2) %1 confirmations (mined in block %2) - %1 confirmations (mined in block %2) + %1 confirmaciones (minadas en el bloque %2) Copy block height - Copy block height + Copiar altura del bloque Fees - Fees + Comisiones Size - Size + Tamaño %1 bytes - + %1 bytes %1 bytes @@ -1024,123 +1030,123 @@ El cambio volverá a la clave importada. Inputs - Inputs + Entradas - + Copy Address - Copy Address + Copiar dirección Outputs - Outputs + Salidas main - + Activity - Activity + Actividad - + Archived wallets do not check for activities. Balance may be out of date. - Archived wallets do not check for activities. Balance may be out of date. + Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado. - + Unarchive - Unarchive + Desarchivar - + This wallet needs a password to open. - This wallet needs a password to open. + Esta cartera necesita una contraseña para abrirse. - + Password: - Password: + Contraseña: - + Invalid password - Invalid password + Contraseña invalida - + Open - Open + Abrir - + Send - Send + Enviar - + Receive - Receive + Recibir - + Balance - Balance + Balance - + Main balance (money), non specified - Main + Principal - + Unconfirmed balance (money) - Unconfirmed + Sin confirmar - + Immature balance (money) - Immature + Sin madurar - + 1 BCH is: %1 - 1 BCH is: %1 + 1 BCH es: %1 - + Network status - Network status + Estado de la red - + Offline - Offline + Sin conexión - + Add Bitcoin Cash wallet - Add Bitcoin Cash wallet + Añadir monedero de Bitcoin Cash - + Archived wallets [%1] Arg is wallet count - Archived wallets [%1] + Monederos archivados [%1] Archived wallets [%1] - + Preparing... - Preparing... + Preparando... diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index fe27367..23b080b 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -530,140 +530,146 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. SendTransactionPane - + Confirm delete Verwijderen bevestigen - + Do you really want to delete this detail? Wilt u dit detail echt wissen? - + Add Destination Voeg bestemming toe - + Prepare Bereid voor - + Enter your PIN Voer uw PIN in - + + + Warning + Waarschuwing + + + + Payment request warnings: + Betalingsverzoek meldingen: + + + Transaction Details Transactiedetails - + Not prepared yet Nog niet voorbereid - + Copy transaction-ID Kopieer transactie-ID - + Fee Transactiekosten - + Transaction size Transactie grootte - + %1 bytes %1 bytes - + Fee per byte Transactiekosten per byte - + %1 sat/byte fee %1 sat/byte - + Send Verstuur - + Destination Bestemming - + Max available The maximum balance available Max. beschikbaar - + %1 to %2 summary text to pay X-euro to address M %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - + Copy Address Kopieer adres - + Amount Bedrag - + Max Max - - Warning - Waarschuwing - - - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Cancel Afbreken - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -672,53 +678,53 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt @@ -851,15 +857,15 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - Wallet already has pin to pay applied - Portomonee heeft al Pin to Pay - - - Wallet already has pin to open applied Portomonee heeft al Pin to Open + + + Wallet already has pin to pay applied + Portomonee heeft al Pin to Pay + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. @@ -1028,7 +1034,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Copy Address Kopieer adres @@ -1041,95 +1047,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1138,7 +1144,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index 5795a14..bea8c89 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -6,44 +6,44 @@ Details - Details + Szczegóły Unarchive - Unarchive + Przywróć Archive Wallet - Archive Wallet + Zarchiwizuj portfel Make Primary - Make Primary + Ustaw jako główny ★ Primary - ★ Primary + ★ Główny Protect With Pin... - Protect With Pin... + Chroń za pomocą pinu... Open Open encrypted wallet - Open + Otwórz Close Close encrypted wallet - Close + Zamknij @@ -86,12 +86,12 @@ Include balance in total - Include balance in total + Uwzględnij saldo ogółem Hide in private mode - Hide in private mode + Ukryj w trybie prywatnym @@ -470,12 +470,12 @@ Change will come back to the imported key. Encrypted Wallet - Encrypted Wallet + Zaszyfrowany Portfel Import Running... - Import Running... + Trwa Importowanie... @@ -531,140 +531,146 @@ Change will come back to the imported key. SendTransactionPane - + Confirm delete Potwierdź usunięcie - + Do you really want to delete this detail? Czy na pewno chcesz usunąć te szczegóły? - + Add Destination Dodaj odbiorcę - + Prepare Przygotuj - + Enter your PIN Wprowadź PIN - + + + Warning + Uwaga! + + + + Payment request warnings: + Ostrzeżenie o żądaniu płatności: + + + Transaction Details Szczegóły transakcji - + Not prepared yet Jeszcze nie przygotowano - + Copy transaction-ID Kopiuj ID transakcji - + Fee Opłata - + Transaction size Rozmiar transakcji - + %1 bytes %1 B - + Fee per byte Opłata za bajt - + %1 sat/byte fee %1 sat/byte - + Send Wyślij - + Destination Odbiorca - + Max available The maximum balance available Maksymalnie dostępne - + %1 to %2 summary text to pay X-euro to address M %1 do %2 - + Enter Bitcoin Cash Address Wprowadź adres Bitcoin Cash - + Copy Address - Copy Address + Skopiuj Adres - + Amount Kwota - + Max Maks. - - Warning - Uwaga! - - - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + Continue Kontynuuj - + Cancel Anuluj - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -675,53 +681,53 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę @@ -741,7 +747,7 @@ Change will come back to the imported key. Show Bitcoin Cash value on Activity page - Show Bitcoin Cash value on Activity page + Pokaż wartość Bitcoin Cash na stronie Aktywności @@ -761,12 +767,12 @@ Change will come back to the imported key. Private Mode - Private Mode + Tryb prywatny Hides private wallets while enabled - Hides private wallets while enabled + Ukrywa prywatne portfele @@ -781,7 +787,7 @@ Change will come back to the imported key. Synchronization - Synchronization + Synchronizacja @@ -854,15 +860,15 @@ Change will come back to the imported key. - Wallet already has pin to pay applied - Ten portfel ma już opcję "PIN by płacić" - - - Wallet already has pin to open applied Ten portfel ma już opcję "PIN by otworzyć" + + + Wallet already has pin to pay applied + Ten portfel ma już opcję "PIN by płacić" + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. @@ -1035,7 +1041,7 @@ Change will come back to the imported key. - + Copy Address Kopiuj adres @@ -1048,95 +1054,95 @@ Change will come back to the imported key. main - + Activity Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1147,7 +1153,7 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… diff --git a/translations/floweepay-desktop_pt.ts b/translations/floweepay-desktop_pt.ts new file mode 100644 index 0000000..c824439 --- /dev/null +++ b/translations/floweepay-desktop_pt.ts @@ -0,0 +1,1152 @@ + + + + + AccountConfigMenu + + + Details + Detalhes + + + + Unarchive + Desarquivar + + + + Archive Wallet + Arquivar Carteira + + + + Make Primary + Tornar principal + + + + ★ Primary + ★ Principal + + + + Protect With Pin... + Proteger com Pin... + + + + Open + Open encrypted wallet + Abrir + + + + Close + Close encrypted wallet + Fechar + + + + AccountDetails + + + Wallet Details + Detalhes da carteira + + + + Name + Nome + + + + Sync Status + Status da sincronização + + + + Encryption + Criptografia + + + + Password + Senha + + + + Open + Abrir + + + + Invalid PIN + PIN inválido + + + + Include balance in total + Incluir saldo no total + + + + Hide in private mode + Esconder no modo privado + + + + Address List + Lista de endereços + + + + Change Addresses + Alterar endereço + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Altera entre endereços para receber pagamentos e endereços que a carteira usa para mandar troco de volta para você. + + + + Used Addresses + Endereços utilizados + + + + Switches between unused and used Bitcoin addresses + Alterna entre endereços usados e não usados + + + + Backup details + Detalhes do backup + + + + Seed-phrase + Frase secreta + + + + Derivation + Derivação + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Por favor, anote as 12 palavras no papel, na ordem correta, com o caminho de derivação. Esta senha permitirá recuperar a sua carteira caso precise. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Importante</b>: Nunca compartilhe sua frase secreta com outros! + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Esta carteira está protegida por senha (pin-to-pay). Para ver os detalhes de backup, você precisa digitar a senha. + + + + AccountListItem + + + Last Transaction + Última transação + + + + NetView + + + Peers (%1) + + (%1) Pares + Peers (%1) + + + + + Address + network address (IP) + Endereço + + + + Start-height: %1 + %1 Bloco inicial + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + initializing connection + + + + Verifying peer + Verifying peer + + + + Assigning... + Assigning... + + + + Peer for wallet: %1 + Peer for wallet: %1 + + + + Peer for initial wallet + Peer for initial wallet + + + + Close + Close + + + + NewAccountCreateBasicAccount + + + This creates a new empty wallet with simple multi-address capability + This creates a new empty wallet with simple multi-address capability + + + + Name + Name + + + + Go + Go + + + + Advanced Options + Advanced Options + + + + Force Single Address + Force Single Address + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + + + + NewAccountCreateHDAccount + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + + + + Name + Name + + + + Go + Go + + + + Advanced Options + Advanced Options + + + + Derivation + Derivation + + + + NewAccountImportAccount + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + + + + Secret + The seed-phrase or private key + Secret + + + + Example: %1 + placeholder text + Example: %1 + + + + Name + Name + + + + Private key + description of type + Private key + + + + BIP 39 seed-phrase + description of type + BIP 39 seed-phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unrecognized word + + + + Import wallet + Import wallet + + + + Advanced Options + Advanced Options + + + + Force Single Address + Force Single Address + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + + + + Start Height + Start Height + + + + Derivation + Derivation + + + + Alternate phrase + Alternate phrase + + + + NewAccountPane + + + New Bitcoin Cash Wallet + New Bitcoin Cash Wallet + + + + Basic + Basic + + + + Private keys based + Property of a wallet + Private keys based + + + + Difficult to backup + Context: wallet type + Difficult to backup + + + + Great for brief usage + Context: wallet type + Great for brief usage + + + + HD wallet + HD wallet + + + + Seed-phrase based + Context: wallet type + Seed-phrase based + + + + Easy to backup + Context: wallet type + Easy to backup + + + + Most compatible + The most compatible wallet type + Most compatible + + + + Import + Import + + + + Imports seed-phrase + Imports seed-phrase + + + + Imports private key + Imports private key + + + + Basic wallet with private keys + Basic wallet with private keys + + + + Seed-phrase wallet + Seed-phrase wallet + + + + Import of an existing wallet + Import of an existing wallet + + + + PaymentTweakingPanel + + + Add Detail + Add Detail + + + + Coin Selector + Coin Selector + + + + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + + + + ReceiveTransactionPane + + + Share your QR code or copy address to receive + Share your QR code or copy address to receive + + + + Encrypted Wallet + Encrypted Wallet + + + + Import Running... + Import Running... + + + + Checking + Checking + + + + Transaction high risk + Transaction high risk + + + + Payment Seen + Payment Seen + + + + Payment Accepted + Payment Accepted + + + + Payment Settled + Payment Settled + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + + Description + Description + + + + Amount + Amount + + + + Clear + Clear + + + + Done + Done + + + + SendTransactionPane + + + Confirm delete + Confirm delete + + + + Do you really want to delete this detail? + Do you really want to delete this detail? + + + + Add Destination + Add Destination + + + + Prepare + Prepare + + + + Enter your PIN + Enter your PIN + + + + + Warning + Warning + + + + Payment request warnings: + Payment request warnings: + + + + Transaction Details + Transaction Details + + + + Not prepared yet + Not prepared yet + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Fee + + + + Transaction size + Transaction size + + + + %1 bytes + %1 bytes + + + + Fee per byte + Fee per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Send + Send + + + + Destination + Destination + + + + Max available + The maximum balance available + Max available + + + + %1 to %2 + summary text to pay X-euro to address M + %1 to %2 + + + + Enter Bitcoin Cash Address + Enter Bitcoin Cash Address + + + + Copy Address + Copy Address + + + + Amount + Amount + + + + Max + Max + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + Continue + Continue + + + + Cancel + Cancel + + + + Coin Selector + Coin Selector + + + + Selected %1 %2 in %3 coins + selected 2 BCH in 5 coins + + Selected %1 %2 in %3 coins + Selected %1 %2 in %3 coins + + + + + Total + Number of coins + Total + + + + Needed + Needed + + + + Selected + Selected + + + + Value + Value + + + + Locked coins will never be used for payments. Right-click for menu. + Locked coins will never be used for payments. Right-click for menu. + + + + Age + Age + + + + Unselect All + Unselect All + + + + Select All + Select All + + + + Unlock coin + Unlock coin + + + + Lock coin + Lock coin + + + + SettingsPane + + + Settings + Settings + + + + Unit + Unit + + + + Show Bitcoin Cash value on Activity page + Show Bitcoin Cash value on Activity page + + + + Show Block Notifications + Show Block Notifications + + + + When a new block is mined, Flowee Pay shows a desktop notification + When a new block is mined, Flowee Pay shows a desktop notification + + + + Night Mode + Night Mode + + + + Private Mode + Private Mode + + + + Hides private wallets while enabled + Hides private wallets while enabled + + + + Version + Version + + + + Library Version + Library Version + + + + Synchronization + Synchronization + + + + Network Status + Network Status + + + + WalletEncryption + + + Protect your wallet with a password + Protect your wallet with a password + + + + Pin to Pay + Pin to Pay + + + + Protect your funds + pin to pay + Protect your funds + + + + Fully open, except for sending funds + pin to pay + Fully open, except for sending funds + + + + Keeps in sync + pin to pay + Keeps in sync + + + + Pin to Open + Pin to Open + + + + Protect your entire wallet + pin to open + Protect your entire wallet + + + + Balance and history protected + pin to open + Balance and history protected + + + + Requires Pin to view, sync or pay + pin to open + Requires Pin to view, sync or pay + + + + Make "%1" wallet require a pin to pay + Make "%1" wallet require a pin to pay + + + + Make "%1" wallet require a pin to open + Make "%1" wallet require a pin to open + + + + + Wallet already has pin to open applied + Wallet already has pin to open applied + + + + Wallet already has pin to pay applied + Wallet already has pin to pay applied + + + + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + + + + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + + + + Password + Password + + + + Wallet + Wallet + + + + Encrypt + Encrypt + + + + Invalid password to open this wallet + Invalid password to open this wallet + + + + Close + Close + + + + Repeat password + Repeat password + + + + Please confirm the password by entering it again + Please confirm the password by entering it again + + + + Typed passwords do not match + Typed passwords do not match + + + + WalletEncryptionStatus + + + Pin to Pay + Pin to Pay + + + + Pin to Open + Pin to Open + + + + (Opened) + Wallet is decrypted + (Opened) + + + + WalletTransaction + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + WalletTransactionDetails + + + Copy transaction-ID + Copy transaction-ID + + + + Status + Status + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + %1 confirmations (mined in block %2) + + %1 confirmations (mined in block %2) + %1 confirmations (mined in block %2) + + + + + Copy block height + Copy block height + + + + Fees + Fees + + + + Size + Size + + + + %1 bytes + + %1 bytes + %1 bytes + + + + + Inputs + Inputs + + + + + Copy Address + Copy Address + + + + Outputs + Outputs + + + + main + + + Activity + Activity + + + + Archived wallets do not check for activities. Balance may be out of date. + Archived wallets do not check for activities. Balance may be out of date. + + + + Unarchive + Unarchive + + + + This wallet needs a password to open. + This wallet needs a password to open. + + + + Password: + Password: + + + + Invalid password + Invalid password + + + + Open + Open + + + + Send + Send + + + + Receive + Receive + + + + Balance + Balance + + + + Main + balance (money), non specified + Main + + + + Unconfirmed + balance (money) + Unconfirmed + + + + Immature + balance (money) + Immature + + + + 1 BCH is: %1 + 1 BCH is: %1 + + + + Network status + Network status + + + + Offline + Offline + + + + Add Bitcoin Cash wallet + Add Bitcoin Cash wallet + + + + Archived wallets [%1] + Arg is wallet count + + Archived wallets [%1] + Archived wallets [%1] + + + + + Preparing... + Preparing... + + + diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index f5d3f9b..514cbd4 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -457,12 +457,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. InstaPayConfigPage - + Instant Pay Sofortzahlung - + Requests for payment can be approved automatically using Instant Pay. Zahlungsanfragen können automatisch mit Sofortzahlung bewilligt werden. @@ -472,12 +472,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Geschützte Geldbörsen können nicht für Sofortzahlung verwendet werden, da sie vor der Nutzung eine PIN benötigen - + Enable Instant Pay for this wallet Sofortzahlung für diese Geldbörse aktivieren - + Maximum Amount Höchstbetrag @@ -538,7 +538,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. MenuOverlay - + Add Wallet Geldbörse hinzufügen @@ -697,37 +697,37 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBetrag bearbeiten - + Invalid QR code Ungültiger QR Code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. - + details details - + Scanned text: <pre>%1</pre> Gescannter Text: <pre>%1</pre> - + Payment description Zahlungsbeschreibung - + Destination Address Zieladresse - + Unlock Wallet Geldbörse entsperren @@ -770,15 +770,25 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss QRScannerOverlay - + Paste Einfügen - + Failed Fehlgeschlagen + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + ReceiveTab diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 84b0fe9..2a8517a 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -457,12 +457,12 @@ Change will come back to the imported key. InstaPayConfigPage - + Instant Pay Instant Pay - + Requests for payment can be approved automatically using Instant Pay. Requests for payment can be approved automatically using Instant Pay. @@ -472,12 +472,12 @@ Change will come back to the imported key. Protected wallets can not be used for InstaPay because they require a PIN before usage - + Enable Instant Pay for this wallet Enable Instant Pay for this wallet - + Maximum Amount Maximum Amount @@ -538,7 +538,7 @@ Change will come back to the imported key. MenuOverlay - + Add Wallet Add Wallet @@ -697,37 +697,37 @@ This ensures only one private key will need to be backed up Edit Amount - + Invalid QR code Invalid QR code - + I don't understand the scanned code. I'm sorry, I can't start a payment. I don't understand the scanned code. I'm sorry, I can't start a payment. - + details details - + Scanned text: <pre>%1</pre> Scanned text: <pre>%1</pre> - + Payment description Payment description - + Destination Address Destination Address - + Unlock Wallet Unlock Wallet @@ -770,15 +770,25 @@ This ensures only one private key will need to be backed up QRScannerOverlay - + Paste Paste - + Failed Failed + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + ReceiveTab diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index 34b7410..a2c068e 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -457,12 +457,12 @@ El cambio volverá a la clave importada. InstaPayConfigPage - + Instant Pay Pago instantáneo - + Requests for payment can be approved automatically using Instant Pay. Las solicitudes de pago pueden ser aprobadas automáticamente usando Pago Instantáneo. @@ -472,12 +472,12 @@ El cambio volverá a la clave importada. Los monederos protegidos no se pueden utilizar para InstaPagos porque requieren un PIN antes de usarse - + Enable Instant Pay for this wallet Habilitar Pago Instantáneo para este monedero - + Maximum Amount Monto máximo @@ -538,7 +538,7 @@ El cambio volverá a la clave importada. MenuOverlay - + Add Wallet Añadir Monedero @@ -697,37 +697,37 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Editar cantidad - + Invalid QR code Código QR inválido - + I don't understand the scanned code. I'm sorry, I can't start a payment. No entiendo el código escaneado. Lo siento, no puedo iniciar un pago. - + details detalles - + Scanned text: <pre>%1</pre> Texto escaneado: <pre>%1</pre> - + Payment description Descripción del pago - + Destination Address Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -770,15 +770,25 @@ Esto asegura que solo una clave privada tendrá que ser respaldada QRScannerOverlay - + Paste Pegar - + Failed Fallido + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + ReceiveTab diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 1b92487..e389312 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -457,12 +457,12 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. InstaPayConfigPage - + Instant Pay Direct Betalen - + Requests for payment can be approved automatically using Instant Pay. Betalingsverzoeken worden automatisch goedgekeurd met behulp van Direct Betalen. @@ -472,12 +472,12 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Beveiligde portemonnees kunnen niet worden gebruikt voor Direct Betalen ze een PIN vereisen voor gebruik - + Enable Instant Pay for this wallet Actief maken voor portemonnee - + Maximum Amount Maximum Bedrag @@ -538,7 +538,7 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MenuOverlay - + Add Wallet Portemonnee toevoegen @@ -697,37 +697,37 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -770,15 +770,25 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt QRScannerOverlay - + Paste Plak - + Failed Mislukt! + + + Instant Pay limit is %1 + Directbetalen limiet is %1 + + + + Selected wallet: '%1' + Geselecteerde portemonnee: '%1' + ReceiveTab diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 0cf67aa..fe263cd 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -456,12 +456,12 @@ Change will come back to the imported key. InstaPayConfigPage - + Instant Pay Szybkie płatności - + Requests for payment can be approved automatically using Instant Pay. Przy Szybkich Płatnościach żądania będą automatycznie zatwierdzane. @@ -471,12 +471,12 @@ Change will come back to the imported key. Zabezpieczone portfele nie mogą być używane do Szybkich Płatności, ponieważ wymagają PIN-u przed użyciem - + Enable Instant Pay for this wallet Włącz Szybkie Płatności w tym portfelu - + Maximum Amount Maksymalna Kwota @@ -537,7 +537,7 @@ Change will come back to the imported key. MenuOverlay - + Add Wallet Dodaj Portfel @@ -696,37 +696,37 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoEdytuj kwotę - + Invalid QR code Nieprawidłowy kod QR - + I don't understand the scanned code. I'm sorry, I can't start a payment. Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. - + details szczegóły - + Scanned text: <pre>%1</pre> Zeskanowany tekst: <pre>%1</pre> - + Payment description Opis płatności - + Destination Address Adres docelowy - + Unlock Wallet Odblokuj portfel @@ -769,15 +769,25 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego QRScannerOverlay - + Paste Wklej - + Failed Niepowodzenie + + + Instant Pay limit is %1 + Limit szybkiej płatności wynosi %1 + + + + Selected wallet: '%1' + Wybrany portfel: '%1' + ReceiveTab @@ -838,32 +848,32 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Transaction high risk - Transaction high risk + Transakcja o wysokim poziomie ryzyka Partially Paid - Partially Paid + Częściowo opłacona Payment Accepted - Payment Accepted + Płatność zaakceptowana Payment Settled - Payment Settled + Płatność rozliczona Instant payment failed. Wait for confirmation. (double spent proof received) - Instant payment failed. Wait for confirmation. (double spent proof received) + Płatność błyskawiczna nie powiodła się. Poczekaj na potwierdzenie. (otrzymano dowód podwójnej płatności) Continue - Continue + Kontynuuj @@ -871,27 +881,27 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Select Wallet - Select Wallet + Wybierz portfel Pick which wallet will be selected on starting Flowee Pay - Pick which wallet will be selected on starting Flowee Pay + Wybierz portfel, który zostanie pokazany przy uruchomieniu FloweePay No InstaPay limit set - No InstaPay limit set + Nie ustawiono limitu Szybkich Płatności InstaPay till %1 - InstaPay till %1 + Szybkie płatności do %1 InstaPay is turned off - InstaPay is turned off + Szybkie Płatności wyłączone @@ -899,12 +909,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Send - Send + Wyślij Start Payment - Start Payment + Rozpocznij płatność @@ -912,7 +922,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego SLIDE TO SEND - SLIDE TO SEND + PRZECIĄGNIJ ŻEBY WYSŁAĆ @@ -920,32 +930,32 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Welcome! - Welcome! + Witaj! Continue - Continue + Kontynuuj Moving the world towards a Bitcoin Cash economy - Moving the world towards a Bitcoin Cash economy + Przesuwamy świat w kierunku ekonomii Bitcoin Cash Scan me to send funds to your HD wallet - Scan me to send funds to your HD wallet + Zeskanuj mnie, aby wysłać środki do swojego portfela HD OR - OR + LUB Add a different wallet - Add a different wallet + Dodaj inny portfel @@ -953,12 +963,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Transaction Details - Transaction Details + Szczegóły transakcji Transaction Hash - Transaction Hash + Hasz Transakcji @@ -978,11 +988,11 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego %1 blocks ago - + + %1 blok temu + %1 bloki temu %1 bloków temu - %1 blocks ago - %1 blocks ago - %1 blocks ago + %1 bloki temu @@ -1073,11 +1083,11 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego %1 blocks ago Confirmations - - %1 blocks ago - %1 blocks ago + + %1 blok temu + %1 bloki temu %1 bloków temu - %1 blocks ago + %1 bloki temu diff --git a/translations/floweepay-mobile_pt.ts b/translations/floweepay-mobile_pt.ts new file mode 100644 index 0000000..8e8da26 --- /dev/null +++ b/translations/floweepay-mobile_pt.ts @@ -0,0 +1,1155 @@ + + + + + About + + + About + About + + + + Help translate this app + Help translate this app + + + + License + License + + + + + Credits + Credits + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander and contributors + + + + Project Home + Project Home + + + + With git repository and issues tracker + With git repository and issues tracker + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Home + + + + Pay + Pay + + + + Receive + Receive + + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + Sending + Sending + + + + Seen + Seen + + + + Rejected + Rejected + + + + AccountPageListItem + + + Name + Name + + + + Archived wallets do not check for activities. Balance may be out of date + Archived wallets do not check for activities. Balance may be out of date + + + + Backup information + Backup information + + + + Backup Details + Backup Details + + + + Wallet seed-phrase + Wallet seed-phrase + + + + Starting Height + height refers to block-height + Starting Height + + + + Derivation Path + Derivation Path + + + + xpub + xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Important</b>: Never share your seed-phrase with others! + + + + Wallet keys + Wallet keys + + + + Show Index + toggle to show numbers + Show Index + + + + Change Addresses + Change Addresses + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + + + + Used Addresses + Used Addresses + + + + Switches between still in use addresses and formerly used, new empty, addresses + Switches between still in use addresses and formerly used, new empty, addresses + + + + Addresses and keys + Addresses and keys + + + + Sync Status + Sync Status + + + + Hide balance in overviews + Hide balance in overviews + + + + Hide in private mode + Hide in private mode + + + + Unarchive Wallet + Unarchive Wallet + + + + Archive Wallet + Archive Wallet + + + + AccountSelectorPopup + + + Your Wallets + Your Wallets + + + + last active + last active + + + + Needs PIN to open + Needs PIN to open + + + + Balance Total + Balance Total + + + + AccountSyncState + + + Status: Offline + Status: Offline + + + + AccountsList + + + Wallet + Wallet + + + + Wallets + Wallets + + + + Add Wallet + Add Wallet + + + + Default Wallet + Default Wallet + + + + %1 is used on startup + %1 is used on startup + + + + Exit Private Mode + Exit Private Mode + + + + Enter Private Mode + Enter Private Mode + + + + Reveals wallets marked private + Reveals wallets marked private + + + + Hides wallets marked private + Hides wallets marked private + + + + CurrencySelector + + + Select Currency + Select Currency + + + + ExploreModules + + + Explore + Explore + + + + ON + Enabled. SHORT TEXT! + ON + + + + GuiSettings + + + Display Settings + Display Settings + + + + Font sizing + Font sizing + + + + Unit + Unit + + + + Change Currency (%1) + Change Currency (%1) + + + + Main View + Main View + + + + Show Bitcoin Cash value + Show Bitcoin Cash value + + + + ImportWalletPage + + + Import Wallet + Import Wallet + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + + + + Secret + The seed-phrase or private key + Secret + + + + Private key + description of type + Private key + + + + BIP 39 seed-phrase + description of type + BIP 39 seed-phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Unrecognized word + + + + Name + Name + + + + Force Single Address + Force Single Address + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + + + + Oldest Transaction + Oldest Transaction + + + + Derivation + Derivation + + + + Alternate phrase + Alternate phrase + + + + Create + Create + + + + InstaPayConfigButton + + + Enable Instant Pay + Enable Instant Pay + + + + Configure Instant Pay + Configure Instant Pay + + + + Fast payments for low amounts + Fast payments for low amounts + + + + Not configured + Not configured + + + + Limit set to: %1 + Limit set to: %1 + + + + InstaPayConfigPage + + + Instant Pay + Instant Pay + + + + Requests for payment can be approved automatically using Instant Pay. + Requests for payment can be approved automatically using Instant Pay. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Protected wallets can not be used for InstaPay because they require a PIN before usage + + + + Enable Instant Pay for this wallet + Enable Instant Pay for this wallet + + + + Maximum Amount + Maximum Amount + + + + LockApplication + + + Security + Security + + + + PIN on startup + PIN on startup + + + + Enter current PIN + Enter current PIN + + + + Enter new PIN + Enter new PIN + + + + Repeat PIN + Repeat PIN + + + + Remove PIN + Remove PIN + + + + Set PIN + Set PIN + + + + PIN has been removed + PIN has been removed + + + + PIN has been set + PIN has been set + + + + Ok + Ok + + + + MenuOverlay + + + Add Wallet + Add Wallet + + + + NewAccount + + + New Bitcoin Cash Wallet + New Bitcoin Cash Wallet + + + + Create a New Wallet + Create a New Wallet + + + + HD wallet + HD wallet + + + + Seed-phrase based + Context: wallet type + Seed-phrase based + + + + Easy to backup + Context: wallet type + Easy to backup + + + + Most compatible + The most compatible wallet type + Most compatible + + + + Basic + Basic + + + + Private keys based + Property of a wallet + Private keys based + + + + Difficult to backup + Context: wallet type + Difficult to backup + + + + Great for brief usage + Context: wallet type + Great for brief usage + + + + Import Existing Wallet + Import Existing Wallet + + + + Import + Import + + + + Imports seed-phrase + Imports seed-phrase + + + + Imports private key + Imports private key + + + + New Wallet + New Wallet + + + + This creates a new empty wallet with simple multi-address capability + This creates a new empty wallet with simple multi-address capability + + + + + Name + Name + + + + Force Single Address + Force Single Address + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + + + + + Create + Create + + + + New HD-Wallet + New HD-Wallet + + + + This creates a new wallet that can be backed up with a seed-phrase + This creates a new wallet that can be backed up with a seed-phrase + + + + Derivation + Derivation + + + + PayWithQR + + + Approve Payment + Approve Payment + + + + Send All + all money in wallet + Send All + + + + Show Address + to show a bitcoincash address + Show Address + + + + Edit Amount + Edit amount of money to send + Edit Amount + + + + Invalid QR code + Invalid QR code + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + I don't understand the scanned code. I'm sorry, I can't start a payment. + + + + details + details + + + + Scanned text: <pre>%1</pre> + Scanned text: <pre>%1</pre> + + + + Payment description + Payment description + + + + Destination Address + Destination Address + + + + Unlock Wallet + Unlock Wallet + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + PriceInputWidget + + + All Currencies + All Currencies + + + + QRScannerOverlay + + + Paste + Paste + + + + Failed + Failed + + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + + + + ReceiveTab + + + Receive + Receive + + + + Share this QR to receive + Share this QR to receive + + + + Encrypted Wallet + Encrypted Wallet + + + + Import Running... + Import Running... + + + + + Description + Description + + + + Amount + requested amount of coin + Amount + + + + Address + Bitcoin Cash address + Address + + + + Clear + Clear + + + + + Payment Seen + Payment Seen + + + + Checking... + Checking... + + + + Transaction high risk + Transaction high risk + + + + Partially Paid + Partially Paid + + + + Payment Accepted + Payment Accepted + + + + Payment Settled + Payment Settled + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Instant payment failed. Wait for confirmation. (double spent proof received) + + + + Continue + Continue + + + + SelectDefaultAccountPage + + + Select Wallet + Select Wallet + + + + Pick which wallet will be selected on starting Flowee Pay + Pick which wallet will be selected on starting Flowee Pay + + + + No InstaPay limit set + No InstaPay limit set + + + + InstaPay till %1 + InstaPay till %1 + + + + InstaPay is turned off + InstaPay is turned off + + + + SendTransactionsTab + + + Send + Send + + + + Start Payment + Start Payment + + + + SlideToApprove + + + SLIDE TO SEND + SLIDE TO SEND + + + + StartupScreen + + + Welcome! + Welcome! + + + + Continue + Continue + + + + Moving the world towards a Bitcoin Cash economy + Moving the world towards a Bitcoin Cash economy + + + + Scan me to send funds to your HD wallet + Scan me to send funds to your HD wallet + + + + OR + OR + + + + Add a different wallet + Add a different wallet + + + + TransactionDetails + + + Transaction Details + Transaction Details + + + + Transaction Hash + Transaction Hash + + + + Rejected + Rejected + + + + Unconfirmed + Unconfirmed + + + + Mined at + Mined at + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + + + + + Transaction comment + Transaction comment + + + + Size: %1 bytes + Size: %1 bytes + + + + Coinbase + Coinbase + + + + Is a CashFusion transaction. + Is a CashFusion transaction. + + + + Fees paid + Fees paid + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Fused from my addresses + + + + Sent from my addresses + Sent from my addresses + + + + Sent from addresses + Sent from addresses + + + + + Copy Address + Copy Address + + + + Fused into my addresses + Fused into my addresses + + + + Received at addresses + Received at addresses + + + + Received at my addresses + Received at my addresses + + + + TxInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 blocks ago + %1 blocks ago + + + + + Miner Reward + Miner Reward + + + + Cash Fusion + Cash Fusion + + + + Received + Received + + + + Payment to self + Payment to self + + + + Sent + Sent + + + + Holds a token + Holds a token + + + + Sent to + Sent to + + + + Value now + Value now + + + + Value then + Value then + + + + Transaction Details + Transaction Details + + + + UnlockWidget + + + Enter your wallet passcode + Enter your wallet passcode + + + + Open + open wallet with PIN + Open + + + diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index ab89c2c..2a6f545 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -5,20 +5,24 @@ floweepay-common_pl.qm floweepay-common_de.qm floweepay-common_es.qm + floweepay-common_pt.qm floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm floweepay-mobile_de.qm floweepay-mobile_es.qm + floweepay-mobile_pt.qm module-build-transaction_en.qm module-build-transaction_nl.qm module-build-transaction_pl.qm module-build-transaction_de.qm module-build-transaction_es.qm + module-build-transaction_pt.qm module-peers-view_en.qm module-peers-view_nl.qm module-peers-view_pl.qm module-peers-view_de.qm module-peers-view_es.qm + module-peers-view_pt.qm diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 4ede30d..e97aa66 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -6,17 +6,17 @@ Create Transactions - Create Transactions + Utwórz Transakcje This module allows building more powerful transactions in one simple user interface. - This module allows building more powerful transactions in one simple user interface. + Ten moduł umożliwia tworzenie potężniejszych transakcji w jednym prostym interfejsie użytkownika. Build Transaction - Build Transaction + Utwórz transakcję @@ -24,146 +24,146 @@ Build Transaction - Build Transaction + Utwórz transakcję Building Error error during build - Building Error + Błąd Przy Tworzeniu Add Payment Detail page title - Add Payment Detail + Dodaj szczegóły płatności Add Destination - Add Destination + Dodaj Odbiorcę an address to send money to - an address to send money to + adres do wysłania pieniędzy Confirm Sending confirm we want to send the transaction - Confirm Sending + Potwierdź Wysyłanie TXID - TXID + TXID Copy transaction-ID - Copy transaction-ID + Kopiuj ID transakcji Fee - Fee + Opłata Transaction size - Transaction size + Rozmiar transakcji %1 bytes - %1 bytes + %1 bajtów Fee per byte - Fee per byte + Opłata za bajt %1 sat/byte fee - %1 sat/byte + %1 sat/bajt Destination - Destination + Odbiorca unset indication of empty - unset + dezaktywuj invalid address is not correct - invalid + Nieprawidłowy Copy Address - Copy Address + Skopiuj Adres Edit Destination - Edit Destination + Zmień Odbiorcę Send All all money in wallet - Send All + Wyślij Wszystko Bitcoin Cash Address - Bitcoin Cash Address + Adres Bitcoin Cash Warning - Warning + Uwaga This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? I am certain - I am certain + Na Pewno Drag to Edit - Drag to Edit + Przeciągnij, aby edytować Drag to Delete - Drag to Delete + Przeciągnij, aby usunąć Prepare Payment... - Prepare Payment... + Przygotuj płatność... Unlock Wallet - Unlock Wallet + Odblokuj portfel diff --git a/translations/module-build-transaction_pt.ts b/translations/module-build-transaction_pt.ts new file mode 100644 index 0000000..dc0ad1e --- /dev/null +++ b/translations/module-build-transaction_pt.ts @@ -0,0 +1,169 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Create Transactions + + + + This module allows building more powerful transactions in one simple user interface. + This module allows building more powerful transactions in one simple user interface. + + + + Build Transaction + Build Transaction + + + + PayToOthers + + + Build Transaction + Build Transaction + + + + Building Error + error during build + Building Error + + + + Add Payment Detail + page title + Add Payment Detail + + + + + Add Destination + Add Destination + + + + an address to send money to + an address to send money to + + + + Confirm Sending + confirm we want to send the transaction + Confirm Sending + + + + TXID + TXID + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Fee + + + + Transaction size + Transaction size + + + + %1 bytes + %1 bytes + + + + Fee per byte + Fee per byte + + + + %1 sat/byte + fee + %1 sat/byte + + + + Destination + Destination + + + + unset + indication of empty + unset + + + + invalid + address is not correct + invalid + + + + + Copy Address + Copy Address + + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Send All + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Warning + Warning + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + I am certain + I am certain + + + + Drag to Edit + Drag to Edit + + + + Drag to Delete + Drag to Delete + + + + Prepare Payment... + Prepare Payment... + + + + Unlock Wallet + Unlock Wallet + + + diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts index 326ecb8..9f58bfc 100644 --- a/translations/module-peers-view_pl.ts +++ b/translations/module-peers-view_pl.ts @@ -6,7 +6,7 @@ Peers - Peers + Parowie @@ -50,12 +50,12 @@ Peers View - Peers View + Zobacz Parów This module provides a view of network servers we connect to often called 'peers'. - This module provides a view of network servers we connect to often called 'peers'. + Ten moduł zapewnia widok serwerów sieciowych, z którymi łączymy się nazywanych 'parami'. diff --git a/translations/module-peers-view_pt.ts b/translations/module-peers-view_pt.ts new file mode 100644 index 0000000..d8b41fe --- /dev/null +++ b/translations/module-peers-view_pt.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Address + + + + Start-height: %1 + Start-height: %1 + + + + ban-score: %1 + ban-score: %1 + + + + initializing connection + initializing connection + + + + Verifying peer + Verifying peer + + + + Peer for wallet: %1 + Peer for wallet: %1 + + + + Peer for wallet + Peer for wallet + + + + PeersViewModuleInfo + + + Peers View + Peers View + + + + This module provides a view of network servers we connect to often called 'peers'. + This module provides a view of network servers we connect to often called 'peers'. + + + + Network Details + Network Details + + + -- 2.54.0 From 692b29404fa901c984e983554122db80597ee160 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Oct 2023 22:50:41 +0100 Subject: [PATCH 0707/1428] add credits --- guis/mobile/About.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index dddb9c5..f66cc9d 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -75,11 +75,13 @@ Deutc
Tom Zander
Polski
Yantri & Karol Trzeszczkowski
+
Portuguese
+
bitcoincashbrazil
## Code Contributors -You? +Calin Culianu ## Art Contributors -- 2.54.0 From a7fd8a4d86c44cb68d21c35dcea065c18c48dc5c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Oct 2023 15:25:31 +0100 Subject: [PATCH 0708/1428] UX: hide help text when not appropriate. --- guis/desktop/SendTransactionPane.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 95e4039..a492622 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -331,7 +331,7 @@ Item { property var addressType: Pay.identifyString(text); Layout.fillWidth: true Layout.columnSpan: 3 - placeholderText: qsTr("Enter Bitcoin Cash Address") + placeholderText: enabled ? qsTr("Enter Bitcoin Cash Address") : "" text: destinationPane.paymentDetail.address onTextChanged: { destinationPane.paymentDetail.address = text -- 2.54.0 From c962b6db1818f689d8aca2aefb9a08c8cd986741 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Oct 2023 15:40:45 +0100 Subject: [PATCH 0709/1428] Improve bip70 support This makes the system work for non-standard addresses and different number of outputs (than one). --- src/PaymentProtocol.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 6c6cdbd..6cf850d 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -287,32 +287,26 @@ void PaymentProtocolBip70::fetchedRequest() } m_payment->setUserComment(m_memo); + assert(m_payment->paymentDetails().size() == 1); + auto *firstOut = dynamic_cast(m_payment->paymentDetails().first()); + assert(firstOut); + // lets add all the outputs for (const auto &out : m_outputs) { auto simple = CashAddress::extractAddressFromScript(out.script); + PaymentDetailOutput *addressDetail = firstOut; + firstOut = nullptr; + if (!addressDetail) // first was already used, create next + addressDetail = m_payment->addExtraOutput()->toOutput(); if (simple.hash.empty()) { logInfo(10003) << "Output is not an address. Using bytearray instead"; - auto *addressDetail = m_payment->addExtraOutput()->toOutput(); addressDetail->setOutputScript(out.script); - addressDetail->setFiatFollows(true); - addressDetail->setPaymentAmount(out.amount); - addressDetail->setEditable(false); } else { auto address = CashAddress::encodeCashAddr(chainPrefix(), simple); logDebug(10003) << "Payment to" << address; - if (m_outputs.size() == 1) { - // we place the data directly on the payment object because that - // is the most compatible for consumers of its properties. - m_payment->setTargetAddress(QString::fromLatin1(address)); - m_payment->setPaymentAmount(out.amount); - } - else { - auto *addressDetail = m_payment->addExtraOutput()->toOutput(); - addressDetail->setAddress(QString::fromLatin1(address)); - addressDetail->setFiatFollows(true); - addressDetail->setPaymentAmount(out.amount); - addressDetail->setEditable(false); - } + addressDetail->setAddress(QString::fromLatin1(address)); } + addressDetail->setPaymentAmount(out.amount); + addressDetail->setEditable(false); } // then we have to wait for the user to Ok it and only after broadcast started, -- 2.54.0 From 7729854fc0575ad6739003f467ea43dec3779060 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Oct 2023 22:08:21 +0100 Subject: [PATCH 0710/1428] Update various versions for Android build-env Boost -> 1.83 Qt -> 6.5.3 OpenSSL -> 3.1.3 ZXIng -> 2.1.0 --- android/build-pay.sh | 2 +- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- android/docker/scripts/buildBoost.sh | 5 +++-- android/docker/scripts/buildOpenSsl.sh | 2 +- android/docker/scripts/buildQt.sh | 19 +++++++++++++------ android/docker/scripts/buildZXing.sh | 2 +- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 4ded814..e42cafe 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.1" + _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.3" fi if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index c52887b..7c67979 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.5.2 +ARG QtVersion=v6.5.3 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 793bc16..76338a7 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.5.2 +QtVersion=v6.5.3 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index ebfc0dc..e2a0f54 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=1.67.0 +VERSION=1.83.0 echo "Based on boost version $VERSION" >> /etc/versions source /etc/profile @@ -16,12 +16,13 @@ tar xf /usr/local/cache/boost_$VER2.tar.bz2 cd boost_$VER2 export PATH="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" + ./bootstrap.sh --without-icu \ --with-toolset=clang \ --with-libraries=chrono,filesystem,iostreams,program_options,system,thread \ --prefix=/opt/android-boost -echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang++ : \"-std=c++17 -fvisibility=hidden -fPIC\" \"-fPIC\" ;" > user-config.jam +echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang++ : \"-std=c++17 -DBOOST_FILESYSTEM_DISABLE_STATX -DBOOST_FILESYSTEM_DISABLE_GETRANDOM -fvisibility=hidden -fPIC\" \"-fPIC\" ;" > user-config.jam ./b2 \ --reconfigure \ diff --git a/android/docker/scripts/buildOpenSsl.sh b/android/docker/scripts/buildOpenSsl.sh index 29e3409..3c3a136 100755 --- a/android/docker/scripts/buildOpenSsl.sh +++ b/android/docker/scripts/buildOpenSsl.sh @@ -1,7 +1,7 @@ #!/bin/bash # this runs as root. -VERSION=1.1.1q +VERSION=3.1.3 echo "Using OpenSSL $VERSION" >> /etc/versions source /etc/profile cd /usr/local/cache diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index a2cc076..4601458 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -26,14 +26,18 @@ function checkout ( # The QtBase builds are different. checkout qtbase +cd ~builduser +curl 'https://code.qt.io/cgit/qt/qtbase.git/patch/?id=8af35d27' > libxkbcommon-1.6.patch +patch -d qtbase -p1 < libxkbcommon-1.6.patch # Fix build with libxkbcommon 1.6 mkdir -p ~builduser/build/qtbase -(cd ~builduser/build/qtbase && \ +cd ~builduser/build/qtbase ~builduser/qtbase/configure \ -prefix /usr/local \ -no-openssl \ -nomake examples \ - -no-dbus && \ -ninja install) + -no-dbus +cmake --build . --parallel +cmake --install . rm -rf ~builduser/build/* ### Android build @@ -51,7 +55,8 @@ cd ~builduser/build/qtbase -- \ -DOPENSSL_USE_STATIC_LIBS=ON \ -DOPENSSL_ROOT_DIR=/opt/android-ssl -ninja install +cmake --build . --parallel +cmake --install . rm -rf ~builduser/build/* @@ -62,7 +67,8 @@ do mkdir -p ~builduser/build/$i cd ~builduser/build/$i /usr/local/bin/qt-configure-module ~builduser/$i - ninja install + cmake --build . --parallel + cmake --install . cd ~builduser rm -rf build/* @@ -70,7 +76,8 @@ do mkdir -p ~builduser/build/$i cd ~builduser/build/$i /opt/android-qt6/bin/qt-configure-module ~builduser/$i - ninja install + cmake --build . --parallel + cmake --install . cd ~builduser rm -rf build/* done diff --git a/android/docker/scripts/buildZXing.sh b/android/docker/scripts/buildZXing.sh index cbe9269..6e86810 100755 --- a/android/docker/scripts/buildZXing.sh +++ b/android/docker/scripts/buildZXing.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=1.4.0 +VERSION=2.1.0 echo "Based on zxing version $VERSION" >> /etc/versions source /etc/profile -- 2.54.0 From 750dc9b54d675362be9e8851ffb080800356a509 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Nov 2023 14:48:32 +0100 Subject: [PATCH 0711/1428] Format currency concistently. Negative values have the minus now also here before any currency symbol. --- src/PriceDataProvider.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 097505a..34b3362 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -160,8 +160,8 @@ QString PriceDataProvider::formattedPrice(int64_t fiatValue) const % m_currencySymbolPost; } else { - return m_currencySymbolPrefix - % (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) + return (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) + % m_currencySymbolPrefix % actualPrice % m_currencySymbolPost; } -- 2.54.0 From e8530f5255b306dff453471fd3bcaa6cbe6b0743 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Nov 2023 15:55:13 +0100 Subject: [PATCH 0712/1428] Various fixes for huge values. Avoid overflow when typing improbably big numbers. --- src/BitcoinValue.cpp | 12 ++++++++++-- src/PaymentDetailOutput.cpp | 11 +++++++---- src/PaymentDetailOutput_p.h | 8 ++++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/BitcoinValue.cpp b/src/BitcoinValue.cpp index 276ae5c..1d9461e 100644 --- a/src/BitcoinValue.cpp +++ b/src/BitcoinValue.cpp @@ -24,6 +24,8 @@ #include #include +#include + BitcoinValue::BitcoinValue(QObject *parent) : QObject(parent), m_value(0) @@ -169,7 +171,7 @@ bool BitcoinValue::setStringValue(const QString &value) before = QStringView(value).left(separator); after = value.mid(separator + 1); } - qint64 newVal = before.toLong(); + quint64 newVal = before.toLongLong(); const int unitConfigDecimals = m_fiatMode ? 2 : FloweePay::instance()->unitAllowedDecimals(); for (int i = 0; i < unitConfigDecimals; ++i) { newVal *= 10; @@ -177,7 +179,13 @@ bool BitcoinValue::setStringValue(const QString &value) while (after.size() < unitConfigDecimals) after += '0'; - newVal += QStringView(after).left(unitConfigDecimals).toInt(); + newVal += QStringView(after).left(unitConfigDecimals).toLongLong(); + if (newVal > std::numeric_limits::max()) + return false; + double test = newVal; + qint64 test2 = test; + if (test2 != static_cast(newVal)) // we lose on conversion, then the value is too big. + return false; if (!m_fiatMode) { constexpr int64_t Coin = 100000000; // num satoshis in a single coin diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index ea38c11..5ab6a84 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -40,7 +40,10 @@ double PaymentDetailOutput::paymentAmount() const if (p->fiatPrice() == 0) return 0; - return static_cast(m_fiatAmount * 100000000L / p->fiatPrice()); + double answer = m_fiatAmount; + answer /= p->fiatPrice(); + answer *= 1E8; + return answer; } if (m_maxAllowed && m_maxSelected) { /* @@ -262,14 +265,14 @@ void PaymentDetailOutput::setFiatFollows(bool on) emit fiatFollowsChanged(); } -int PaymentDetailOutput::paymentAmountFiat() const +qint64 PaymentDetailOutput::paymentAmountFiat() const { if (m_fiatFollows) { Payment *p = qobject_cast(parent()); assert(p); if (p->fiatPrice() == 0) return 0; - qint64 amount = m_paymentAmount; + quint64 amount = m_paymentAmount; if (m_maxAllowed && m_maxSelected) { assert(p->currentAccount()); auto wallet = p->currentAccount()->wallet(); @@ -292,7 +295,7 @@ int PaymentDetailOutput::paymentAmountFiat() const return m_fiatAmount; } -void PaymentDetailOutput::setPaymentAmountFiat(int amount) +void PaymentDetailOutput::setPaymentAmountFiat(qint64 amount) { if (m_fiatAmount == amount) return; diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 2da959d..12cb641 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -30,7 +30,7 @@ class PaymentDetailOutput : public PaymentDetail */ Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY paymentAmountChanged) - Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountFiatChanged) + Q_PROPERTY(qint64 paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountFiatChanged) // cleaned up and re-formatted, empty if invalid. Q_PROPERTY(QString formattedTarget READ formattedTarget NOTIFY correctAddressChanged) // same as formatted, but without prefix. @@ -74,8 +74,8 @@ public: bool maxAllowed() const; void setMaxAllowed(bool on); - int paymentAmountFiat() const; - void setPaymentAmountFiat(int amount); + qint64 paymentAmountFiat() const; + void setPaymentAmountFiat(qint64 amount); bool fiatFollows() const; void setFiatFollows(bool on); @@ -112,7 +112,7 @@ private: void createFormattedAddress(); qint64 m_paymentAmount = 0; - int m_fiatAmount = 0; + qint64 m_fiatAmount = 0; bool m_maxAllowed = true; // only the last in the sequence can have 'max' bool m_fiatFollows = false; bool m_maxSelected = false; -- 2.54.0 From 99801a2bf6c9f4a3b4ecf50d23044b8e235ce316 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Nov 2023 16:10:11 +0100 Subject: [PATCH 0713/1428] Cleanup network logger cmake stuff Move messge below the main apps Make the defines to be used in all places. --- CMakeLists.txt | 12 ++++++++---- src/CMakeLists.txt | 7 ++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98bcaa6..9b936b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,11 @@ if(NOT ANDROID) ) endif() +add_compile_definitions(LOG_DEFAULT_SECTION=10000) +add_compile_definitions(LOG_WALLET=10001) +if (NetworkLogClient) + add_compile_definitions(NETWORK_LOGGER) +endif() ###### Pay executable include_directories(${CMAKE_SOURCE_DIR}/src) @@ -326,10 +331,6 @@ message("Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} if (${local_qml}) message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") endif () -if (NetworkLogClient) - message ("-> Including network-logging capability") - add_compile_definitions(NETWORK_LOGGER) -endif() if (${BUILD_DESKTOP_PAY}) message ("-> Building Desktop-Pay...") if (${Qt6DBus_FOUND}) @@ -347,6 +348,9 @@ if (${BUILD_MOBILE_PAY}) message (" Found translation files, including in package") endif () endif() +if (NetworkLogClient) + message ("-> Including network-logging capability") +endif() if (${BUILD_PAY_TOOLS}) message ("-> Building Pay tools") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80007e9..cad1fef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,9 +19,6 @@ project(flowee_lib) option(local_qml "Allow local QML loading" OFF) option(NetworkLogClient "Include the network based logging in the executables" OFF) -add_definitions(-DLOG_DEFAULT_SECTION=10000) -add_definitions(-DLOG_WALLET=10001) - set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp @@ -43,6 +40,7 @@ set (PAY_SOURCES PriceDataProvider.cpp PriceHistoryDataProvider.cpp QRCreator.cpp + QMLClipboardHelper.cpp TransactionInfo.cpp Wallet.cpp WalletCoinsModel.cpp @@ -71,8 +69,7 @@ else () list(APPEND PAY_SOURCES main_utils.cpp) endif () -add_library(pay_lib STATIC ${PAY_SOURCES} - QMLClipboardHelper.h QMLClipboardHelper.cpp) +add_library(pay_lib STATIC ${PAY_SOURCES}) target_link_libraries(pay_lib flowee_apputils -- 2.54.0 From cd3e4495d750d55fcebdb56a812be6fa013b578c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Nov 2023 00:01:16 +0100 Subject: [PATCH 0714/1428] Fix linter issue; init order. --- src/FloweePay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 4535b40..c01ee5d 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -138,8 +138,8 @@ QString joinWords(const QList &words, bool lowercaseFirstWord) } FloweePay::FloweePay() - : m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), - m_chain(s_chain) + : m_chain(s_chain), + m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); -- 2.54.0 From 73ac074009fe59e307a05c7083df49549268bf3f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Nov 2023 00:02:43 +0100 Subject: [PATCH 0715/1428] Avoid saving twice This avoid us calling save twice within milliseconds. --- src/FloweePay.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index c01ee5d..a89b093 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -163,11 +163,13 @@ FloweePay::FloweePay() assert(guiApp); connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { - logInfo() << "App no longer active. Start saving data"; - m_sleepStart = QDateTime::currentDateTimeUtc(); - saveAll(); - p2pNet()->saveData(); - saveData(); + if (m_sleepStart.isNull()) { + logInfo() << "App no longer active. Start saving data"; + m_sleepStart = QDateTime::currentDateTimeUtc(); + saveAll(); + p2pNet()->saveData(); + saveData(); + } } else if (state == Qt::ApplicationActive) { /* @@ -190,6 +192,7 @@ FloweePay::FloweePay() // re-lock the app after 10 minutes of not being in the front. setAppProtection(AppPassword); } + m_sleepStart = QDateTime(); } }); #endif -- 2.54.0 From a3500b3ffff676c7e30e163eb1c044be83ce9aa0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Nov 2023 15:59:49 +0100 Subject: [PATCH 0716/1428] Lower logs in exe on mobile --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b936b5..ca76b5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,9 @@ add_compile_definitions(LOG_DEFAULT_SECTION=10000) add_compile_definitions(LOG_WALLET=10001) if (NetworkLogClient) add_compile_definitions(NETWORK_LOGGER) +elseif (ANDROID) + # include much less logging in the codebase, no point since we can't access it anyway + add_compile_definitions(BCH_NO_DEBUG_OUTPUT BCH_NO_INFO_OUTPUT BCH_NO_WARNING_OUTPUT) endif() ###### Pay executable -- 2.54.0 From 1ad76e4857da5761d815f8db28a6b85dcd5d9030 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Nov 2023 16:00:01 +0100 Subject: [PATCH 0717/1428] Start new version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7361f02..dfd02e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.10.0"); + qapp.setApplicationVersion("2023.11.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From d8e45a93fbe20839674219370e51eef568ae98c2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Nov 2023 16:27:15 +0100 Subject: [PATCH 0718/1428] Make swipe-start area smaller To swipe from the left edge now is limited to only 50 'pixels' instead of half the width. Additionally, don't allow interaction while the app pin-screen is showing. --- guis/mobile/MenuOverlay.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 95b98b6..f9e24f9 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee +import Flowee.org.pay; Item { id: root @@ -242,18 +243,19 @@ Item { } Item { id: menuSwipy - width: parent.width / 2 + width: 50 height: parent.height / 3 anchors.bottom: parent.bottom onXChanged: { // moving this drag area makes the menu slowly open. - let progress = x / width; + let SwipeDistance = dragOpenHandler.xAxis.maximum + let progress = x / SwipeDistance; let menuX = 0; if (progress < 0.2) // threshold for movement return; if (progress < 0.39) { // first 50% movement - menuX = Math.pow((progress - 0.1) * 10, 2) * width / 10; + menuX = Math.pow((progress - 0.1) * 10, 2) * SwipeDistance / 10; } else { // progress between 0.4 and 1.0 @@ -265,10 +267,10 @@ Item { DragHandler { id: dragOpenHandler - enabled: root.open === false && thePile.depth === 1 + enabled: root.open === false && thePile.depth === 1 && Pay.appProtection !== FloweePay.AppPassword yAxis.enabled: false // the anchors of parent do that too ¯\_(ツ)_/¯ xAxis.minimum: 0 - xAxis.maximum: parent.width + xAxis.maximum: mainWindow.width / 2 acceptedDevices: PointerDevice.TouchScreen | PointerDevice.Stylus onActiveChanged: { if (!active) { -- 2.54.0 From 25909437fdb7a520d8ec93e4e4d5cd0dd997d44a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 12:23:41 +0100 Subject: [PATCH 0719/1428] Clarify the terminology --- guis/Flowee/{CashFusionIcon.qml => CFIcon.qml} | 2 +- guis/desktop.qrc | 1 - guis/desktop/SendTransactionPane.qml | 4 ++-- guis/desktop/WalletTransaction.qml | 12 ++++++------ guis/mobile.qrc | 1 - guis/mobile/AccountHistory.qml | 12 ++++++------ guis/mobile/TransactionDetails.qml | 11 +++-------- guis/mobile/TxInfoSmall.qml | 6 +++--- guis/widgets.qrc | 3 ++- src/TransactionInfo.cpp | 4 ++-- src/TransactionInfo.h | 6 +++--- src/WalletHistoryModel.cpp | 4 ++-- src/WalletHistoryModel.h | 2 +- src/Wallet_support.cpp | 2 +- 14 files changed, 32 insertions(+), 38 deletions(-) rename guis/Flowee/{CashFusionIcon.qml => CFIcon.qml} (93%) diff --git a/guis/Flowee/CashFusionIcon.qml b/guis/Flowee/CFIcon.qml similarity index 93% rename from guis/Flowee/CashFusionIcon.qml rename to guis/Flowee/CFIcon.qml index 7a27f4b..3d2fd11 100644 --- a/guis/Flowee/CashFusionIcon.qml +++ b/guis/Flowee/CFIcon.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.11 as QQC2 Image { id: fusedIcon visible: fusedCount > 0 - source: "qrc:/cashfusion.svg" + source: "qrc:/cf.svg" width: 24 height: 24 QQC2.ToolTip { diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 273b916..86c3c54 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -3,7 +3,6 @@ images/FloweePay.png images/FloweePay.svg images/FloweePay-light.svg - images/cashfusion.svg images/emblem-warning.svg desktop/images/sendIcon.png desktop/images/sendIcon-light.png diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index a492622..8b9d9be 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -578,7 +578,7 @@ Item { value: model.value anchors.baseline: mainText.baseline anchors.right: parent.right - // only HD wallets can use cash-fusion + // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 } Label { @@ -640,7 +640,7 @@ Item { } } } - Flowee.CashFusionIcon { + Flowee.CFIcon { id: fusedIcon anchors.right: parent.right anchors.verticalCenter: mainText.verticalCenter diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index cab8644..8f716e8 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -41,7 +41,7 @@ Rectangle { model.height model.date model.isCoinbase - model.isCashFusion + model.isFused model.comment */ @@ -61,8 +61,8 @@ Rectangle { text: { if (model.isCoinbase) return qsTr("Miner Reward") - if (model.isCashFusion) - return qsTr("Cash Fusion") + if (model.isFused) + return qsTr("Fused") if (model.fundsIn === 0) return qsTr("Received") let diff = model.fundsOut - model.fundsIn; @@ -96,9 +96,9 @@ Rectangle { } } - Flowee.CashFusionIcon { + Flowee.CFIcon { id: fusedIcon - visible: model.isCashFusion + visible: model.isFused anchors.right: userComment.left anchors.rightMargin: 6 anchors.verticalCenter: userComment.verticalCenter @@ -138,7 +138,7 @@ Rectangle { let inputs = model.fundsIn let outputs = model.fundsOut let diff = model.fundsOut - model.fundsIn - if (!model.isCashFusion + if (!model.isFused && diff < 0 && diff > -1000) // then the diff is likely just fees. return inputs; return diff; diff --git a/guis/mobile.qrc b/guis/mobile.qrc index ec47736..c8954ff 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -2,7 +2,6 @@ images/FloweePay-light.svg images/FloweePay.svg - images/cashfusion.svg images/bch.svg mobile/images/back-arrow.svg mobile/images/maslenica.svg diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index e719ded..880edbe 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -195,7 +195,7 @@ ListView { // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. property bool isMoved: { - if (model.isCoinbase || model.isCashFusion || model.fundsIn === 0) + if (model.isCoinbase || model.isFused || model.fundsIn === 0) return false; var amount = model.fundsOut - model.fundsIn return amount < 0 && amount > -2500 // then the diff is likely just fees. @@ -241,8 +241,8 @@ ListView { // icon Image { source: { - if (model.isCashFusion) - return "qrc:/cashfusion.svg"; + if (model.isFused) + return "qrc:/cf.svg"; if (model.fundsIn === 0) var base = "receiving"; else if (isMoved) @@ -272,8 +272,8 @@ ListView { if (model.isCoinbase) return qsTr("Miner Reward"); - if (model.isCashFusion) - return qsTr("Cash Fusion"); + if (model.isFused) + return qsTr("Fused"); if (model.fundsIn === 0) return qsTr("Received"); if (isMoved) @@ -325,7 +325,7 @@ ListView { anchors.rightMargin: 20 radius: 6 baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: - visible: !model.isCashFusion + visible: !model.isFused color: (isMoved || amountBch < 0) ? "#00000000" : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 60aff69..fbc21a3 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -116,13 +116,8 @@ Page { text: qsTr("Coinbase") visible: root.transaction != null && root.transaction.isCoinbase } - Flowee.Label { - text: qsTr("Is a CashFusion transaction.") - visible: root.transaction != null && root.transaction.isCashFusion - } } - // We can't calculate the fees of just any transaction, only for the ones // this account created. PageTitledBox { @@ -142,11 +137,11 @@ Page { title: { if (root.infoObject == null) return ""; - if (infoObject.isCashFusion) + if (infoObject.isFused) return qsTr("Fused from my addresses"); if (infoObject.createdByUs) return qsTr("Sent from my addresses"); - if (infoObject.isCashFusion) + if (infoObject.isFused) return qsTr("Sent from addresses"); return ""; } @@ -213,7 +208,7 @@ Page { title: { if (root.infoObject == null) return ""; - if (infoObject.isCashFusion) + if (infoObject.isFused) return qsTr("Fused into my addresses"); if (infoObject.createdByUs) return qsTr("Received at addresses"); diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 30f9926..1491efd 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -73,8 +73,8 @@ ColumnLayout { text: { if (model.isCoinbase) return qsTr("Miner Reward") + ":"; - if (model.isCashFusion) - return qsTr("Cash Fusion") + ":"; + if (model.isFused) + return qsTr("Fees") + ":"; if (model.fundsIn === 0) return qsTr("Received") + ":"; if (isMoved) @@ -154,7 +154,7 @@ ColumnLayout { visible: { if (root.minedHeight < 1) return false; - if (model.isCashFusion) + if (model.isFused) return false; if (isMoved) return false; diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 82b4ddc..38ad9bf 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -8,6 +8,7 @@ images/edit-copy-light.svg images/internet.svg images/CashTokens.svg + images/cashfusion.svg images/lock-light.svg images/lock.svg images/Flowee-Symbols.otf @@ -31,7 +32,7 @@ Flowee/FiatValueField.qml Flowee/BitcoinValueField.qml Flowee/Dialog.qml - Flowee/CashFusionIcon.qml + Flowee/CFIcon.qml Flowee/DialogButtonBox.qml Flowee/WarningLabel.qml Flowee/PasswdDialog.qml diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 98f9b4f..b062929 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -86,9 +86,9 @@ void TransactionInfo::setUserComment(const QString &comment) emit commentChanged(); } -bool TransactionInfo::isCashFusion() const +bool TransactionInfo::isFused() const { - return m_isCashFusion; + return m_isFused; } bool TransactionInfo::isCoinbase() const diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index a8578d0..7fed7f6 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -104,7 +104,7 @@ class TransactionInfo : public QObject */ Q_PROPERTY(QString receiver READ receiver CONSTANT) Q_PROPERTY(bool isCoinbase READ isCoinbase CONSTANT) - Q_PROPERTY(bool isCashFusion READ isCashFusion CONSTANT) + Q_PROPERTY(bool isFused READ isFused CONSTANT) Q_PROPERTY(bool createdByUs READ createdByUs CONSTANT) Q_PROPERTY(bool commentEditable READ commentEditable CONSTANT) @@ -118,7 +118,7 @@ public: const QString &userComment() const; void setUserComment(const QString &comment); bool isCoinbase() const; - bool isCashFusion() const; + bool isFused() const; bool createdByUs() const; QString receiver() const; @@ -137,7 +137,7 @@ public: QList m_inputs; QString m_userComment; bool m_isCoinbase = false; - bool m_isCashFusion = false; + bool m_isFused = false; bool m_createdByUs = false; signals: diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 51d69ba..2abfd9d 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -164,7 +164,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(item.minedBlockHeight > m_lastSyncIndicator || item.isUnconfirmed()); case IsCoinbase: return QVariant(item.isCoinbase); - case IsCashFusion: + case IsFused: return QVariant(item.isCashFusionTx); case Comment: return QVariant(item.userComment); @@ -242,7 +242,7 @@ QHash WalletHistoryModel::roleNames() const answer[FundsOut] = "fundsOut"; answer[WalletIndex] = "walletIndex"; answer[IsCoinbase] = "isCoinbase"; - answer[IsCashFusion] = "isCashFusion"; + answer[IsFused] = "isFused"; answer[Comment] = "comment"; answer[PlacementInGroup] = "placementInGroup"; answer[GroupId] = "grouping"; diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 0910395..3c5b60c 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -50,7 +50,7 @@ public: FundsOut, ///< value (in sats) of the outputs created we own WalletIndex, ///< wallet-internal index for this transaction. IsCoinbase, - IsCashFusion, + IsFused, Comment, PlacementInGroup, ///< Is an enum WalletEnums::PlacementInGroup to help with painting outlines. GroupId ///< The index in the m_groups vector diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 02fa292..b041eac 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -205,7 +205,7 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) throw std::runtime_error("Invalid tx-index"); const auto &wtx = iter->second; - info->m_isCashFusion = wtx.isCashFusionTx; + info->m_isFused = wtx.isCashFusionTx; info->m_isCoinbase = wtx.isCoinbase; info->m_userComment = wtx.userComment; -- 2.54.0 From 00069c10b6932535f23a358e94cd67cf58fa4376 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 12:57:38 +0100 Subject: [PATCH 0720/1428] Import translations from crowdin Also apply the CF changes --- translations/floweepay-common_de.ts | 18 +++++++++--------- translations/floweepay-common_en.ts | 4 ++-- translations/floweepay-common_es.ts | 4 ++-- translations/floweepay-common_nl.ts | 4 ++-- translations/floweepay-common_pl.ts | 4 ++-- translations/floweepay-common_pt.ts | 4 ++-- translations/floweepay-desktop_de.ts | 2 +- translations/floweepay-mobile-nl.ts | 13 ++++--------- translations/floweepay-mobile_de.ts | 17 ++++++----------- translations/floweepay-mobile_en.ts | 11 +++-------- translations/floweepay-mobile_es.ts | 11 +++-------- translations/floweepay-mobile_nl.ts | 13 ++++--------- translations/floweepay-mobile_pl.ts | 13 ++++--------- translations/floweepay-mobile_pt.ts | 13 ++++--------- 14 files changed, 48 insertions(+), 83 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index bf0d477..bf65977 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -91,7 +91,7 @@ Failed - Failed + Fehlgeschlagen @@ -101,12 +101,12 @@ Payment has been sent to: - Payment has been sent to: + Zahlung wurde gesendet an: Copied address to clipboard - Copied address to clipboard + Adresse in Zwischenablage kopiert @@ -125,9 +125,9 @@
- CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity Coin wurde für eine erhöhte Anonymität fusioniert @@ -288,12 +288,12 @@ Request received over insecure channel. Anyone could have altered it! - Request received over insecure channel. Anyone could have altered it! + Anfrage über unsicheren Kanal empfangen. Jeder hätte sie verändern können! Download of payment request failed. - Download of payment request failed. + Download der Zahlungsanfrage fehlgeschlagen. @@ -301,13 +301,13 @@ Payment request unreadable - Payment request unreadable + Zahlungsanfrage unlesbar Payment request expired - Payment request expired + Zahlungsanfrage abgelaufen diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index 19662ae..66a081d 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -125,9 +125,9 @@
- CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity Coin has been fused for increased anonymity diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 3f75a59..6aa0957 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -125,9 +125,9 @@
- CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity La moneda se ha fusionado para aumentar el anonimato diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 3c69baa..28330f9 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -125,9 +125,9 @@
- CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity Munt is gefuseerd voor verhoogde anonimiteit diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 7497791..21e4604 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -131,9 +131,9 @@ - CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity Moneta poddana fuzji dla podniesienia anonimowości diff --git a/translations/floweepay-common_pt.ts b/translations/floweepay-common_pt.ts index 86c6e59..a962a9d 100644 --- a/translations/floweepay-common_pt.ts +++ b/translations/floweepay-common_pt.ts @@ -125,9 +125,9 @@ - CashFusionIcon + CFIcon - + Coin has been fused for increased anonymity Moeda fundida para maior anonimato diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 2947b8c..fcde05f 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -563,7 +563,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Payment request warnings: - Payment request warnings: + Warnungen für Zahlungsanforderungen: diff --git a/translations/floweepay-mobile-nl.ts b/translations/floweepay-mobile-nl.ts index cfbce8f..3348bd4 100644 --- a/translations/floweepay-mobile-nl.ts +++ b/translations/floweepay-mobile-nl.ts @@ -69,8 +69,8 @@ - Cash Fusion - Cash Fusion + Fused + Fused @@ -965,11 +965,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Size: %1 bytes Grootte: %1 bytes - - - Is a CashFusion transaction. - CashFusion transactie. - Fees paid @@ -1055,8 +1050,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Cash Fusion - Cash Fusion + Fees + Kosten diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 514cbd4..49b5736 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -69,8 +69,8 @@ - Cash Fusion - Cash Fusion + Fused + Fused @@ -782,12 +782,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Instant Pay limit is %1 - Instant Pay limit is %1 + Sofortzahlungslimit ist %1 Selected wallet: '%1' - Selected wallet: '%1' + Ausgewählte Geldbörse: '%1' @@ -1009,11 +1009,6 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussCoinbase Coinbase - - - Is a CashFusion transaction. - Ist eine CashFusion Transaktion. - Fees paid @@ -1094,8 +1089,8 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - Cash Fusion - Cash Fusion + Fees + Bezahlte Gebühren diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 2a8517a..61a0077 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -69,7 +69,7 @@ - Cash Fusion + Fused Cash Fusion @@ -1009,11 +1009,6 @@ This ensures only one private key will need to be backed up Coinbase Coinbase - - - Is a CashFusion transaction. - Is a CashFusion transaction. - Fees paid @@ -1094,8 +1089,8 @@ This ensures only one private key will need to be backed up - Cash Fusion - Cash Fusion + Fees + Fees diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index a2c068e..91ff94f 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -69,7 +69,7 @@ - Cash Fusion + Fused Fusión de Monedas @@ -1009,11 +1009,6 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Coinbase Coinbase - - - Is a CashFusion transaction. - Es una transacción de CashFusion. - Fees paid @@ -1094,8 +1089,8 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - Cash Fusion - Fusión de Monedas + Fees + Comisiones diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index e389312..930988c 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -69,8 +69,8 @@ - Cash Fusion - Cash Fusion + Fused + Gefuseerd @@ -1009,11 +1009,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Coinbase Coinbase - - - Is a CashFusion transaction. - CashFusion transactie. - Fees paid @@ -1094,8 +1089,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Cash Fusion - Cash Fusion + Fees + Kosten diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index fe263cd..d054521 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -69,8 +69,8 @@ - Cash Fusion - Cash Fusion + Fused + Fused @@ -1010,11 +1010,6 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoCoinbase Coinbase - - - Is a CashFusion transaction. - Jest transakcją CashFusion. - Fees paid @@ -1097,8 +1092,8 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - Cash Fusion - Cash Fusion + Fees + Fees diff --git a/translations/floweepay-mobile_pt.ts b/translations/floweepay-mobile_pt.ts index 8e8da26..6e11b75 100644 --- a/translations/floweepay-mobile_pt.ts +++ b/translations/floweepay-mobile_pt.ts @@ -69,8 +69,8 @@ - Cash Fusion - Cash Fusion + Fused + Fused @@ -1009,11 +1009,6 @@ This ensures only one private key will need to be backed up Coinbase Coinbase - - - Is a CashFusion transaction. - Is a CashFusion transaction. - Fees paid @@ -1094,8 +1089,8 @@ This ensures only one private key will need to be backed up - Cash Fusion - Cash Fusion + Fees + Fees -- 2.54.0 From be3d6607e73a773e05b1830db5e12f734ddd61f7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 15:00:11 +0100 Subject: [PATCH 0721/1428] UX; quick-change darkmode unsets system User-changing the darkmode implicitly unsets the app following the system setting. --- guis/mobile/MenuOverlay.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index f9e24f9..13e2bfa 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -98,7 +98,10 @@ Item { MouseArea { anchors.fill: parent anchors.margins: -10 - onClicked: Pay.useDarkSkin = !Pay.useDarkSkin + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = !Pay.useDarkSkin; + } } } Flowee.Label { -- 2.54.0 From 038cd724287ef93ea5ce3be32d9948795b6a7cb5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 15:55:10 +0100 Subject: [PATCH 0722/1428] Re-work 'back' button behavior Pressing 'Escape' or (android) back button is now more logical. Closing the menu is new, going back to the 'main' tab as well. This also fixes some odd behavior when using the back button while the camera is active. And last, when there is nothing more to back out of, on Android we end up closing the application. This is what people expect on that platform. --- guis/mobile/MainViewBase.qml | 10 +++++ guis/mobile/Page.qml | 6 --- guis/mobile/QRScannerOverlay.qml | 6 --- guis/mobile/UnlockApplication.qml | 9 ++--- guis/mobile/main.qml | 62 +++++++++++++++++++++++-------- 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 890b361..852a97f 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -220,4 +220,14 @@ QQC2.Control { source: parent.visible ? "./UnlockWalletPanel.qml" : "" } } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { + // when we are on another tab, we can go to 'main' on back. + if (root.currentIndex != 0) { + root.currentIndex = 0; + event.accepted = true; + } + } + // in all other cases, propagate the event up the stack. + } } diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 023107e..31836ac 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -129,10 +129,4 @@ QQC2.Control { contentItem: FocusScope { id: focusScope } - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back) { - event.accepted = true; - thePile.pop(); - } - } } diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index d77d7f2..4f13785 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -28,12 +28,6 @@ FocusScope { enabled: visible onVisibleChanged: if (visible) root.forceActiveFocus(); - Keys.onPressed: (event)=> { - if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { - event.accepted = true; - CameraController.abort(); - } - } Rectangle { id: background anchors.fill: parent diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 88474e7..d2486a5 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -30,13 +30,10 @@ FocusScope { anchors.fill: parent anchors.margins: 10 onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); - - // called from main.qml, need to be implemented here - // since we dont extend the Page type. - function takeFocus() { - } } Keys.onPressed: (event)=> { - event.accepted = true; // at all key events. + if (event.key !== Qt.Key_Back) { // exit app on 'back'. + event.accepted = true; // at all other key events. + } } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 21cfa7e..bfeedeb 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -63,21 +63,55 @@ ApplicationWindow { property color errorRed: Pay.useDarkSkin ? "#ff6568" : "#940000" property color errorRedBg: Pay.useDarkSkin ? "#671314" : "#9f1d1f" - StackView { - id: thePile - anchors.fill: parent - initialItem: "./Loading.qml" - onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); - enabled: !menuOverlay.open - } - MenuOverlay { - id: menuOverlay + FocusScope { + id: rootFocusScope anchors.fill: parent + + StackView { + id: thePile + anchors.fill: parent + initialItem: "./Loading.qml" + onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); + enabled: !menuOverlay.open + Keys.onPressed: (event)=> { + if (depth > 1 + && (event.key === Qt.Key_Escape || event.key === Qt.Key_Back)) { + pop(); + event.accepted = true; + } + } + } + MenuOverlay { + id: menuOverlay + anchors.fill: parent + } + QRScannerOverlay { + id: scannerOverlay + anchors.fill: parent + } + Loader { + source: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "" + anchors.fill: parent + } + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { + event.accepted = true; + // Aborting the camera will simply close its user page. + if (scannerOverlay.visible) { + CameraController.abort(); + } + // the 'menu' can be closed on back. + else if (menuOverlay.open) { + menuOverlay.open = false; + } + else { + mainWindow.close(); + } + } + } } - QRScannerOverlay { - anchors.fill: parent - } QQC2.Popup { id: notificationPopup y: 110 @@ -130,8 +164,4 @@ ApplicationWindow { } } - Loader { - source: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "" - anchors.fill: parent - } } -- 2.54.0 From 227936956971abfa882592d023505e3c2d830580 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 16:28:39 +0100 Subject: [PATCH 0723/1428] Fix typo in language-name Thanks for Georg for reporting! --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index f66cc9d..8c61b96 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -66,7 +66,7 @@ Tom Zander ## Translations -Deutc +Deutsc
h
Georg Engelmann
Español
-- 2.54.0 From d976ebbb59e069a9a05f2e9dfb518754edb63eb8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 17:17:18 +0100 Subject: [PATCH 0724/1428] Set focus properly after scanning. In the case where there is no specific input needed, because the amounts were all specified, we still need to set the focus to the page because otherwise 'back' / 'esc' don't behave correctly. --- guis/mobile/PayWithQR.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 11aabf5..d9c5dd7 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -91,6 +91,8 @@ Page { root.allowEditAmount = payment.paymentAmount <= 0; if (root.allowEditAmount) priceInput.takeFocus(); + else + root.takeFocus(); } } Payment { -- 2.54.0 From fc33df842b53e133d6b20bfc9b6ccbc9fb65c15e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 18:02:16 +0100 Subject: [PATCH 0725/1428] Update android version --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 1fc52a1..53bf1ea 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="19" android:versionName="2023.11.0"> -- 2.54.0 From e2c5d7191eda8e94a82e29c799942ba856dd0037 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 19:48:13 +0100 Subject: [PATCH 0726/1428] Avoid buttons overlapping. --- guis/Flowee/ImageButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index 8e889bf..86f6306 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -20,7 +20,7 @@ import QtQuick import QtQuick.Controls as QQC2 QQC2.Control { - implicitWidth: iconSize + 8 + implicitWidth: Math.max(iconSize + 8, label.contentWidth) implicitHeight: iconSize + 8 + (label.visible ? label.height + 6 : 0) signal clicked; -- 2.54.0 From 6bafcecde94e82c6b38acf962e8ea0a8fbe29018 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Nov 2023 19:51:57 +0100 Subject: [PATCH 0727/1428] Wrap the GuiSettings in a Flickable For some screens it would not fit anymore on one screen. --- guis/mobile/GuiSettings.qml | 259 +++++++++++++++++++----------------- 1 file changed, 134 insertions(+), 125 deletions(-) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 1447617..0ce6a3e 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -24,147 +24,156 @@ Page { headerText: qsTr("Display Settings") id: root - ColumnLayout { - width: parent.width - spacing: 10 + Flickable { + anchors.fill: parent + contentWidth: width + contentHeight: col.height + boundsBehavior: Flickable.StopAtBounds // don't show this is a flickable if there is no space issues - PageTitledBox { - title: qsTr("Font sizing") - Row { - width: parent.width - id: fontSizing - property double buttonWidth: width / 6 - Repeater { - model: 6 - delegate: Item { - width: fontSizing.buttonWidth - height: 30 - property int target: index * 25 + 75 + ColumnLayout { + id: col + width: parent.width + spacing: 10 - Rectangle { - width: parent.width - 5 - x: 2.5 - height: 3 - color: Pay.fontScaling === target ? palette.highlight : palette.button + PageTitledBox { + title: qsTr("Font sizing") + Row { + width: parent.width + id: fontSizing + property double buttonWidth: width / 6 + Repeater { + model: 6 + delegate: Item { + width: fontSizing.buttonWidth + height: 30 + property int target: index * 25 + 75 + + Rectangle { + width: parent.width - 5 + x: 2.5 + height: 3 + color: Pay.fontScaling === target ? palette.highlight : palette.button + } + + Flowee.Label { + font.pixelSize: 15 + text: "" + target + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + MouseArea { + anchors.fill: parent + anchors.topMargin: -30 + onClicked: Pay.fontScaling = target + } } + } + } + } + PageTitledBox { + title: qsTr("Dark Theme") + Flowee.RadioButton { + text: "Follow System" + checked: Pay.skinFollowsPlatform + onClicked: Pay.skinFollowsPlatform = true; + } + Flowee.RadioButton { + text: "Dark" + checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = true; + } + } + Flowee.RadioButton { + text: "Light" + checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = false; + } + } + } + + PageTitledBox { + title: qsTr("Unit") + + Flowee.ComboBox { + id: unitSelector + model: { + var answer = []; + for (let i = 0; i < 5; ++i) { + answer[i] = Pay.nameOfUnit(i); + } + return answer; + } + currentIndex: Pay.unit + onCurrentIndexChanged: Pay.unit = currentIndex + } + + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + color: "#00000000" + radius: 6 + border.color: palette.button + border.width: 0.8 + + implicitHeight: units.height + 10 + implicitWidth: units.width + 10 + + GridLayout { + id: units + columns: 3 + x: 5; y: 5 + rowSpacing: 0 Flowee.Label { - font.pixelSize: 15 - text: "" + target - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - } - MouseArea { - anchors.fill: parent - anchors.topMargin: -30 - onClicked: Pay.fontScaling = target - } - } - } - } - } - - PageTitledBox { - title: qsTr("Dark Theme") - Flowee.RadioButton { - text: "Follow System" - checked: Pay.skinFollowsPlatform - onClicked: Pay.skinFollowsPlatform = true; - } - Flowee.RadioButton { - text: "Dark" - checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin - onClicked: { - Pay.skinFollowsPlatform = false; - Pay.useDarkSkin = true; - } - } - Flowee.RadioButton { - text: "Light" - checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin - onClicked: { - Pay.skinFollowsPlatform = false; - Pay.useDarkSkin = false; - } - } - } - - PageTitledBox { - title: qsTr("Unit") - - Flowee.ComboBox { - id: unitSelector - model: { - var answer = []; - for (let i = 0; i < 5; ++i) { - answer[i] = Pay.nameOfUnit(i); - } - return answer; - } - currentIndex: Pay.unit - onCurrentIndexChanged: Pay.unit = currentIndex - } - - Rectangle { - anchors.horizontalCenter: parent.horizontalCenter - color: "#00000000" - radius: 6 - border.color: palette.button - border.width: 0.8 - - implicitHeight: units.height + 10 - implicitWidth: units.width + 10 - - GridLayout { - id: units - columns: 3 - x: 5; y: 5 - rowSpacing: 0 - Flowee.Label { - text: { - var answer = "1"; - for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { - answer += "0"; + text: { + var answer = "1"; + for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { + answer += "0"; + } + return answer + " " + Pay.unitName; } - return answer + " " + Pay.unitName; + Layout.alignment: Qt.AlignRight } - Layout.alignment: Qt.AlignRight - } - Flowee.Label { text: "=" } - Flowee.Label { text: "1 Bitcoin Cash" } + Flowee.Label { text: "=" } + Flowee.Label { text: "1 Bitcoin Cash" } - Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} - Flowee.Label { text: "="; visible: Pay.isMainChain} - Flowee.Label { - text: { - var amount = 1; - for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { - amount = amount * 10; + Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} + Flowee.Label { text: "="; visible: Pay.isMainChain} + Flowee.Label { + text: { + var amount = 1; + for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { + amount = amount * 10; + } + return Fiat.formattedPrice(amount, Fiat.price); } - return Fiat.formattedPrice(amount, Fiat.price); + visible: Pay.isMainChain } - visible: Pay.isMainChain } } } - } - TextButton { - text: qsTr("Change Currency (%1)").arg(Fiat.currencyName) - showPageIcon: true - onClicked: thePile.push("./CurrencySelector.qml") - } + TextButton { + Layout.fillWidth: true + text: qsTr("Change Currency (%1)").arg(Fiat.currencyName) + showPageIcon: true + onClicked: thePile.push("./CurrencySelector.qml") + } - PageTitledBox { - title: qsTr("Main View") - visible: Pay.isMainChain // because we only have one option right now + PageTitledBox { + title: qsTr("Main View") + visible: Pay.isMainChain // because we only have one option right now - Flowee.CheckBox { - width: parent.width - text: qsTr("Show Bitcoin Cash value") - checked: Pay.activityShowsBch - onCheckedChanged: Pay.activityShowsBch = checked - visible: Pay.isMainChain // only mainchain has fiat value + Flowee.CheckBox { + width: parent.width + text: qsTr("Show Bitcoin Cash value") + checked: Pay.activityShowsBch + onCheckedChanged: Pay.activityShowsBch = checked + visible: Pay.isMainChain // only mainchain has fiat value + } } } } -- 2.54.0 From 2d49bfb8045debc4f29b01b87333f11e3977ce6b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 19 Nov 2023 17:13:00 +0100 Subject: [PATCH 0728/1428] Payments are valid also with non-p2pkh targets. --- src/PaymentDetailOutput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 5ab6a84..ff91f87 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -188,7 +188,7 @@ void PaymentDetailOutput::createFormattedAddress() void PaymentDetailOutput::checkValid() { - bool valid = m_addressOk; + bool valid = m_addressOk || !m_outputScript.isEmpty(); valid = valid && ((m_maxSelected && m_maxAllowed) || (m_fiatFollows && m_paymentAmount > 600) -- 2.54.0 From 47746e724de54ccfe9909d36d19571daf1320c89 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 24 Nov 2023 18:20:41 +0100 Subject: [PATCH 0729/1428] Follow upstream includes rename The PublicKey. and PrivteKey.h used to be called different. --- src/AccountInfo.cpp | 2 +- src/PaymentRequest.h | 2 +- src/Wallet.h | 4 ++-- src/main.cpp | 2 +- testing/wallet/TestWallet.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 4ebc82c..bc01d7d 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 3d3325b..364c45a 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -19,7 +19,7 @@ #define PAYMENTREQUEST_H #include -#include +#include class AccountInfo; class WalletKeyView; diff --git a/src/Wallet.h b/src/Wallet.h index 3d07165..ba34c77 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -27,8 +27,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/src/main.cpp b/src/main.cpp index dfd02e7..aad731d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,7 +35,7 @@ #include "QRScanner.h" #endif -#include // for ECC_Start() +#include // for ECC_Start() #include #include diff --git a/testing/wallet/TestWallet.h b/testing/wallet/TestWallet.h index e47bc59..6f29b8a 100644 --- a/testing/wallet/TestWallet.h +++ b/testing/wallet/TestWallet.h @@ -22,7 +22,7 @@ #include #include -#include +#include struct ECC_State { ECC_State() { ECC_Start(); } -- 2.54.0 From dad6f5479025b2f0846a5ff7c1546af7d1d4c721 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 30 Nov 2023 21:16:52 +0100 Subject: [PATCH 0730/1428] New bugfix version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 53bf1ea..65632f9 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="20" android:versionName="2023.11.1"> diff --git a/src/main.cpp b/src/main.cpp index aad731d..e8d5659 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.11.0"); + qapp.setApplicationVersion("2023.11.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From b317e20bdffa6b6e6ac3355994cb3ad0e6fb6433 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 7 Dec 2023 12:55:39 +0100 Subject: [PATCH 0731/1428] Make pressing back on popup close popup All key handlers should act on 'pressed' instead of 'released' in order to avoid a parent stealing a key from a child. --- guis/mobile/PopupOverlay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index a4ac5ca..868fbf1 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -81,7 +81,7 @@ FocusScope { } } - Keys.onReleased: (event)=> { + Keys.onPressed: (event)=> { if (event.key === Qt.Key_Back || event.key === Qt.Key_Escape) { event.accepted = true; root.close(); -- 2.54.0 From 8e0c7c57e60b71f81158789aadc8995ac8c7eee6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 22 Dec 2023 14:56:21 +0100 Subject: [PATCH 0732/1428] Follow Streaming::pool() refactor This upstream refactor stopped passing in the pool by pointer and now wraps it in a shared_ptr. A lot less 'address-of' operators and generally cleaner code are the result. --- src/FloweePay.cpp | 8 ++-- src/ModuleManager.cpp | 2 +- src/PriceHistoryDataProvider.cpp | 22 ++++----- src/Wallet.cpp | 50 ++++++++++---------- src/Wallet.h | 2 +- src/Wallet_encryption.cpp | 12 ++--- testing/wallet/TestWallet.cpp | 80 ++++++++++++++++---------------- 7 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index a89b093..654de53 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -277,9 +277,9 @@ FloweePay::FloweePay() QFile in(m_basedir + AppdataFilename); if (in.open(QIODevice::ReadOnly)) { const auto dataSize = in.size(); - auto &pool = Streaming::pool(dataSize); - in.read(pool.begin(), dataSize); - Streaming::MessageParser parser(pool.commit(dataSize)); + auto pool = Streaming::pool(dataSize); + in.read(pool->begin(), dataSize); + Streaming::MessageParser parser(pool->commit(dataSize)); while (parser.next() == Streaming::FoundTag) { switch (parser.tag()) { case AppProtectionType: @@ -480,7 +480,7 @@ void FloweePay::loadingCompleted() void FloweePay::saveData() { - Streaming::BufferPool data(m_wallets.size() * 100); + auto data = std::make_shared(m_wallets.size() * 100); Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { if (wallet->encryptionSeed() != 0) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 6b8ad9a..cd32c2e 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -169,7 +169,7 @@ void ModuleManager::save() const saveFileSize += m->sections().size() * 5; } - Streaming::BufferPool pool(saveFileSize); + auto pool = std::make_shared(saveFileSize); Streaming::MessageBuilder builder(pool); for (const auto *m : m_modules) { builder.add(ModuleId, m->id().toStdString()); diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index 836cf37..6136e50 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -60,9 +60,9 @@ PriceHistoryDataProvider::PriceHistoryDataProvider(const QString &basedir, const le32toh(*reinterpret_cast(buf + 4)))); } } else { - auto &pool = Streaming::pool(static_cast(fileSize)); - input.read(pool.begin(), fileSize); - data->valueBlob = pool.commit(fileSize); + auto pool = Streaming::pool(static_cast(fileSize)); + input.read(pool->begin(), fileSize); + data->valueBlob = pool->commit(fileSize); data->hasBlob = true; } } @@ -199,13 +199,13 @@ struct Day { return diff >= 60 * 60 * 24; } - void writeAverage(Streaming::BufferPool &pool) + void writeAverage(const std::shared_ptr&pool) { if (count > 0) { char buf[8]; *reinterpret_cast(buf) = htole32(timestampCum / count); *reinterpret_cast(buf + 4) = htole32(valueCum / count); - pool.write(buf, 8); + pool->write(buf, 8); } } @@ -233,8 +233,8 @@ void PriceHistoryDataProvider::processLog() assert(data->log); data->log->close(); - auto &pool = Streaming::pool(data->logValues.size() * 8 + data->valueBlob.size()); - pool.write(data->valueBlob); + auto pool = Streaming::pool(data->logValues.size() * 8 + data->valueBlob.size()); + pool->write(data->valueBlob); // Iterate over the log and for each 24h period compress all items into // one entry for that day. @@ -259,7 +259,7 @@ void PriceHistoryDataProvider::processLog() QFile blobData(fiatPath); if (!blobData.open(QIODevice::WriteOnly)) throw std::runtime_error("PriceHistory: Failed to open file for write"); - data->valueBlob = pool.commit(); + data->valueBlob = pool->commit(); auto count = blobData.write(data->valueBlob.begin(), data->valueBlob.size()); assert(count == data->valueBlob.size()); data->logValues.clear(); @@ -313,9 +313,9 @@ void PriceHistoryDataProvider::initialPopulate() QFile input(m_basedir + '/' + currency); if (input.open(QIODevice::ReadOnly)) { const auto fileSize = input.size(); - auto &pool = Streaming::pool(static_cast(fileSize)); - input.read(pool.begin(), fileSize); - data->valueBlob = pool.commit(fileSize); + auto pool = Streaming::pool(static_cast(fileSize)); + input.read(pool->begin(), fileSize); + data->valueBlob = pool->commit(fileSize); } }); f->fetch(m_basedir, m_currency); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 1b87575..d7c8c04 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -635,9 +635,9 @@ void Wallet::saveTransaction(const Tx &tx) } AES256CBCEncrypt crypto(&m_encryptionKey[0], &m_encryptionIR[0], true); - auto &pool = Streaming::pool(data.size() + AES_BLOCKSIZE); - int size = crypto.encrypt(data.begin(), data.size(), pool.data()); - data = pool.commit(size); + auto pool = Streaming::pool(data.size() + AES_BLOCKSIZE); + int size = crypto.encrypt(data.begin(), data.size(), pool->data()); + data = pool->commit(size); } QString filename = QString::fromStdString(txid.ToString()); QString localdir = dir.arg(filename.left(2)); @@ -654,7 +654,7 @@ void Wallet::saveTransaction(const Tx &tx) } } -Tx Wallet::loadTransaction(const uint256 &txid, Streaming::BufferPool &pool) const +Tx Wallet::loadTransaction(const uint256 &txid, const std::shared_ptr &pool) const { uint256 filename(txid); if (m_encryptionLevel == FullyEncrypted) { @@ -671,17 +671,17 @@ Tx Wallet::loadTransaction(const uint256 &txid, Streaming::BufferPool &pool) con QFile reader(path); if (reader.open(QIODevice::ReadOnly)) { int txSize = reader.size(); - pool.reserve(txSize); - reader.read(pool.begin(), txSize); + pool->reserve(txSize); + reader.read(pool->begin(), txSize); if (m_encryptionLevel == FullyEncrypted) { // decrypt the tx assert(m_haveEncryptionKey); // checked above AES256CBCDecrypt crypto(&m_encryptionKey[0], &m_encryptionIR[0], true); - auto encryptedTx = pool.commit(txSize); - pool.reserve(txSize); - txSize = crypto.decrypt(encryptedTx.begin(), txSize, pool.data()); + auto encryptedTx = pool->commit(txSize); + pool->reserve(txSize); + txSize = crypto.decrypt(encryptedTx.begin(), txSize, pool->data()); } - return Tx(pool.commit(txSize)); + return Tx(pool->commit(txSize)); } // return empty tx return Tx(); @@ -834,11 +834,11 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons const auto mnemonicBytes = mnemonic.toUtf8(); const auto pwdBytes = pwd.toUtf8(); // convert to constBuf containers. - auto &pool = Streaming::pool(mnemonicBytes.size() + pwdBytes.size()); - pool.write(mnemonicBytes.constData(), mnemonicBytes.size()); - auto mnemonicBuf = pool.commit(); - pool.write(pwdBytes.constData(), pwdBytes.size()); - auto pwdBuf = pool.commit(); + auto pool = Streaming::pool(mnemonicBytes.size() + pwdBytes.size()); + pool->write(mnemonicBytes.constData(), mnemonicBytes.size()); + auto mnemonicBuf = pool->commit(); + pool->write(pwdBytes.constData(), pwdBytes.size()); + auto pwdBuf = pool->commit(); m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, electrumFormat ? HDMasterKey::ElectrumMnemonic @@ -1531,9 +1531,9 @@ Streaming::ConstBuffer Wallet::readSecrets() const if (!in.is_open()) throw std::runtime_error("Missing secrets.dat"); const auto dataSize = boost::filesystem::file_size(m_basedir / "secrets.dat"); - auto &pool = Streaming::pool(dataSize); - in.read(pool.begin(), dataSize); - return pool.commit(dataSize); + auto pool = Streaming::pool(dataSize); + in.read(pool->begin(), dataSize); + return pool->commit(dataSize); } void Wallet::loadSecrets() @@ -1773,10 +1773,10 @@ void Wallet::saveSecrets() std::filesystem::create_directories(m_basedir.string()); if (m_encryptionLevel == FullyEncrypted) { - auto &pool = Streaming::pool(data.size() + AES_BLOCKSIZE); + auto pool = Streaming::pool(data.size() + AES_BLOCKSIZE); AES256CBCEncrypt crypto(&m_encryptionKey[0], &m_encryptionIR[0], true); - int size = crypto.encrypt(data.begin(), data.size(), pool.data()); - data = pool.commit(size); + int size = crypto.encrypt(data.begin(), data.size(), pool->data()); + data = pool->commit(size); } const auto basefile = (m_basedir / "secrets.dat").string(); std::ofstream outFile(basefile + "~"); @@ -2092,7 +2092,7 @@ void Wallet::saveWallet() saveFileSize += 110 + wtx.inputToWTX.size() * 22 + wtx.outputs.size() * 30; } - Streaming::BufferPool pool(saveFileSize); + auto pool = std::make_shared(saveFileSize); Streaming::MessageBuilder builder(pool); for (const auto &item : m_walletTransactions) { builder.add(WalletPriv::Index, item.first); @@ -2139,9 +2139,9 @@ void Wallet::saveWallet() std::filesystem::create_directories(m_basedir.string()); if (m_encryptionLevel == FullyEncrypted) { AES256CBCEncrypt crypto(&m_encryptionKey[0], &m_encryptionIR[0], true); - pool.reserve(data.size() + AES_BLOCKSIZE); - int size = crypto.encrypt(data.begin(), data.size(), pool.data()); - data = pool.commit(size); + pool->reserve(data.size() + AES_BLOCKSIZE); + int size = crypto.encrypt(data.begin(), data.size(), pool->data()); + data = pool->commit(size); } const auto basefile = (m_basedir / "wallet.dat").string(); diff --git a/src/Wallet.h b/src/Wallet.h index ba34c77..512b7a1 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -523,7 +523,7 @@ private: * * pool.reserve() is called by this method with the actual tx size. */ - Tx loadTransaction(const uint256 &txid, Streaming::BufferPool &pool) const; + Tx loadTransaction(const uint256 &txid, const std::shared_ptr &pool) const; struct InsertBeforeData { diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 8e756e4..21dfc76 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -138,17 +138,17 @@ void Wallet::setEncryption(EncryptionLevel level, const QString &password) logDebug(LOG_WALLET) << "Missing transaction file"; continue; } - auto &pool = Streaming::pool(reader.size()); - reader.read(pool.begin(), reader.size()); + auto pool = Streaming::pool(reader.size()); + reader.read(pool->begin(), reader.size()); reader.close(); - auto orig = pool.commit(reader.size()); + auto orig = pool->commit(reader.size()); if (crypto.get() == nullptr) crypto.reset(new AES256CBCEncrypt(&m_encryptionKey[0], &m_encryptionIR[0], true)); - pool.reserve(orig.size()); - auto newSize = crypto->encrypt(orig.begin(), orig.size(), pool.data()); + pool->reserve(orig.size()); + auto newSize = crypto->encrypt(orig.begin(), orig.size(), pool->data()); assert(newSize > 0); - auto newFile = pool.commit(newSize); + auto newFile = pool->commit(newSize); uint256 txid(i->second.txid); for (int j = 0; j < 32; ++j) { diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index d17a103..8956d90 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -39,7 +39,7 @@ public: broadcastTxFinished(txIndex, false); } - Tx loadTx(const uint256 &txHash, Streaming::BufferPool &pool) const { + Tx loadTx(const uint256 &txHash, const std::shared_ptr &pool) const { return loadTransaction(txHash, pool); } }; @@ -50,13 +50,13 @@ void TestWallet::transactionOrdering() uint256 prevTxId = uint256S("0x12830924807308721309128309128"); b1.appendInput(prevTxId, 0); b1.appendOutput(10000); - Streaming::BufferPool pool; - Tx t1 = b1.createTransaction(&pool); + auto pool = std::make_shared(); + Tx t1 = b1.createTransaction(pool); TransactionBuilder b2; b2.appendInput(t1.createHash(), 0); // t2 spends an output of t1 b2.appendOutput(10000); - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); { @@ -90,8 +90,8 @@ void TestWallet::addingTransactions() b1.appendInput(prevTxId, 0); b1.appendOutput(1000000); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Streaming::BufferPool pool; - Tx t1 = b1.createTransaction(&pool); + auto pool = std::make_shared(); + Tx t1 = b1.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -128,7 +128,7 @@ void TestWallet::addingTransactions() b2.pushOutputPay2Address(id); int64_t change = -1; - auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(&pool).size(), change); + auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change); QCOMPARE(funding.outputs.size(), 1L); for (auto ref : funding.outputs) { @@ -137,7 +137,7 @@ void TestWallet::addingTransactions() QCOMPARE(wallet->unlockKey(ref).sigType, Wallet::NotUsedYet); b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); } - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 1000000); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -161,14 +161,14 @@ void TestWallet::addingTransactions() TransactionBuilder b3; b3.appendOutput(698700); b3.pushOutputPay2Address(wallet->nextUnusedAddress()); - funding = wallet->findInputsFor(698700, 1, b3.createTransaction(&pool).size(), change); + funding = wallet->findInputsFor(698700, 1, b3.createTransaction(pool).size(), change); QCOMPARE(funding.outputs.size(), 2L); for (auto ref : funding.outputs) { b3.appendInput(wallet->txid(ref), ref.outputIndex()); auto output = wallet->txOutput(ref); b3.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); } - Tx t3 = b3.createTransaction(&pool); + Tx t3 = b3.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 200000 + 500000); @@ -195,7 +195,7 @@ void TestWallet::addingTransactions() } QVERIFY(found); b4.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx t4 = b4.createTransaction(&pool); + Tx t4 = b4.createTransaction(pool); // then add the double spend as confirmed. // This should replace the previous one. @@ -247,8 +247,8 @@ void TestWallet::addingTransactions2() b1.pushOutputPay2Address(wallet->nextUnusedAddress()); b1.appendOutput(5000); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Streaming::BufferPool pool; - Tx tx1 = b1.createTransaction(&pool); + auto pool = std::make_shared(); + Tx tx1 = b1.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -261,14 +261,14 @@ void TestWallet::addingTransactions2() b2.appendInput(tx1.createHash(), 0); b2.appendOutput(999); b2.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx tx2 = b2.createTransaction(&pool); + Tx tx2 = b2.createTransaction(pool); TransactionBuilder b3; b3.appendInput(tx1.createHash(), 1); b3.appendInput(tx2.createHash(), 0); b3.appendOutput(5990); b3.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx tx3 = b3.createTransaction(&pool); + Tx tx3 = b3.createTransaction(pool); wallet->newTransactions(blockId210, 210, {tx3}); // the wallet doesn't know we spent one of its own @@ -294,7 +294,7 @@ void TestWallet::addingTransactions2() void TestWallet::lockingOutputs() { - Streaming::BufferPool pool; + auto pool = std::make_shared(); { // See if locking outputs manually has an effect on findInputs auto wallet = createWallet(); @@ -326,7 +326,7 @@ void TestWallet::lockingOutputs() for (auto ref2 : walletSet.outputs) { try { b1.appendInput(wallet->txid(ref2), ref2.outputIndex()); - b1.pushInputSignature(wallet->unlockKey(ref2).key, pool.commit(100), 1, TransactionBuilder::Schnorr); + b1.pushInputSignature(wallet->unlockKey(ref2).key, pool->commit(100), 1, TransactionBuilder::Schnorr); } catch (const std::exception &e) { logFatal() << e; } @@ -335,7 +335,7 @@ void TestWallet::lockingOutputs() KeyId address; wallet->reserveUnusedAddress(address); b1.pushOutputPay2Address(address); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); wallet->newTransaction(t1); QCOMPARE(wallet->balanceUnconfirmed(), 5000); @@ -377,7 +377,7 @@ void TestWallet::lockingOutputs() for (auto ref : walletSet.outputs) { try { b1.appendInput(wallet->txid(ref), ref.outputIndex()); - b1.pushInputSignature(wallet->unlockKey(ref).key, pool.commit(100), 1, TransactionBuilder::Schnorr); + b1.pushInputSignature(wallet->unlockKey(ref).key, pool->commit(100), 1, TransactionBuilder::Schnorr); } catch (const std::exception &e) { logFatal() << e; } @@ -386,7 +386,7 @@ void TestWallet::lockingOutputs() KeyId address; wallet->reserveUnusedAddress(address); b1.pushOutputPay2Address(address); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); wallet->newTransaction(t1); QCOMPARE(wallet->balanceUnconfirmed(), 700000); @@ -422,8 +422,8 @@ void TestWallet::testSpam() b1.appendInput(prevTxId, 0); b1.appendOutput(1000000); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Streaming::BufferPool pool; - Tx t1 = b1.createTransaction(&pool); + auto pool = std::make_shared(); + Tx t1 = b1.createTransaction(pool); wallet->newTransactions(dummyBlockId, 1, { t1 }); QCOMPARE(wallet->balanceConfirmed(), 1000000); @@ -439,13 +439,13 @@ void TestWallet::testSpam() b2.pushOutputPay2Address(id); int64_t change = -1; - auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(&pool).size(), change); + auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change); for (auto ref : funding.outputs) { b2.appendInput(wallet->txid(ref), ref.outputIndex()); auto output = wallet->txOutput(ref); b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); } - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); wallet->newTransactions(dummyBlockId, 2, { t2 } ); QCOMPARE(wallet->balanceConfirmed(), 200000); // does NOT include the 547 QCOMPARE(wallet->balanceUnconfirmed(), 0); @@ -454,7 +454,7 @@ void TestWallet::testSpam() void TestWallet::saveTransaction() { - Streaming::BufferPool pool; + auto pool = std::make_shared(); // add a simple transaction and see if it saves. { auto wallet = createWallet(); @@ -463,7 +463,7 @@ void TestWallet::saveTransaction() b1.appendInput(prevTxId, 0); b1.appendOutput(1000000); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -489,13 +489,13 @@ void TestWallet::saveTransaction() b2.pushOutputPay2Address(id); int64_t change = -1; - auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(&pool).size(), change); + auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change); QCOMPARE(funding.outputs.size(), 1L); auto ref = funding.outputs.front(); b2.appendInput(wallet->txid(ref), ref.outputIndex()); auto output = wallet->txOutput(ref); b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 1000000); @@ -519,7 +519,7 @@ void TestWallet::saveTransaction() void TestWallet::saveTransaction2() { - Streaming::BufferPool pool; + auto pool = std::make_shared(); // add a simple transaction and see if it saves. { auto wallet = createWallet(); @@ -527,7 +527,7 @@ void TestWallet::saveTransaction2() b1.appendInput(uint256(), 0); // coinbase b1.appendOutput(50); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); std::deque list; list.push_back(t1); wallet->newTransactions(dummyBlockId, 10, list); @@ -539,7 +539,7 @@ void TestWallet::saveTransaction2() b2.appendInput(uint256(), 0); // coinbase b2.appendOutput(51); b2.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); list[0] = t2; wallet->newTransactions(dummyBlockId, 50, list); QCOMPARE(wallet->balanceConfirmed(), 0); @@ -553,7 +553,7 @@ void TestWallet::saveTransaction2() b3.appendOutput(10); KeyId id("66666666666666666666"); b3.pushOutputPay2Address(id); - Tx t3 = b3.createTransaction(&pool); + Tx t3 = b3.createTransaction(pool); list[0] = t3; wallet->newTransactions(dummyBlockId, 140, list); QCOMPARE(wallet->balanceConfirmed(), 40); @@ -592,7 +592,7 @@ void TestWallet::unconfirmed() * Then spend one with a transaction that places new coins in the wallet. * Make sure that after reload the balance is proper. */ - Streaming::BufferPool pool; + auto pool = std::make_shared(); { auto wallet = createWallet(); TransactionBuilder b1; @@ -600,7 +600,7 @@ void TestWallet::unconfirmed() b1.appendInput(prevTxId, 0); b1.appendOutput(1000000); b1.pushOutputPay2Address(wallet->nextUnusedAddress()); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 0); QCOMPARE(wallet->balanceUnconfirmed(), 0); QCOMPARE(wallet->balanceImmature(), 0); @@ -624,13 +624,13 @@ void TestWallet::unconfirmed() b2.pushOutputPay2Address(wallet->nextUnusedAddress()); int64_t change = -1; - auto funding = wallet->findInputsFor(900000, 1, b2.createTransaction(&pool).size(), change); + auto funding = wallet->findInputsFor(900000, 1, b2.createTransaction(pool).size(), change); QCOMPARE(funding.outputs.size(), 1L); auto ref = funding.outputs.front(); b2.appendInput(wallet->txid(ref), ref.outputIndex()); auto output = wallet->txOutput(ref); b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA); - Tx t2 = b2.createTransaction(&pool); + Tx t2 = b2.createTransaction(pool); QCOMPARE(wallet->balanceConfirmed(), 1000000); QCOMPARE(wallet->balanceUnconfirmed(), 0); @@ -725,7 +725,7 @@ void TestWallet::hierarchicallyDeterministic() void TestWallet::rejectTx() { - Streaming::BufferPool pool; + auto pool = std::make_shared(); { auto wallet = createWallet(); wallet->addTestTransactions(); @@ -735,13 +735,13 @@ void TestWallet::rejectTx() TransactionBuilder b1; for (auto ref : walletSet.outputs) { b1.appendInput(wallet->txid(ref), ref.outputIndex()); - b1.pushInputSignature(wallet->unlockKey(ref).key, pool.commit(100), 1, TransactionBuilder::Schnorr); + b1.pushInputSignature(wallet->unlockKey(ref).key, pool->commit(100), 1, TransactionBuilder::Schnorr); } b1.appendOutput(5000); // 9000000 was available, so the fee is enormous. But this makes it easy to test. KeyId address; wallet->reserveUnusedAddress(address); b1.pushOutputPay2Address(address); - Tx t1 = b1.createTransaction(&pool); + Tx t1 = b1.createTransaction(pool); wallet->newTransaction(t1); QCOMPARE(wallet->balanceUnconfirmed(), 5000); // simple check to know its been accepted @@ -858,7 +858,7 @@ void TestWallet::testEncryption2() const QString PWD("Hello this is a password"); uint32_t seed = 0; // the seed is stored outside of the wallet. Tx theTx; - Streaming::BufferPool pool; + auto pool = std::make_shared(); { auto wallet = createWallet(); QCOMPARE(wallet->encryptionSeed(), 0); -- 2.54.0 From 6f9f17a46b60524274da9e6be91069f4603447f0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 22 Dec 2023 18:52:05 +0100 Subject: [PATCH 0733/1428] Make scanning simple QRs better. This specifically allows pasting and scanning of bitcoincash addresses without the 'bitcoincash' prefix. Additionally this cleans up the QRScanner API a little and merges two methods. Last, at popular request, this makes showing the address on the confirmation screen default to be on. This allows people to actually verify the address they pay to. Except when paying to a BIP70 payment because that is practically speaking a system that avoids talking about addresses in the first place. No point in trying to verify the actual address THERE. --- guis/mobile/PayWithQR.qml | 4 ++++ guis/mobile/QRScannerOverlay.qml | 2 +- src/CameraController.cpp | 12 +++++++++--- src/CameraController.h | 2 +- src/Payment.cpp | 16 ++++++++++++++-- src/Payment.h | 7 ++++++- src/PaymentProtocol.h | 9 +++++++++ src/PaymentProtocol_p.h | 7 +++++++ src/QRScanner.cpp | 18 ++++++------------ src/QRScanner.h | 10 ++++------ 10 files changed, 61 insertions(+), 26 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index d9c5dd7..8cea5a1 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -37,6 +37,7 @@ Page { } property QtObject showTargetAddress: QQC2.Action { checkable: true + checked: true text: qsTr("Show Address", "to show a bitcoincash address") } property QtObject editPrice: QQC2.Action { @@ -81,6 +82,9 @@ Page { if (resultSource === QRScanner.Clipboard) payment.instaPay = false; + // check if payment has been done from 'simple' payment-protocol + showTargetAddress.checked = payment.simpleAddressTarget; + // Take the entire QR-url and let the Payment object parse it. // this updates things like amount, comment and indeed address. let success = payment.pasteTargetAddress(rc); diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 4f13785..9e4e4ac 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -122,7 +122,7 @@ FocusScope { id: pasteButton source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); text: qsTr("Paste") - onClicked: pasteFeedback.visible = !CameraController.importScanData(cbh.text); + onClicked: pasteFeedback.visible = !CameraController.pasteData(cbh.text); } ClipboardHelper { diff --git a/src/CameraController.cpp b/src/CameraController.cpp index f4dd67a..e07f92c 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -352,6 +352,11 @@ void QRScanningThread::run() text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); return; } + if (bytes.size() > 40 && bytes.size() < 45 && (bytes[0] == 'q' || bytes[0] == 'p')) { + // possibly a raw bitcoin cash address. + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + return; + } break; case QRScanner::PaymentDetailsTestnet: assert(false); // TODO @@ -619,7 +624,7 @@ void CameraController::setTorchEnabled(bool on) emit torchEnabledChanged(); } -bool CameraController::importScanData(const QString &string) +bool CameraController::pasteData(const QString &string) { if (d->scanRequest == nullptr) return false; @@ -627,7 +632,8 @@ bool CameraController::importScanData(const QString &string) return false; if (!string.isEmpty()) { - d->scanRequest->setScanResult(string, QRScanner::Clipboard); + d->scanRequest->finishedScan(string, QRScanner::Clipboard); + d->scanRequest = nullptr; // stop camera d->cameraStarted = false; emit cameraActiveChanged(); @@ -702,7 +708,7 @@ void CameraController::qrScanFinished() QObject::disconnect(d->videoSink, nullptr, this, nullptr); if (d->scanRequest) { - d->scanRequest->finishedScan(resultText); + d->scanRequest->finishedScan(resultText, QRScanner::Camera); d->scanRequest = nullptr; } QCamera *cam = qobject_cast(d->camera); diff --git a/src/CameraController.h b/src/CameraController.h index b1c82ae..f6ead41 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -59,7 +59,7 @@ public: /** * Try to complete the current scan request by instead taking the \a string. */ - Q_INVOKABLE bool importScanData(const QString &string); + Q_INVOKABLE bool pasteData(const QString &string); void setCamera(QObject *object); QObject *camera() const; diff --git a/src/Payment.cpp b/src/Payment.cpp index 62ec8f9..4f37a22 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -81,10 +81,17 @@ void Payment::setPaymentAmountFiat(int amount) bool Payment::pasteTargetAddress(const QString &address) { - if (m_paymentDetails.size() == 1) { // payment protocols only allowed on empty tx + // payment protocols only allowed on empty tx + const bool possiblyPaymentProtocol = m_paymentDetails.size() == 1; + if (possiblyPaymentProtocol) { auto protocol = PaymentProtocol::create(this, address, m_paymentDetails.at(0)); - return protocol; + if (protocol) { + m_simpleAddressTarget = protocol->simpleAddressTarget(); + emit simpleAddressTargetChanged(); + return true; + } } + auto out = soleOut(); out->setAddress(address.trimmed()); return !out->formattedTarget().isEmpty(); @@ -437,6 +444,11 @@ void Payment::addDetail(PaymentDetail *detail) doAutoPrepare(); } +bool Payment::simpleAddressTarget() const +{ + return m_simpleAddressTarget; +} + Tx Payment::tx() const { return m_tx; diff --git a/src/Payment.h b/src/Payment.h index 14ef2f5..a5596c8 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -59,6 +59,7 @@ class Payment : public QObject Q_PROPERTY(bool walletNeedsPin READ walletNeedsPin NOTIFY walletPinChanged) Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) Q_PROPERTY(bool instaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY allowInstaPayChanged) + Q_PROPERTY(bool simpleAddressTarget READ simpleAddressTarget NOTIFY simpleAddressTargetChanged FINAL) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared @@ -256,6 +257,9 @@ public: /// Bypass the broadcast mechanism and mark the transaction as received. void confirmReceivedOk(); + bool simpleAddressTarget() const; + void setSimpleAddressTarget(bool newSimpleAddressTarget); + private slots: void sentToPeer(); void txRejected(short reason, const QString &message); @@ -281,7 +285,7 @@ signals: void autoPrepareChanged(); void allowInstaPayChanged(); void warningsChanged(); - + void simpleAddressTargetChanged(); void approvedByUser(); private: @@ -303,6 +307,7 @@ private: bool m_txBroadcastStarted; bool m_preferSchnorr; bool m_allowInstaPay = false; + bool m_simpleAddressTarget = true; // only 'advanced' payment protocols set this to false Tx m_tx; int m_fee; // in sats per byte int m_assignedFee; diff --git a/src/PaymentProtocol.h b/src/PaymentProtocol.h index c9ae61e..5b0017f 100644 --- a/src/PaymentProtocol.h +++ b/src/PaymentProtocol.h @@ -30,6 +30,15 @@ public: static PaymentProtocol* create(Payment *target, const QString &uri, QObject *parent); + /** + * A payment protocol decides if the target is a simple address or not. + * If it is, then the UI will default to show the address we are paying to + * as that helps end-users to verify they are paying to the right person. + * More complex protocols probably want to turn this off as the address + * idea is more hidden for them. + */ + virtual bool simpleAddressTarget() const = 0; + protected: virtual void setUri(const QString &uri) = 0; void finished(); diff --git a/src/PaymentProtocol_p.h b/src/PaymentProtocol_p.h index 6701506..83b4c98 100644 --- a/src/PaymentProtocol_p.h +++ b/src/PaymentProtocol_p.h @@ -34,6 +34,9 @@ public: explicit PaymentProtocolBip21(Payment *payment, QObject *parent); void setUri(const QString &uri) override; + bool simpleAddressTarget() const override { + return true; + } }; class PaymentProtocolBip70 : public PaymentProtocol @@ -44,6 +47,10 @@ public: void setUri(const QString &uri) override; + bool simpleAddressTarget() const override { + return false; + } + private slots: void fetchedRequest(); void sentTransaction(); diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index 8a1b431..e832d40 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -58,9 +58,13 @@ void QRScanner::setScanType(ScanType type) emit scanTypeChanged(); } -void QRScanner::finishedScan(const QString &resultString) +void QRScanner::finishedScan(const QString &result, ResultSource source) { - setScanResult(resultString); + if (m_scanResult == result || result.isEmpty()) + return; + m_scanResult = result; + m_resultSource = source; + emit scanResultChanged(); emit finished(); setIsScanning(false); } @@ -70,16 +74,6 @@ QString QRScanner::scanResult() const return m_scanResult; } -void QRScanner::setScanResult(const QString &result, ResultSource source) -{ - if (m_scanResult == result || result.isEmpty()) - return; - m_scanResult = result; - m_resultSource = source; - emit scanResultChanged(); - setIsScanning(false); -} - void QRScanner::resetScanResult() { if (!m_scanResult.isEmpty()) { diff --git a/src/QRScanner.h b/src/QRScanner.h index 3b658a4..23cac74 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -26,7 +26,7 @@ class QRScanner : public QObject { Q_OBJECT Q_PROPERTY(ScanType scanType READ scanType WRITE setScanType NOTIFY scanTypeChanged REQUIRED) - Q_PROPERTY(QString scanResult READ scanResult WRITE setScanResult RESET resetScanResult NOTIFY scanResultChanged) + Q_PROPERTY(QString scanResult READ scanResult NOTIFY scanResultChanged) Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) @@ -46,18 +46,16 @@ public: ScanType scanType() const; void setScanType(ScanType type); - - /// Notice that resultString may be empty if we didn't scan any valid QR - void finishedScan(const QString &resultString); - enum ResultSource { Camera, Clipboard }; Q_ENUM(ResultSource) + /// Notice that resultString may be empty if we didn't scan any valid QR + void finishedScan(const QString &resultString, ResultSource source); + QString scanResult() const; - void setScanResult(const QString &result, ResultSource source = Camera); void resetScanResult(); bool autostart() const; -- 2.54.0 From 3115f1527de5a9af8ed1f793c687f29c8d60fccb Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 30 Dec 2023 10:41:00 +0100 Subject: [PATCH 0734/1428] Make indents more readable --- guis/desktop/NewAccountImportAccount.qml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 605247b..c3245b2 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -27,10 +27,14 @@ GridLayout { rowSpacing: 10 property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey || ((typedData === Wallet.CorrectMnemonic - || typedData === Wallet.ElectrumMnemonic) - && derivationPath.derivationOk); - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo || typedData === Wallet.ElectrumMnemonic; + property bool finished: typedData === Wallet.PrivateKey + || ((typedData === Wallet.CorrectMnemonic + || typedData === Wallet.ElectrumMnemonic) + && derivationPath.derivationOk); + property bool isMnemonic: typedData === Wallet.CorrectMnemonic + || typedData === Wallet.PartialMnemonic + || typedData === Wallet.PartialMnemonicWithTypo + || typedData === Wallet.ElectrumMnemonic; property bool isElectrumMnemonic: typedData === Wallet.ElectrumMnemonic property bool isPrivateKey: typedData === Wallet.PrivateKey -- 2.54.0 From b0a0528b93bbfc950e0335cf6241a6ca40b531e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 30 Dec 2023 10:41:25 +0100 Subject: [PATCH 0735/1428] Reviewed the checkbox --- guis/desktop/NewAccountImportAccount.qml | 4 ++-- guis/mobile/ImportWalletPage.qml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index c3245b2..056a045 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -148,8 +148,8 @@ GridLayout { } Flowee.CheckBox { id: forceElectrum - text: qsTr("Force Electrum"); - toolTipText: qsTr("When enabled, the seed phrase specified will be force-interpreted as an Electrum and/or Electron Cash phrase.") + text: qsTr("Old Electrum Phrase"); + toolTipText: qsTr("When Electrum detection fails, and you are sure it was created in that wallet, enable this option.") checked: importAccount.isElectrumMnemonic visible: importAccount.isMnemonic enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 26207d8..4b690f0 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -126,8 +126,8 @@ Page { } Flowee.CheckBox { id: forceElectrum - text: qsTr("Force Electrum"); - toolTipText: qsTr("When enabled, the seed phrase specified will be force-interpreted as an Electrum and/or Electron Cash phrase.") + text: qsTr("Old Electrum Phrase"); + toolTipText: qsTr("When Electrum detection fails, and you are sure it was created in that wallet, enable this option."); checked: importAccount.isElectrumMnemonic visible: importAccount.isMnemonic enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic -- 2.54.0 From 1ca77a427a6a9d838a44664e394ce3a78e72cd5b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 3 Jan 2024 19:44:30 +0100 Subject: [PATCH 0736/1428] Import translations from crowdin. This adds a new language: Hausa --- CMakeLists.txt | 5 + translations/desktop-i18n.qrc | 2 + translations/floweepay-common_de.ts | 32 +- translations/floweepay-common_en.ts | 32 +- translations/floweepay-common_es.ts | 48 +- translations/floweepay-common_ha.ts | 451 +++++++ translations/floweepay-common_nl.ts | 32 +- translations/floweepay-common_pl.ts | 32 +- translations/floweepay-common_pt.ts | 58 +- translations/floweepay-desktop_de.ts | 69 +- translations/floweepay-desktop_en.ts | 69 +- translations/floweepay-desktop_es.ts | 69 +- translations/floweepay-desktop_ha.ts | 1177 ++++++++++++++++++ translations/floweepay-desktop_nl.ts | 69 +- translations/floweepay-desktop_pl.ts | 69 +- translations/floweepay-desktop_pt.ts | 73 +- translations/floweepay-mobile_de.ts | 182 +-- translations/floweepay-mobile_en.ts | 182 +-- translations/floweepay-mobile_es.ts | 184 +-- translations/floweepay-mobile_ha.ts | 1180 +++++++++++++++++++ translations/floweepay-mobile_nl.ts | 180 +-- translations/floweepay-mobile_pl.ts | 184 +-- translations/floweepay-mobile_pt.ts | 182 +-- translations/mobile-i18n.qrc | 4 + translations/module-build-transaction_de.ts | 45 +- translations/module-build-transaction_en.ts | 45 +- translations/module-build-transaction_es.ts | 45 +- translations/module-build-transaction_ha.ts | 174 +++ translations/module-build-transaction_nl.ts | 45 +- translations/module-build-transaction_pl.ts | 45 +- translations/module-build-transaction_pt.ts | 47 +- translations/module-peers-view_ha.ts | 66 ++ translations/module-peers-view_pt.ts | 2 +- 33 files changed, 4261 insertions(+), 818 deletions(-) create mode 100644 translations/floweepay-common_ha.ts create mode 100644 translations/floweepay-desktop_ha.ts create mode 100644 translations/floweepay-mobile_ha.ts create mode 100644 translations/module-build-transaction_ha.ts create mode 100644 translations/module-peers-view_ha.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index ca76b5a..feeef30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,7 @@ if(NOT ANDROID) translations/floweepay-desktop_de.ts translations/floweepay-desktop_es.ts translations/floweepay-desktop_pt.ts + translations/floweepay-desktop_ha.ts translations/floweepay-common_en.ts translations/floweepay-common_nl.ts @@ -92,6 +93,7 @@ if(NOT ANDROID) translations/floweepay-common_de.ts translations/floweepay-common_es.ts translations/floweepay-common_pt.ts + translations/floweepay-common_ha.ts translations/floweepay-mobile_en.ts translations/floweepay-mobile_nl.ts @@ -99,6 +101,7 @@ if(NOT ANDROID) translations/floweepay-mobile_de.ts translations/floweepay-mobile_es.ts translations/floweepay-mobile_pt.ts + translations/floweepay-mobile_ha.ts translations/module-build-transaction_en.ts translations/module-build-transaction_nl.ts @@ -106,6 +109,7 @@ if(NOT ANDROID) translations/module-build-transaction_de.ts translations/module-build-transaction_es.ts translations/module-build-transaction_pt.ts + translations/module-build-transaction_ha.ts translations/module-peers-view_en.ts translations/module-peers-view_nl.ts @@ -113,6 +117,7 @@ if(NOT ANDROID) translations/module-peers-view_de.ts translations/module-peers-view_es.ts translations/module-peers-view_pt.ts + translations/module-peers-view_ha.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/desktop-i18n.qrc b/translations/desktop-i18n.qrc index d8a6877..34b3c27 100644 --- a/translations/desktop-i18n.qrc +++ b/translations/desktop-i18n.qrc @@ -6,11 +6,13 @@ floweepay-desktop_de.qm floweepay-desktop_es.qm floweepay-desktop_pt.qm + floweepay-desktop_ha.qm floweepay-common_nl.qm floweepay-common_pl.qm floweepay-common_en.qm floweepay-common_de.qm floweepay-common_pt.qm floweepay-common_es.qm + floweepay-common_ha.qm diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index bf65977..8d40fda 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -135,30 +135,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -167,13 +167,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -266,32 +266,32 @@ Payment - + Invalid PIN Ungültige PIN - + Not enough funds selected for fees Nicht genug Guthaben für Gebühren ausgewählt - + Not enough funds in wallet to make payment! Nicht genügend Guthaben in der Geldbörse, um eine Zahlung zu machen! - + Transaction too large. Amount selected needs too many coins. Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. - + Request received over insecure channel. Anyone could have altered it! Anfrage über unsicheren Kanal empfangen. Jeder hätte sie verändern können! - + Download of payment request failed. Download der Zahlungsanfrage fehlgeschlagen. @@ -305,7 +305,7 @@ - + Payment request expired Zahlungsanfrage abgelaufen diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index 66a081d..c11e88f 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -135,30 +135,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -167,13 +167,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -266,32 +266,32 @@ Payment - + Invalid PIN Invalid PIN - + Not enough funds selected for fees Not enough funds selected for fees - + Not enough funds in wallet to make payment! Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -305,7 +305,7 @@ - + Payment request expired Payment request expired diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 6aa0957..4fdc1b6 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -26,7 +26,7 @@ Behind: %1 days - Retraso: %1 día[s] + Retraso: %1 día Retraso: %1 días @@ -44,7 +44,7 @@ Still %1 hours behind - Todavía %1 hora[s] de retraso + Todavía %1 hora de retraso Todavía %1 horas de retraso @@ -135,49 +135,49 @@ FloweePay - + Initial Wallet Monedero inicial - - + + Today Hoy - - + + Yesterday Ayer - + Now timestamp Ahora - + %1 minutes ago relative time stamp - Hace %1 minuto[s] + Hace %1 minuto Hace %1 minutos - + ½ hour ago timestamp Hace ½ hora - + %1 hours ago timestamp - Hace %1 hora[s] + Hace %1 hora Hace %1 horas @@ -266,32 +266,32 @@ Payment - + Invalid PIN PIN inválido - + Not enough funds selected for fees No se han seleccionado suficientes fondos para cubrir la comisión - + Not enough funds in wallet to make payment! ¡El monedero no posee suficientes fondos para procesar el pago! - + Transaction too large. Amount selected needs too many coins. Transacción demasiado larga. La cantidad seleccionada necesita demasiadas monedas. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -305,7 +305,7 @@ - + Payment request expired Payment request expired @@ -345,7 +345,7 @@ %1 hours age, like: hours old - %1 hora[s] + %1 hora %1 horas @@ -354,7 +354,7 @@ %1 days age, like: days old - %1 día[s] + %1 día %1 días @@ -363,7 +363,7 @@ %1 weeks age, like: weeks old - %1 semana[s] + %1 semana %1 semanas @@ -372,7 +372,7 @@ %1 months age, like: months old - %1 mes[es] + %1 mes %1 meses diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts new file mode 100644 index 0000000..49412c6 --- /dev/null +++ b/translations/floweepay-common_ha.ts @@ -0,0 +1,451 @@ + + + + + AccountInfo + + + Offline + Baya kan na'ura + + + + Wallet: Up to date + Wallet: Na zamani + + + + Behind: %1 weeks, %2 days + counter on weeks + + Bayan: %1 makonni, %2 kwanaki + Bayan: %1 makonni, %2 kwanaki + + + + + Behind: %1 days + + Bayan: %1 kwanaki + Bayan: %1 kwanaki + + + + + Up to date + Na zamani + + + + Updating + Sabontawa + + + + Still %1 hours behind + + Har yanzu %1 a baya + Har yanzu %1 a baya + + + + + AccountTypeLabel + + + This wallet is a single-address wallet. + Wannan asusun jakar adireshi ɗaya ce. + + + + This wallet is based on a HD seed-phrase + Wannan asusun ya dogara ne akan jimlar iri na HD + + + + This wallet is a simple multiple-address wallet. + Wannan asusun ɗin adireshi ne mai sauƙi da yawa. + + + + AddressInfoWidget + + + self + payment to self + Kai + + + + BroadcastFeedback + + + Sending Payment + Aika Biyan Kuɗi + + + + Payment Sent + An aika Biya + + + + Failed + Ba a yi nasara ba + + + + Transaction rejected by network + hanyar sadarwa ta ƙi ciniki + + + + Payment has been sent to: + An aika biyan kuɗi zuwa: + + + + Copied address to clipboard + Adireshin da aka kwafi zuwa allo + + + + Opening Website + Buɗe Yanar Gizon + + + + Add a personal note + Ƙara bayanin kula na sirri + + + + Close + Kulle + + + + CFIcon + + + Coin has been fused for increased anonymity + An haɗa tsabar kuɗi don ƙara ɓoye suna + + + + FloweePay + + + Initial Wallet + Asusu na farko + + + + + Today + Yau + + + + + Yesterday + Jiya + + + + Now + timestamp + Yanzu + + + + %1 minutes ago + relative time stamp + + Minti daya da yawuce + Minti 1 da ya wuce + + + + + ½ hour ago + timestamp + Rabin awa da ya wuce + + + + %1 hours ago + timestamp + + Awa daya da ya wuce + Awa 1 da ta wuce + + + + + LabelWithClipboard + + + Copy + Kwafi + + + + MenuModel + + + Explore + Bincika + + + + Settings + Saituna + + + + Security + Tsaro + + + + About + Game da + + + + Wallets + Asusu + + + + NotificationManager + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 + + + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) toshe ma'adinai: %1 + + + + Mute + Shiru + + + + New Transactions + dialog-title + + Sabbin Ma'amaloli + Sabbin Ma'amaloli + + + + + %1 new transactions across %2 wallets found (%3) + sabbin ma'amaloli a cikin asusun da aka samu + + + + A payment of %1 has been sent + An aika biyan kuɗi na %1 + + + + %1 new transactions found (%2) + + sababbin ma'amaloli akasamu + sababbin ma'amaloli aka samu + + + + + Payment + + + Invalid PIN + Makulli Mara inganci + + + + Not enough funds selected for fees + Babu isassun kuɗin da aka zaɓa don kuɗi + + + + Not enough funds in wallet to make payment! + Babu isassun kuɗi a cikin asusu don biyan kuɗi! + + + + Transaction too large. Amount selected needs too many coins. + Ma'amala yayi girma sosai. Adadin da aka zaɓa yana buƙatar tsabar kuɗi da yawa. + + + + Request received over insecure channel. Anyone could have altered it! + An karɓi buƙatar akan tashar da ba ta da tsaro. Kowa zai iya canza shi! + + + + Download of payment request failed. + Sauke buƙatar biyan kuɗi ya gagara. + + + + PaymentProtocolBip70 + + + Payment request unreadable + Ba za a iya karanta buƙatar biyan kuɗi ba + + + + + Payment request expired + Neman biyan kuɗi ya ƙare + + + + QRWidget + + + Copied to clipboard + An kwafo zuwa allo + + + + Wallet + + + + Change #%1 + Chanji + + + + + Main #%1 + Mafarin + + + + WalletCoinsModel + + + Unconfirmed + Ba a tabbatar ba + + + + %1 hours + age, like: hours old + + Awa + Awowi + + + + + %1 days + age, like: days old + + Ranaku + Ranaku + + + + + %1 weeks + age, like: weeks old + + makonni + Makonni + + + + + %1 months + age, like: months old + + Watanni + Watanni + + + + + Change #%1 + Chanji daya + + + + WalletHistoryModel + + + Today + Yau + + + + Yesterday + Jiya + + + + Earlier this week + A farkon wannan makon + + + + This week + Wannan Makon + + + + Earlier this month + A farkon wannan watan + + + + WalletSecretsView + + + Explanation + Bayani + + + + Coins a / b + a) active coin-count. + b) historical coin-count. + Tsabar kudi a / b +a) ƙidaya tsabar kudi +b) tsabar kudi na tarihi. + + + + + Copy Address + Kwafi Adireshi + + + + Copy Private Key + Kwafi Keɓaɓɓen Maɓalli + + + + Coins: %1 / %2 + Tsabar kudi: %1/%2 + + + + Signed with Schnorr signatures in the past + Sa hannu tare da sa hannun Schnorr a baya + + + diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 28330f9..bc3bd58 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -135,30 +135,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -167,13 +167,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -266,32 +266,32 @@ Payment - + Invalid PIN Ongeldige PIN - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. - + Request received over insecure channel. Anyone could have altered it! Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! - + Download of payment request failed. Downloaden van betalingsverzoek mislukt. @@ -305,7 +305,7 @@ - + Payment request expired Betalingsverzoek verlopen diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 21e4604..bab2e65 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -141,30 +141,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -175,13 +175,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -280,32 +280,32 @@ Payment - + Invalid PIN Nieprawidłowy PIN - + Not enough funds selected for fees Brak środków, by pokryć koszt transakcji - + Not enough funds in wallet to make payment! Niewystarczająca ilość środków w portfelu, aby dokonać płatności! - + Transaction too large. Amount selected needs too many coins. Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. - + Request received over insecure channel. Anyone could have altered it! Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł go zmodyfikować! - + Download of payment request failed. Pobieranie żądania płatności nie powiodło się. @@ -319,7 +319,7 @@ - + Payment request expired Żądanie płatności wygasło diff --git a/translations/floweepay-common_pt.ts b/translations/floweepay-common_pt.ts index a962a9d..a784a6c 100644 --- a/translations/floweepay-common_pt.ts +++ b/translations/floweepay-common_pt.ts @@ -1,6 +1,6 @@ - + AccountInfo @@ -135,50 +135,50 @@ FloweePay - + Initial Wallet Carteira inicial - - + + Today Hoje - - + + Yesterday Ontem - + Now timestamp Agora - + %1 minutes ago relative time stamp - - %1 minuto(s) atrás - %1 minutes ago + + %1 minuto atrás + %1 minutos atrás - + ½ hour ago timestamp Meia hora atrás - + %1 hours ago timestamp - - %1 hora(s) atrás - %1 hours ago + + %1 hora atrás + %1 horas atrás @@ -266,32 +266,32 @@ Payment - + Invalid PIN PIN inválido - + Not enough funds selected for fees Saldo insuficiente para taxas - + Not enough funds in wallet to make payment! Saldo insuficiente para realizar pagamento! - + Transaction too large. Amount selected needs too many coins. Valor muito alto. A quantia selecionada requer moedas demais. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -305,7 +305,7 @@ - + Payment request expired Payment request expired @@ -344,18 +344,18 @@ %1 hours age, like: hours old - - %1 hora(s) - %1 hours + + %1 hora + %1 horas %1 days age, like: days old - - %1 dia(s) - %1 days + + %1 dia + %1 dias diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index fcde05f..1e08e5f 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -130,21 +130,26 @@ + Seed format + Seed format + + + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -280,79 +285,101 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. - + Secret The seed-phrase or private key Geheimnis - + Example: %1 placeholder text Beispiel: %1 - + Name Name - + Private key description of type Privater Schlüssel - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + BIP 39 seed-phrase description of type BIP 39 Seed-Phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unerkanntes Wort - + Import wallet Geldbörse importieren - + Advanced Options Erweiterte Optionen - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Start Height Starthöhe - + Derivation Ableitung - + Alternate phrase Alternative Phrase @@ -920,12 +947,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. WalletEncryptionStatus - + Pin to Pay Pin um zu bezahlen - + Pin to Open Pin zum Öffnen @@ -945,8 +972,8 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - Cash Fusion - Cash Fusion + Fused + Fused diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 29923f8..37d1803 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -130,21 +130,26 @@ + Seed format + Seed format + + + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. @@ -280,79 +285,101 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - + Secret The seed-phrase or private key Secret - + Example: %1 placeholder text Example: %1 - + Name Name - + Private key description of type Private key - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + BIP 39 seed-phrase description of type BIP 39 seed-phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unrecognized word - + Import wallet Import wallet - + Advanced Options Advanced Options - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Start Height Start Height - + Derivation Derivation - + Alternate phrase Alternate phrase @@ -920,12 +947,12 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Pay Pin to Pay - + Pin to Open Pin to Open @@ -945,8 +972,8 @@ Change will come back to the imported key. - Cash Fusion - Cash Fusion + Fused + Fused diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index dc7b562..a5bb29e 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -130,21 +130,26 @@ + Seed format + Seed format + + + Derivation Derivación - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. @@ -280,79 +285,101 @@ Esto asegura que solo una clave privada tendrá que ser respaldada NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. - + Secret The seed-phrase or private key Secreto - + Example: %1 placeholder text Ejemplo: %1 - + Name Nombre - + Private key description of type Llave privada - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + BIP 39 seed-phrase description of type Frase semilla BIP 39 - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Palabra no reconocida - + Import wallet Importar monedero - + Advanced Options Opciones Avanzadas - + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Start Height Altura de inicio - + Derivation Derivación - + Alternate phrase Frase alternativa @@ -920,12 +947,12 @@ El cambio volverá a la clave importada. WalletEncryptionStatus - + Pin to Pay PIN para pagar - + Pin to Open PIN para abrir @@ -945,8 +972,8 @@ El cambio volverá a la clave importada. - Cash Fusion - Fusión de Monedas + Fused + Fused diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts new file mode 100644 index 0000000..3c5945c --- /dev/null +++ b/translations/floweepay-desktop_ha.ts @@ -0,0 +1,1177 @@ + + + + + AccountConfigMenu + + + Details + Bayanai + + + + Unarchive + Cire Takardu + + + + Archive Wallet + Ma'ajiyin taskoki + + + + Make Primary + Haɗin farko + + + + ★ Primary + Mafari + + + + Protect With Pin... + Tsarewa da ɗan makulli... + + + + Open + Open encrypted wallet + Budewa + + + + Close + Close encrypted wallet + Kullewa + + + + AccountDetails + + + Wallet Details + Bayanan asusun ajiya + + + + Name + Suna + + + + Sync Status + Daidaita matsayi + + + + Encryption + Rufewa + + + + Password + Kwadon shiga + + + + Open + Bude + + + + Invalid PIN + Makulli Mara inganci + + + + Include balance in total + Haɗa ma'aunin chanji baki ɗaya + + + + Hide in private mode + Ɓoye a yanayin sirri + + + + Address List + Jerin adireshi + + + + Change Addresses + Chanjin adireshi + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. + + + + Used Addresses + Adireshin da aka yi amfani da shi + + + + Switches between unused and used Bitcoin addresses + Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba + + + + Backup details + Ajiyayyen bayanai + + + + Seed-phrase + Jimlar iri + + + + Seed format + Seed format + + + + Derivation + Samo asali + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. + Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! + + + + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. + Wannan asusun na tsare da almar sirri (pin-to-pay). Don ganin bayanan ciki akwai buƙatar samar da kalmar wucewa. + + + + AccountListItem + + + Last Transaction + Ciniki na ƙarshe + + + + NetView + + + Peers (%1) + + Takwarori (%1) + Peers (%1) + + + + + Address + network address (IP) + Adireshi + + + + Start-height: %1 + Fara-tsawo: %1 + + + + ban-score: %1 + Tsame-ci: %1 + + + + initializing connection + Farawa haɗi + + + + Verifying peer + Tabbatar da tsari + + + + Assigning... + Sanyawa... + + + + Peer for wallet: %1 + Haɗin asusu %1 + + + + Peer for initial wallet + Haɗi na asusun farko + + + + Close + Kulle + + + + NewAccountCreateBasicAccount + + + This creates a new empty wallet with simple multi-address capability + Wannan yana haifar da sabon asusu mara komai tare da sauƙin adireshi da yawa + + + + Name + Suna + + + + Go + Tafi + + + + Advanced Options + Zaɓuɓɓuka na ci gaba + + + + Force Single Address + Tilasta Adireshi Guda Daya + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Lokacin da aka kunna, wannan asusun za'a iyakance shi ga adireshi ɗaya. Wannan yana tabbatar da maɓallin keɓaɓɓen maɓalli ɗaya ne kawai zai buƙaci a yi masa tallafi + + + + NewAccountCreateHDAccount + + + This creates a new empty wallet with smart creation of addresses from a single seed-phrase + Wannan yana haifar da sabon asusu mara komai tare da ƙirƙirar adireshi masu wayo daga jimla iri ɗaya + + + + Name + Suna + + + + Go + Tafi + + + + Advanced Options + Zaɓuɓɓuka na ci gaba + + + + Derivation + Samo asali + + + + NewAccountImportAccount + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Ataimaka a shigar da sirrin asusu don shigo da shi. Wannan na iya zama jimla-jinin ko maɓalli na sirri. + + + + Secret + The seed-phrase or private key + Sirri + + + + Example: %1 + placeholder text + Misali: %1 + + + + Name + Suna + + + + Private key + description of type + Keɓaɓɓen maɓalli + + + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + + BIP 39 seed-phrase + description of type + BIP 39 zuriyar jimla + + + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + + Unrecognized word + Word from the seed-phrases lexicon + Kalmar da ba'a gane ba + + + + Import wallet + Ɗauko asusu + + + + Advanced Options + Zaɓuɓɓuka na ci gaba + + + + Force Single Address + Tilasta Adireshi Guda + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. + + + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + + Start Height + Fara tsawo + + + + Derivation + Samo asali + + + + Alternate phrase + Madadin magana + + + + NewAccountPane + + + New Bitcoin Cash Wallet + Sabon asusun Bitcoin Cash + + + + Basic + Na asali + + + + Private keys based + Property of a wallet + Maɓallai masu zaman kansu + + + + Difficult to backup + Context: wallet type + Wahalar dawo da ajiya + + + + Great for brief usage + Context: wallet type + Mai girma don taƙaitaccen amfani + + + + HD wallet + Asusun HD + + + + Seed-phrase based + Context: wallet type + Tushen jimlar iri + + + + Easy to backup + Context: wallet type + Sauƙi wurin dawo da ajiya + + + + Most compatible + The most compatible wallet type + Mafi dacewa + + + + Import + Shigo da + + + + Imports seed-phrase + Shigo da jumla iri-iri + + + + Imports private key + Shigo da maɓalli na sirri + + + + Basic wallet with private keys + Asusu na asali tare da maɓallan sirri + + + + Seed-phrase wallet + Tushen jimlar iri + + + + Import of an existing wallet + Shigo da asusun da ke akwai + + + + PaymentTweakingPanel + + + Add Detail + Ƙara Dalla-dalla + + + + Coin Selector + Zaɓin Tsabar kudi + + + + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + Domin soke tsoffin zaɓin tsabar kudi waɗanda ake amfani da su don yin mu'amala, zaku iya ƙara 'tsabar kudi r' inda tsabar kudi inda za'a iya gani. + + + + ReceiveTransactionPane + + + Share your QR code or copy address to receive + Bada lambar QR ɗinku ko a kwafi adireshin don karɓa + + + + Encrypted Wallet + Rufaffen asusu + + + + Import Running... + Tsarin Shiga na gudu... + + + + Checking + Dubawa + + + + Transaction high risk + Haɗarin cinikayya + + + + Payment Seen + Anga shaidar biya + + + + Payment Accepted + An Karɓa Biya + + + + Payment Settled + Biya An daidaita + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Biyan nan take ya kasa. Jira tabbaci. (Anga shaida guda biyu da aka kashe) + + + + Description + Bayani + + + + Amount + Adadi + + + + Clear + Share + + + + Done + An gama + + + + SendTransactionPane + + + Confirm delete + Tabbatar da gogewa + + + + Do you really want to delete this detail? + Shin kuna son share wannan dalla-dalla? + + + + Add Destination + Ƙara madakata + + + + Prepare + Shirya + + + + Enter your PIN + Shigar da lambar tsaro + + + + + Warning + Gargaɗi + + + + Payment request warnings: + Gargadin neman biyan kuɗi: + + + + Transaction Details + Cikakken Bayanin Kasuwanci + + + + Not prepared yet + Ba'a shirya ba tukuna + + + + Copy transaction-ID + Kwafi shaidar ma'amala-ID + + + + Fee + Kudin + + + + Transaction size + Girman ciniki + + + + %1 bytes + %1 bytes + + + + Fee per byte + Kudin kowane byte + + + + %1 sat/byte + fee + %1 sat/bytes + + + + Send + Send + + + + Destination + Destination + + + + Max available + The maximum balance available + Max available + + + + %1 to %2 + summary text to pay X-euro to address M + %1 to %2 + + + + Enter Bitcoin Cash Address + Enter Bitcoin Cash Address + + + + Copy Address + Copy Address + + + + Amount + Adadi + + + + Max + Max + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + Continue + Continue + + + + Cancel + Cancel + + + + Coin Selector + Zaɓin Tsabar kudi + + + + Selected %1 %2 in %3 coins + selected 2 BCH in 5 coins + + Selected %1 %2 in %3 coins + Selected %1 %2 in %3 coins + + + + + Total + Number of coins + Total + + + + Needed + Needed + + + + Selected + Selected + + + + Value + Value + + + + Locked coins will never be used for payments. Right-click for menu. + Locked coins will never be used for payments. Right-click for menu. + + + + Age + Age + + + + Unselect All + Cire Zaɓi Duk + + + + Select All + Zaɓi Duk + + + + Unlock coin + Buɗe tsabar kudi + + + + Lock coin + Kulle tsabar kudi + + + + SettingsPane + + + Settings + Saituna + + + + Unit + Naúrar + + + + Show Bitcoin Cash value on Activity page + Nuna ƙimar Bitcoin Cash akan shafin Aiki + + + + Show Block Notifications + Nuna Sanarwa da ta toshe + + + + When a new block is mined, Flowee Pay shows a desktop notification + Lokacin da aka haƙa sabon toshe, Flowee Pay yana nuna sanarwar tebur + + + + Night Mode + Yanayin dare + + + + Private Mode + Yanayin sirri + + + + Hides private wallets while enabled + Ɓoye sirrin asusu yayin kunnawa + + + + Version + Siga + + + + Library Version + Sigar Laburare + + + + Synchronization + Haɗa Aiki tare + + + + Network Status + Matsayin hanyar sadarwa + + + + WalletEncryption + + + Protect your wallet with a password + Protect your wallet with a password + + + + Pin to Pay + Pin to Pay + + + + Protect your funds + pin to pay + Protect your funds + + + + Fully open, except for sending funds + pin to pay + Fully open, except for sending funds + + + + Keeps in sync + pin to pay + Keeps in sync + + + + Pin to Open + Pin to Open + + + + Protect your entire wallet + pin to open + Protect your entire wallet + + + + Balance and history protected + pin to open + Balance and history protected + + + + Requires Pin to view, sync or pay + pin to open + Requires Pin to view, sync or pay + + + + Make "%1" wallet require a pin to pay + Make "%1" wallet require a pin to pay + + + + Make "%1" wallet require a pin to open + Make "%1" wallet require a pin to open + + + + + Wallet already has pin to open applied + Wallet already has pin to open applied + + + + Wallet already has pin to pay applied + Wallet already has pin to pay applied + + + + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + + + + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + + + + Password + Kwadon shiga + + + + Wallet + Wallet + + + + Encrypt + Encrypt + + + + Invalid password to open this wallet + Invalid password to open this wallet + + + + Close + Kulle + + + + Repeat password + Repeat password + + + + Please confirm the password by entering it again + Please confirm the password by entering it again + + + + Typed passwords do not match + Typed passwords do not match + + + + WalletEncryptionStatus + + + Pin to Pay + Pin to Pay + + + + Pin to Open + Pin to Open + + + + (Opened) + Wallet is decrypted + (Opened) + + + + WalletTransaction + + + Miner Reward + Miner Reward + + + + Fused + Fused + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + WalletTransactionDetails + + + Copy transaction-ID + Copy transaction-ID + + + + Status + Status + + + + rejected + rejected + + + + unconfirmed + unconfirmed + + + + %1 confirmations (mined in block %2) + + %1 confirmations (mined in block %2) + %1 confirmations (mined in block %2) + + + + + Copy block height + Copy block height + + + + Fees + Fees + + + + Size + Size + + + + %1 bytes + + %1 bytes + %1 bytes + + + + + Inputs + Inputs + + + + + Copy Address + Copy Address + + + + Outputs + Outputs + + + + main + + + Activity + Activity + + + + Archived wallets do not check for activities. Balance may be out of date. + Archived wallets do not check for activities. Balance may be out of date. + + + + Unarchive + Unarchive + + + + This wallet needs a password to open. + This wallet needs a password to open. + + + + Password: + Password: + + + + Invalid password + Invalid password + + + + Open + Bude + + + + Send + Send + + + + Receive + Receive + + + + Balance + Balance + + + + Main + balance (money), non specified + Main + + + + Unconfirmed + balance (money) + Unconfirmed + + + + Immature + balance (money) + Immature + + + + 1 BCH is: %1 + 1 BCH is: %1 + + + + Network status + Network status + + + + Offline + Offline + + + + Add Bitcoin Cash wallet + Add Bitcoin Cash wallet + + + + Archived wallets [%1] + Arg is wallet count + + Archived wallets [%1] + Archived wallets [%1] + + + + + Preparing... + Preparing... + + + diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 23b080b..6459fd5 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -130,21 +130,26 @@ + Seed format + Herstelzin formaat + + + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -280,79 +285,101 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - + Secret The seed-phrase or private key Geheim - + Example: %1 placeholder text Voorbeeld: %1 - + Name Naam - + Private key description of type Privé-sleutel - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 herstelzin (geïnterpreteerd als Electrum formaat) + + + BIP 39 seed-phrase description of type BIP 39 herstelzin - + + Electrum seed-phrase + description of type + Electron-Cash herstelzin + + + Unrecognized word Word from the seed-phrases lexicon Niet herkend woord - + Import wallet Portemonnee importeren - + Advanced Options Geavanceerde Opties - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + + Old Electrum Phrase + Oude Electrum herstelzin + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Zet deze optie aan als Electrum detectie mislukt en u zeker weet dat je herstelzin in die portemonnee is gemaakt. + + + Start Height Beginhoogte - + Derivation Derivatie - + Alternate phrase Alternatieve zin @@ -920,12 +947,12 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletEncryptionStatus - + Pin to Pay PIN bij betalen - + Pin to Open PIN bij openen @@ -945,8 +972,8 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - Cash Fusion - Cash Fusion + Fused + Gefuseerd diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index bea8c89..648bacf 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -130,21 +130,26 @@ + Seed format + Seed format + + + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -282,78 +287,100 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Wprowadź sekrety portfela, który chcesz importować. Może to być seed lub klucz prywatny. - + Secret The seed-phrase or private key Sekret - + Example: %1 placeholder text Przykład: %1 - + Name Nazwa - + Private key description of type Klucz prywatny - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + BIP 39 seed-phrase description of type Seed BIP 39 - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Nierozpoznawalne słowo - + Import wallet Importuj portfel - + Advanced Options Opcje zaawansowane - + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Start Height Wysokość początkowa - + Derivation Derywacja - + Alternate phrase Alternatywna fraza @@ -923,12 +950,12 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Pay PIN by płacić - + Pin to Open PIN by otworzyć @@ -948,8 +975,8 @@ Change will come back to the imported key. - Cash Fusion - Cash Fusion + Fused + Fused diff --git a/translations/floweepay-desktop_pt.ts b/translations/floweepay-desktop_pt.ts index c824439..d366af7 100644 --- a/translations/floweepay-desktop_pt.ts +++ b/translations/floweepay-desktop_pt.ts @@ -1,6 +1,6 @@ - + AccountConfigMenu @@ -130,21 +130,26 @@ + Seed format + Seed format + + + Derivation Derivação - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, anote as 12 palavras no papel, na ordem correta, com o caminho de derivação. Esta senha permitirá recuperar a sua carteira caso precise. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: Nunca compartilhe sua frase secreta com outros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Esta carteira está protegida por senha (pin-to-pay). Para ver os detalhes de backup, você precisa digitar a senha. @@ -163,7 +168,7 @@ Peers (%1) - (%1) Pares + (%1) Pares Peers (%1) @@ -280,79 +285,101 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - + Secret The seed-phrase or private key Secret - + Example: %1 placeholder text Example: %1 - + Name Name - + Private key description of type Private key - + + BIP 39 seed-phrase (interpreted as Electrum format) + description of type + BIP 39 seed-phrase (interpreted as Electrum format) + + + BIP 39 seed-phrase description of type BIP 39 seed-phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unrecognized word - + Import wallet Import wallet - + Advanced Options Advanced Options - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Start Height Start Height - + Derivation Derivation - + Alternate phrase Alternate phrase @@ -920,12 +947,12 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Pay Pin to Pay - + Pin to Open Pin to Open @@ -945,8 +972,8 @@ Change will come back to the imported key. - Cash Fusion - Cash Fusion + Fused + Fused diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 49b5736..ee1aa15 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -30,17 +30,17 @@ ©️ 2020-2023 Tom Zander und Mitwirkende - + Project Home Projekt-Startseite - + With git repository and issues tracker Mit git Repository und Issue Tracker - + Telegram Telegram @@ -70,7 +70,7 @@ Fused - Fused + Fused @@ -132,88 +132,93 @@ + Seed format + Seed format + + + Starting Height height refers to block-height Starthöhe - + Derivation Path Ableitungspfad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + Wallet keys Geldbörsen-Schlüssel - + Show Index toggle to show numbers Index anzeigen - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between still in use addresses and formerly used, new empty, addresses Wechselt zwischen noch benutzten Adressen und früher genutzten, neuen leeren Adressen - + Addresses and keys Adressen und Schlüssel - + Sync Status Synchronisierungsstatus - + Hide balance in overviews Guthaben in Übersichten ausblenden - + Hide in private mode Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren @@ -327,27 +332,32 @@ Anzeigeeinstellungen - + Font sizing Schriftgröße - + + Dark Theme + Dark Theme + + + Unit Einheit - + Change Currency (%1) Währung ändern (%1) - + Main View Hauptansicht - + Show Bitcoin Cash value Zeige Bitcoin Cash Wert @@ -360,68 +370,90 @@ Geldbörse importieren - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. - + Secret The seed-phrase or private key Geheimnis - + Private key description of type Privater Schlüssel - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 seed-phrase (interpreted as Electrum) + + + BIP 39 seed-phrase description of type BIP 39 Seed-Phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unerkanntes Wort - + Name Name - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Oldest Transaction Älteste Transaktion - + Derivation Ableitung - + Alternate phrase Alternative Phrase - + Create Erstelle @@ -439,17 +471,17 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Sofortzahlung konfigurieren - + Fast payments for low amounts Schnelle Zahlungen für niedrige Beträge - + Not configured Nicht konfiguriert - + Limit set to: %1 Limit gesetzt auf: %1 @@ -538,7 +570,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. MenuOverlay - + Add Wallet Geldbörse hinzufügen @@ -685,49 +717,49 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussAlles senden - + Show Address to show a bitcoincash address Adresse anzeigen - + Edit Amount Edit amount of money to send Betrag bearbeiten - + Invalid QR code Ungültiger QR Code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. - + details details - + Scanned text: <pre>%1</pre> Gescannter Text: <pre>%1</pre> - + Payment description Zahlungsbeschreibung - + Destination Address Zieladresse - + Unlock Wallet Geldbörse entsperren @@ -762,7 +794,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss PriceInputWidget - + All Currencies Alle Währungen @@ -770,22 +802,22 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss QRScannerOverlay - + Paste Einfügen - + Failed Fehlgeschlagen - + Instant Pay limit is %1 Sofortzahlungslimit ist %1 - + Selected wallet: '%1' Ausgewählte Geldbörse: '%1' @@ -814,7 +846,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - + Description Beschreibung @@ -836,43 +868,43 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussLeeren - - + + Payment Seen Zahlung gesichtet - + Checking... Überprüfe... - + Transaction high risk Hoch-Risiko Transaktion - + Partially Paid Teilweise bezahlt - + Payment Accepted Zahlung akzeptiert - + Payment Settled Zahlung abgeschlossen - + Instant payment failed. Wait for confirmation. (double spent proof received) Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) - + Continue Fortfahren @@ -1010,48 +1042,48 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussCoinbase - + Fees paid Bezahlte Gebühren - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 Bytes - + Fused from my addresses Fusioniert von meinen Adressen - + Sent from my addresses Gesendet von meinen Adressen - + Sent from addresses Gesendet von den Adressen - - + + Copy Address Adresse kopieren - + Fused into my addresses Fusioniert in meine Adressen - + Received at addresses Empfangen auf den Adressen - + Received at my addresses Empfangen auf meinen Adressen diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 61a0077..02d8920 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -30,17 +30,17 @@ © 2020-2023 Tom Zander and contributors - + Project Home Project Home - + With git repository and issues tracker With git repository and issues tracker - + Telegram Telegram @@ -70,7 +70,7 @@ Fused - Cash Fusion + Fused @@ -132,88 +132,93 @@ + Seed format + Seed format + + + Starting Height height refers to block-height Starting Height - + Derivation Path Derivation Path - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + Wallet keys Wallet keys - + Show Index toggle to show numbers Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between still in use addresses and formerly used, new empty, addresses Switches between still in use addresses and formerly used, new empty, addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet @@ -327,27 +332,32 @@ Display Settings - + Font sizing Font sizing - + + Dark Theme + Dark Theme + + + Unit Unit - + Change Currency (%1) Change Currency (%1) - + Main View Main View - + Show Bitcoin Cash value Show Bitcoin Cash value @@ -360,68 +370,90 @@ Import Wallet - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - + Secret The seed-phrase or private key Secret - + Private key description of type Private key - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 seed-phrase (interpreted as Electrum) + + + BIP 39 seed-phrase description of type BIP 39 seed-phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unrecognized word - + Name Name - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Oldest Transaction Oldest Transaction - + Derivation Derivation - + Alternate phrase Alternate phrase - + Create Create @@ -439,17 +471,17 @@ Change will come back to the imported key. Configure Instant Pay - + Fast payments for low amounts Fast payments for low amounts - + Not configured Not configured - + Limit set to: %1 Limit set to: %1 @@ -538,7 +570,7 @@ Change will come back to the imported key. MenuOverlay - + Add Wallet Add Wallet @@ -685,49 +717,49 @@ This ensures only one private key will need to be backed up Send All - + Show Address to show a bitcoincash address Show Address - + Edit Amount Edit amount of money to send Edit Amount - + Invalid QR code Invalid QR code - + I don't understand the scanned code. I'm sorry, I can't start a payment. I don't understand the scanned code. I'm sorry, I can't start a payment. - + details details - + Scanned text: <pre>%1</pre> Scanned text: <pre>%1</pre> - + Payment description Payment description - + Destination Address Destination Address - + Unlock Wallet Unlock Wallet @@ -762,7 +794,7 @@ This ensures only one private key will need to be backed up PriceInputWidget - + All Currencies All Currencies @@ -770,22 +802,22 @@ This ensures only one private key will need to be backed up QRScannerOverlay - + Paste Paste - + Failed Failed - + Instant Pay limit is %1 Instant Pay limit is %1 - + Selected wallet: '%1' Selected wallet: '%1' @@ -814,7 +846,7 @@ This ensures only one private key will need to be backed up - + Description Description @@ -836,43 +868,43 @@ This ensures only one private key will need to be backed up Clear - - + + Payment Seen Payment Seen - + Checking... Checking... - + Transaction high risk Transaction high risk - + Partially Paid Partially Paid - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Continue Continue @@ -1010,48 +1042,48 @@ This ensures only one private key will need to be backed up Coinbase - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - - + + Copy Address Copy Address - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index 91ff94f..495c7bd 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -30,17 +30,17 @@ © 2020-2023 Tom Zander y contribuyentes - + Project Home Página del proyecto - + With git repository and issues tracker Con repositorio git y seguimiento de incidencias - + Telegram Telegram @@ -70,7 +70,7 @@ Fused - Fusión de Monedas + Fused @@ -132,88 +132,93 @@ + Seed format + Seed format + + + Starting Height height refers to block-height Altura inicial - + Derivation Path Ruta de Derivación - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que pierda su móvil. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + Wallet keys Llaves del monedero - + Show Index toggle to show numbers Mostrar índice - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Intercambia entre direcciones que otros pueden pagarle y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between still in use addresses and formerly used, new empty, addresses Alterna entre direcciones en uso, previamente usadas y direcciones sin usar - + Addresses and keys Direcciones y claves - + Sync Status Estado de la sincronización - + Hide balance in overviews Ocultar balance en vistas generales - + Hide in private mode Ocultar en modo privado - + Unarchive Wallet Desarchivar monedero - + Archive Wallet Archivar monedero @@ -327,27 +332,32 @@ Mostrar ajustes - + Font sizing Tamaño de fuente - + + Dark Theme + Dark Theme + + + Unit Unidad - + Change Currency (%1) Cambiar moneda (%1) - + Main View Vista principal - + Show Bitcoin Cash value Mostrar valor de Bitcoin Cash @@ -360,68 +370,90 @@ Importar monedero - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. - + Secret The seed-phrase or private key Secreto - + Private key description of type Llave privada - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 seed-phrase (interpreted as Electrum) + + + BIP 39 seed-phrase description of type Frase semilla BIP 39 - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Palabra no reconocida - + Name Nombre - + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Oldest Transaction Transacción más antigua - + Derivation Derivación - + Alternate phrase Frase alternativa - + Create Crear @@ -439,17 +471,17 @@ El cambio volverá a la clave importada. Configurar Pago Instantáneo - + Fast payments for low amounts Pagos rápidos para cantidades bajas - + Not configured Sin configurar - + Limit set to: %1 Límite establecido a: %1 @@ -538,7 +570,7 @@ El cambio volverá a la clave importada. MenuOverlay - + Add Wallet Añadir Monedero @@ -685,49 +717,49 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Enviar Todo - + Show Address to show a bitcoincash address Mostrar dirección - + Edit Amount Edit amount of money to send Editar cantidad - + Invalid QR code Código QR inválido - + I don't understand the scanned code. I'm sorry, I can't start a payment. No entiendo el código escaneado. Lo siento, no puedo iniciar un pago. - + details detalles - + Scanned text: <pre>%1</pre> Texto escaneado: <pre>%1</pre> - + Payment description Descripción del pago - + Destination Address Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -762,7 +794,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada PriceInputWidget - + All Currencies Todas las divisas @@ -770,22 +802,22 @@ Esto asegura que solo una clave privada tendrá que ser respaldada QRScannerOverlay - + Paste Pegar - + Failed Fallido - + Instant Pay limit is %1 Instant Pay limit is %1 - + Selected wallet: '%1' Selected wallet: '%1' @@ -814,7 +846,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - + Description Descripción @@ -836,43 +868,43 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Borrar - - + + Payment Seen Pago Enviado - + Checking... Comprobando... - + Transaction high risk Transacción de alto riesgo - + Partially Paid Parcialmente pagado - + Payment Accepted Pago Aceptado - + Payment Settled Pago Enviado - + Instant payment failed. Wait for confirmation. (double spent proof received) Pago instantáneo fallido. Espere la confirmación. (prueba de doble gasto recibida) - + Continue Continuar @@ -1010,48 +1042,48 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Coinbase - + Fees paid Comisiones pagadas - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fusionado desde mis direcciones - + Sent from my addresses Enviado desde mis direcciones - + Sent from addresses Enviado desde direcciones - - + + Copy Address Copiar dirección - + Fused into my addresses Fusionado en mis direcciones - + Received at addresses Recibido en direcciones - + Received at my addresses Recibido en mis direcciones @@ -1090,7 +1122,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Fees - Comisiones + Fees diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts new file mode 100644 index 0000000..c27c6b0 --- /dev/null +++ b/translations/floweepay-mobile_ha.ts @@ -0,0 +1,1180 @@ + + + + + About + + + About + Game da + + + + Help translate this app + Taimaka fassara wannan manhaja + + + + License + Lasisi + + + + + Credits + Sanya kudi + + + + © 2020-2023 Tom Zander and contributors + © 2020-2023 Tom Zander da masu ba da gudummawa + + + + Project Home + Gidan Aikin + + + + With git repository and issues tracker + Tare da ma'ajiyar git da mai bin diddigin batutuwa + + + + Telegram + Telegram + + + + AccountHistory + + + Home + Gida + + + + Pay + Biya + + + + Receive + Karɓa + + + + Miner Reward + Ladan Ma'adinai + + + + Fused + Fuskanci + + + + Received + An samu + + + + Moved + Motsa + + + + Sent + An aika + + + + Sending + Aikawa + + + + Seen + An gani + + + + Rejected + An ƙii + + + + AccountPageListItem + + + Name + Suna + + + + Archived wallets do not check for activities. Balance may be out of date + Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya ƙare + + + + Backup information + Ajiyayyen Bayanai + + + + Backup Details + Ajiyayyen bayanai + + + + Wallet seed-phrase + Asusun Jumla iri-iri + + + + Seed format + Tsarin iri + + + + Starting Height + height refers to block-height + Fara tsawo + + + + Derivation Path + Hanyar Fitowa + + + + xpub + Xpub + + + + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. + Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. + + + + <b>Important</b>: Never share your seed-phrase with others! + <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! + + + + Wallet keys + Maɓallan asusu + + + + Show Index + toggle to show numbers + Nuna index + + + + Change Addresses + Chanjin adireshi + + + + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. + + + + Used Addresses + Adireshin da aka yi amfani da shi + + + + Switches between still in use addresses and formerly used, new empty, addresses + Yana canzawa tsakanin adiresoshin da ake amfani da su da waɗanda aka yi amfani da su a baya, sabon fankon, adireshi + + + + Addresses and keys + Adireshi da maɓallai + + + + Sync Status + Daidaita matsayi + + + + Hide balance in overviews + Boye ma'auni a cikin bayyani + + + + Hide in private mode + Boye a yanayin sirri + + + + Unarchive Wallet + Ma'ajiyin taskoki + + + + Archive Wallet + Ma'ajiyin taskoki + + + + AccountSelectorPopup + + + Your Wallets + Asusun ku + + + + last active + aiki na ƙarshe + + + + Needs PIN to open + Akwai buƙatar PIN don buɗewa + + + + Balance Total + Jimlar Ma'auni + + + + AccountSyncState + + + Status: Offline + Matsayi: Akashe + + + + AccountsList + + + Wallet + Asusu + + + + Wallets + Asusun + + + + Add Wallet + Ƙara Asusu + + + + Default Wallet + Tsohuwar asusu + + + + %1 is used on startup + Ana amfani da %1 akan farawa + + + + Exit Private Mode + Fita daga Yanayin Sirri + + + + Enter Private Mode + Shiga Keɓaɓɓen Yanayin + + + + Reveals wallets marked private + Bayyana asusun ɗin da ke da alamar sirri + + + + Hides wallets marked private + Ɓoye asusun ɗin da aka yiwa alama na sirri + + + + CurrencySelector + + + Select Currency + Zaɓi Kuɗi + + + + ExploreModules + + + Explore + Bincika + + + + ON + Enabled. SHORT TEXT! + Kan + + + + GuiSettings + + + Display Settings + Baiyana Saituna + + + + Font sizing + Girman haruffa + + + + Dark Theme + Jigo mai duhu + + + + Unit + Na'ura + + + + Change Currency (%1) + Canja Kuɗi (%1) + + + + Main View + Babban Duba + + + + Show Bitcoin Cash value + Nuna darajar Bitcoin Cash + + + + ImportWalletPage + + + Import Wallet + Ɗauko asusu + + + + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. + Ataimaka a shigar da sirrin asusu don shigo da shi. Wannan na iya zama jimla-jinin ko maɓalli na sirri. + + + + Secret + The seed-phrase or private key + Sirri + + + + Private key + description of type + Keɓaɓɓen maɓalli + + + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 zuriyar jimla (an fassara shi azaman Electrum) + + + + BIP 39 seed-phrase + description of type + BIP 39 zuriyar jimla + + + + Electrum seed-phrase + description of type + Zuriyar jimlar Electrum + + + + Unrecognized word + Word from the seed-phrases lexicon + Kalmar da ba'a gane ba + + + + Name + Suna + + + + Force Single Address + Tilasta Adireshi Guda Daya + + + + When enabled, no extra addresses will be auto-generated in this wallet. +Change will come back to the imported key. + Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. + + + + Old Electrum Phrase + Tsohon Zuriyar jimlar Electrum + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Lokacin gano Electrum ya gaza, kuma kun tabbata an ƙirƙira shi a cikin wallet ɗin, kunna wannan zaɓi. + + + + Oldest Transaction + Tsohon ciniki + + + + Derivation + Samo asali + + + + Alternate phrase + Madadin magana + + + + Create + Ƙirƙiri + + + + InstaPayConfigButton + + + Enable Instant Pay + Kunna Biyan Nan take + + + + Configure Instant Pay + Sanya Biyan Nan take + + + + Fast payments for low amounts + Biyan kuɗi mai sauri don ƙananan kuɗi + + + + Not configured + Ba a saita shi ba + + + + Limit set to: %1 + An saita iyaka zuwa: %1 + + + + InstaPayConfigPage + + + Instant Pay + Biyan Nan take + + + + Requests for payment can be approved automatically using Instant Pay. + Ana iya amincewa da buƙatun biyan kuɗi ta atomatik ta amfani da Biyan Nan take. + + + + Protected wallets can not be used for InstaPay because they require a PIN before usage + Ba za a iya amfani da kariyar asusu ɗin don InstaPay ba saboda suna buƙatar PIN kafin amfani + + + + Enable Instant Pay for this wallet + Kunna Biyan Nan take na wannan asusun + + + + Maximum Amount + Makurar Adadi + + + + LockApplication + + + Security + Tsaro + + + + PIN on startup + PIN akan farawa + + + + Enter current PIN + Shigar da lambar tsaro na yanzu + + + + Enter new PIN + Shigar da lambar tsaro + + + + Repeat PIN + Maimaita lambar tsaro + + + + Remove PIN + Cire lambar tsaro + + + + Set PIN + Saita lambar tsaro + + + + PIN has been removed + An cire lambar tsaro + + + + PIN has been set + An saita lambar tsaro + + + + Ok + Ya yi + + + + MenuOverlay + + + Add Wallet + Ƙara Asusu + + + + NewAccount + + + New Bitcoin Cash Wallet + Sabon asusun Bitcoin Cash + + + + Create a New Wallet + Ƙirƙiri sabon asusu + + + + HD wallet + Asusun HD + + + + Seed-phrase based + Context: wallet type + Tushen jimlar iri + + + + Easy to backup + Context: wallet type + Sauƙi wurin dawo da ajiya + + + + Most compatible + The most compatible wallet type + Mafi dacewa + + + + Basic + Na asali + + + + Private keys based + Property of a wallet + Maɓallai masu zaman kansu + + + + Difficult to backup + Context: wallet type + Wahalar dawo da ajiya + + + + Great for brief usage + Context: wallet type + Mai girma don taƙaitaccen amfani + + + + Import Existing Wallet + Shigo da asusun da ke akwai + + + + Import + Shigo da + + + + Imports seed-phrase + Shigo da jumla iri-iri + + + + Imports private key + Shigo da maɓalli na sirri + + + + New Wallet + Sabon Asusu + + + + This creates a new empty wallet with simple multi-address capability + Wannan yana haifar da sabon asusu mara komai tare da sauƙin adireshi da yawa + + + + + Name + Suna + + + + Force Single Address + Tilasta Adireshi Guda + + + + When enabled, this wallet will be limited to one address. +This ensures only one private key will need to be backed up + Lokacin da aka kunna, wannan asusun za'a iyakance shi ga adireshi ɗaya. Wannan yana tabbatar da maɓallin keɓaɓɓen maɓalli ɗaya ne kawai zai buƙaci a yi masa tallafi + + + + + Create + Ƙirƙiri + + + + New HD-Wallet + Sabon Asusun HD + + + + This creates a new wallet that can be backed up with a seed-phrase + Wannan yana haifar da sabon asusu wanda za'a iya tallafawa ko dawo dashi tare da jimlar iri + + + + Derivation + Samo asali + + + + PayWithQR + + + Approve Payment + Amincewa Biya + + + + Send All + all money in wallet + Tura duka + + + + Show Address + to show a bitcoincash address + Nuna adireshi + + + + Edit Amount + Edit amount of money to send + Gyara Adadin + + + + Invalid QR code + Lambar QR mara inganci + + + + I don't understand the scanned code. I'm sorry, I can't start a payment. + Ban fahimci lambar da aka bincika ba. Yi hakuri, ba zan iya fara biya ba. + + + + details + bayanai + + + + Scanned text: <pre>%1</pre> + Rubutun da aka duba: <pre>%1</pre> + + + + Payment description + Bayanin biyan kuɗi + + + + Destination Address + Adireshin Zuwa + + + + Unlock Wallet + Buɗe Asusu + + + + PriceDetails + + + 1 BCH is: %1 + Price of a whole bitcoin cash + 1 BCH is: %1 + + + + 7d + 7 days + 7d + + + + 1m + 1 month + 1m + + + + 3m + 3 months + 3m + + + + PriceInputWidget + + + All Currencies + Ireiren kuɗaɗe + + + + QRScannerOverlay + + + Paste + Wallafa + + + + Failed + Ba a yi nasara ba + + + + Instant Pay limit is %1 + Iyakar Biyan Nan take shine %1 + + + + Selected wallet: '%1' + Asusun da aka zaɓa: '%1' + + + + ReceiveTab + + + Receive + Karɓa + + + + Share this QR to receive + Raba wannan QR don karɓa + + + + Encrypted Wallet + Rufaffen asusu + + + + Import Running... + Tsarin Shiga na gudu... + + + + + Description + Bayani + + + + Amount + requested amount of coin + Adadi + + + + Address + Bitcoin Cash address + Adireshi + + + + Clear + Share + + + + + Payment Seen + Anga shaidar biya + + + + Checking... + Ana dubawa... + + + + Transaction high risk + Haɗarin cinikayya + + + + Partially Paid + An biya wani bangare + + + + Payment Accepted + An Karɓa Biya + + + + Payment Settled + Biya An daidaita + + + + Instant payment failed. Wait for confirmation. (double spent proof received) + Biyan nan take ya kasa. Jira tabbaci. (Anga shaida guda biyu da aka kashe) + + + + Continue + Ci gaba + + + + SelectDefaultAccountPage + + + Select Wallet + Zabi asusu + + + + Pick which wallet will be selected on starting Flowee Pay + Zaɓi wanne asusun kuɗi za a zaɓi akan farawa Flowee Pay + + + + No InstaPay limit set + Ba a saita iyakar InstaPay + + + + InstaPay till %1 + InstaPay har zuwa %1 + + + + InstaPay is turned off + An kashe InstaPay + + + + SendTransactionsTab + + + Send + Aika + + + + Start Payment + Fara Biyan Kuɗi + + + + SlideToApprove + + + SLIDE TO SEND + TURA DON AIKA + + + + StartupScreen + + + Welcome! + Barka da zuwa! + + + + Continue + Ci gaba + + + + Moving the world towards a Bitcoin Cash economy + Matsar da duniya zuwa tattalin arzikin Bitcoin Cash + + + + Scan me to send funds to your HD wallet + Duba ni don aika kuɗi zuwa asusun ku na HD + + + + OR + OR + + + + Add a different wallet + Add a different wallet + + + + TransactionDetails + + + Transaction Details + Cikakken Bayanin Kasuwanci + + + + Transaction Hash + Transaction Hash + + + + Rejected + An ƙii + + + + Unconfirmed + Unconfirmed + + + + Mined at + Mined at + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + + + + + Transaction comment + Transaction comment + + + + Size: %1 bytes + Size: %1 bytes + + + + Coinbase + Coinbase + + + + Fees paid + Fees paid + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Fused from my addresses + Fused from my addresses + + + + Sent from my addresses + Sent from my addresses + + + + Sent from addresses + Sent from addresses + + + + + Copy Address + Copy Address + + + + Fused into my addresses + Fused into my addresses + + + + Received at addresses + Received at addresses + + + + Received at my addresses + Received at my addresses + + + + TxInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 blocks ago + %1 blocks ago + + + + + Miner Reward + Miner Reward + + + + Fees + Fees + + + + Received + Received + + + + Payment to self + Payment to self + + + + Sent + Sent + + + + Holds a token + Holds a token + + + + Sent to + Sent to + + + + Value now + Value now + + + + Value then + Value then + + + + Transaction Details + Cikakken Bayanin Kasuwanci + + + + UnlockWidget + + + Enter your wallet passcode + Enter your wallet passcode + + + + Open + open wallet with PIN + Bude + + + diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 930988c..bdabd4e 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -30,17 +30,17 @@ © 2020-2023 Tom Zander en bijdragers - + Project Home Startpagina project - + With git repository and issues tracker Met git data en takenlijst - + Telegram Telegram @@ -132,88 +132,93 @@ + Seed format + Herstelzin formaat + + + Starting Height height refers to block-height Beginhoogte - + Derivation Path Derivatie pad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between still in use addresses and formerly used, new empty, addresses Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren @@ -327,27 +332,32 @@ Scherminstellingen - + Font sizing Lettertypegrootte - + + Dark Theme + Donker Thema + + + Unit Eenheid - + Change Currency (%1) Verander valuta (%1) - + Main View Hoofd overzicht - + Show Bitcoin Cash value Toon Bitcoin Cash waarde @@ -360,68 +370,90 @@ Portemonnee importeren - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - + Secret The seed-phrase or private key Geheim - + Private key description of type Privé-sleutel - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 herstelzin (geïnterpreteerd als Electrum formaat) + + + BIP 39 seed-phrase description of type BIP 39 herstelzin - + + Electrum seed-phrase + description of type + Electron-Cash herstelzin + + + Unrecognized word Word from the seed-phrases lexicon Niet herkend woord - + Name Naam - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - + + Old Electrum Phrase + Oude Electrum herstelzin + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Zet deze optie aan als Electrum detectie mislukt en u zeker weet dat je herstelzin in die portemonnee is gemaakt. + + + Oldest Transaction Oudste transactie - + Derivation Derivatie - + Alternate phrase Alternatieve zin - + Create Creëer @@ -439,17 +471,17 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Configureer Direct Betalen - + Fast payments for low amounts Direct betalen bij lage bedragen - + Not configured Niet geconfigureerd - + Limit set to: %1 Limiet ingesteld op: %1 @@ -538,7 +570,7 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MenuOverlay - + Add Wallet Portemonnee toevoegen @@ -685,49 +717,49 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Alles verzenden - + Show Address to show a bitcoincash address Toon adres - + Edit Amount Edit amount of money to send Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -762,7 +794,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PriceInputWidget - + All Currencies Alle valuta's @@ -770,22 +802,22 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt QRScannerOverlay - + Paste Plak - + Failed Mislukt! - + Instant Pay limit is %1 Directbetalen limiet is %1 - + Selected wallet: '%1' Geselecteerde portemonnee: '%1' @@ -814,7 +846,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + Description Omschrijving @@ -836,43 +868,43 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Wissen - - + + Payment Seen Betaling gezien - + Checking... Controleren... - + Transaction high risk Transactie met hoog risico - + Partially Paid Deels betaald - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - + Continue Doorgaan @@ -1010,48 +1042,48 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Coinbase - + Fees paid Betaalde kosten - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Mijn gefuseerde adressen - + Sent from my addresses Verzonden vanaf mijn adressen - + Sent from addresses Verzonden vanaf adressen - - + + Copy Address Kopieer adres - + Fused into my addresses Gefuseerd naar mijn adressen - + Received at addresses Ontvangen op adressen - + Received at my addresses Ontvangen op mijn adressen diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index d054521..bb46342 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -30,17 +30,17 @@ ©️ 2020-2023 Tom Zander i współtwórcy - + Project Home Strona projektu - + With git repository and issues tracker Z repozytorium git i trackerem problemów - + Telegram Telegram @@ -70,7 +70,7 @@ Fused - Fused + Fused @@ -132,88 +132,93 @@ + Seed format + Seed format + + + Starting Height height refers to block-height Wysokość przy utworzeniu - + Derivation Path Ścieżka derywacji - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + Wallet keys Klucze portfela - + Show Index toggle to show numbers Pokaż indeks - + Change Addresses Adres Reszty - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between still in use addresses and formerly used, new empty, addresses Przełącza się pomiędzy nadal używanymi adresami i wcześniej używanymi, nowymi pustymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Ukryj saldo w podsumowaniach - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet Anuluj archiwizację portfela - + Archive Wallet Zarchiwizuj portfel @@ -327,27 +332,32 @@ Ustawienia wyświetlania - + Font sizing Rozmiar czcionki - + + Dark Theme + Dark Theme + + + Unit Jednostka - + Change Currency (%1) Zmień Walutę (%1) - + Main View Ekran Główny - + Show Bitcoin Cash value Pokazuj wartość Bitcoin Cash @@ -360,67 +370,89 @@ Importuj Portfel - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Proszę wprowadzić sekret portfela, aby zaimportować. Może to być albo fraza-ziarno, albo klucz prywatny. - + Secret The seed-phrase or private key Sekret - + Private key description of type Klucz prywatny - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 seed-phrase (interpreted as Electrum) + + + BIP 39 seed-phrase description of type Fraza-ziarno BIP 39 - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Nierozpoznane słowo - + Name Nazwa - + Force Single Address Wymuś pojedynczy adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Oldest Transaction Najstarsza transakcja - + Derivation Derywacja - + Alternate phrase Alternatywna fraza - + Create Utwórz @@ -438,17 +470,17 @@ Change will come back to the imported key. Skonfiguruj Szybką Płatność - + Fast payments for low amounts Szybkie płatności na małe kwoty - + Not configured Nie ustawiono - + Limit set to: %1 Limit ustawiony do: %1 @@ -537,7 +569,7 @@ Change will come back to the imported key. MenuOverlay - + Add Wallet Dodaj Portfel @@ -684,49 +716,49 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoWyślij Wszystko - + Show Address to show a bitcoincash address Pokaż adres - + Edit Amount Edit amount of money to send Edytuj kwotę - + Invalid QR code Nieprawidłowy kod QR - + I don't understand the scanned code. I'm sorry, I can't start a payment. Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. - + details szczegóły - + Scanned text: <pre>%1</pre> Zeskanowany tekst: <pre>%1</pre> - + Payment description Opis płatności - + Destination Address Adres docelowy - + Unlock Wallet Odblokuj portfel @@ -761,7 +793,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego PriceInputWidget - + All Currencies Wszystkie Waluty @@ -769,22 +801,22 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego QRScannerOverlay - + Paste Wklej - + Failed Niepowodzenie - + Instant Pay limit is %1 Limit szybkiej płatności wynosi %1 - + Selected wallet: '%1' Wybrany portfel: '%1' @@ -813,7 +845,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - + Description Opis @@ -835,43 +867,43 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoWyczyść - - + + Payment Seen Płatność Wysłana - + Checking... Sprawdzanie... - + Transaction high risk Transakcja o wysokim poziomie ryzyka - + Partially Paid Częściowo opłacona - + Payment Accepted Płatność zaakceptowana - + Payment Settled Płatność rozliczona - + Instant payment failed. Wait for confirmation. (double spent proof received) Płatność błyskawiczna nie powiodła się. Poczekaj na potwierdzenie. (otrzymano dowód podwójnej płatności) - + Continue Kontynuuj @@ -1011,48 +1043,48 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoCoinbase - + Fees paid Koszt transakcji - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bajtów - + Fused from my addresses Fuzja z moich adresów - + Sent from my addresses Wysłano z moich adresów - + Sent from addresses Wysłano z adresów - - + + Copy Address Skopiuj Adres - + Fused into my addresses Fuzja na mój adres - + Received at addresses Adresy otrzymujące - + Received at my addresses Moje adresy otrzymujące @@ -1093,7 +1125,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Fees - Fees + Fees diff --git a/translations/floweepay-mobile_pt.ts b/translations/floweepay-mobile_pt.ts index 6e11b75..b6a5cdd 100644 --- a/translations/floweepay-mobile_pt.ts +++ b/translations/floweepay-mobile_pt.ts @@ -1,6 +1,6 @@ - + About @@ -30,17 +30,17 @@ © 2020-2023 Tom Zander and contributors - + Project Home Project Home - + With git repository and issues tracker With git repository and issues tracker - + Telegram Telegram @@ -132,88 +132,93 @@ + Seed format + Seed format + + + Starting Height height refers to block-height Starting Height - + Derivation Path Derivation Path - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + Wallet keys Wallet keys - + Show Index toggle to show numbers Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between still in use addresses and formerly used, new empty, addresses Switches between still in use addresses and formerly used, new empty, addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet @@ -327,27 +332,32 @@ Display Settings - + Font sizing Font sizing - + + Dark Theme + Dark Theme + + + Unit Unit - + Change Currency (%1) Change Currency (%1) - + Main View Main View - + Show Bitcoin Cash value Show Bitcoin Cash value @@ -360,68 +370,90 @@ Import Wallet - + Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - + Secret The seed-phrase or private key Secret - + Private key description of type Private key - + + BIP 39 seed-phrase (interpreted as Electrum) + description of type + BIP 39 seed-phrase (interpreted as Electrum) + + + BIP 39 seed-phrase description of type BIP 39 seed-phrase - + + Electrum seed-phrase + description of type + Electrum seed-phrase + + + Unrecognized word Word from the seed-phrases lexicon Unrecognized word - + Name Name - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - + + Old Electrum Phrase + Old Electrum Phrase + + + + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + + Oldest Transaction Oldest Transaction - + Derivation Derivation - + Alternate phrase Alternate phrase - + Create Create @@ -439,17 +471,17 @@ Change will come back to the imported key. Configure Instant Pay - + Fast payments for low amounts Fast payments for low amounts - + Not configured Not configured - + Limit set to: %1 Limit set to: %1 @@ -538,7 +570,7 @@ Change will come back to the imported key. MenuOverlay - + Add Wallet Add Wallet @@ -685,49 +717,49 @@ This ensures only one private key will need to be backed up Send All - + Show Address to show a bitcoincash address Show Address - + Edit Amount Edit amount of money to send Edit Amount - + Invalid QR code Invalid QR code - + I don't understand the scanned code. I'm sorry, I can't start a payment. I don't understand the scanned code. I'm sorry, I can't start a payment. - + details details - + Scanned text: <pre>%1</pre> Scanned text: <pre>%1</pre> - + Payment description Payment description - + Destination Address Destination Address - + Unlock Wallet Unlock Wallet @@ -762,7 +794,7 @@ This ensures only one private key will need to be backed up PriceInputWidget - + All Currencies All Currencies @@ -770,22 +802,22 @@ This ensures only one private key will need to be backed up QRScannerOverlay - + Paste Paste - + Failed Failed - + Instant Pay limit is %1 Instant Pay limit is %1 - + Selected wallet: '%1' Selected wallet: '%1' @@ -814,7 +846,7 @@ This ensures only one private key will need to be backed up - + Description Description @@ -836,43 +868,43 @@ This ensures only one private key will need to be backed up Clear - - + + Payment Seen Payment Seen - + Checking... Checking... - + Transaction high risk Transaction high risk - + Partially Paid Partially Paid - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Continue Continue @@ -1010,48 +1042,48 @@ This ensures only one private key will need to be backed up Coinbase - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - - + + Copy Address Copy Address - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index 2a6f545..b206bd0 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -6,23 +6,27 @@ floweepay-common_de.qm floweepay-common_es.qm floweepay-common_pt.qm + floweepay-common_ha.qm floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm floweepay-mobile_de.qm floweepay-mobile_es.qm floweepay-mobile_pt.qm + floweepay-mobile_ha.qm module-build-transaction_en.qm module-build-transaction_nl.qm module-build-transaction_pl.qm module-build-transaction_de.qm module-build-transaction_es.qm module-build-transaction_pt.qm + module-build-transaction_ha.qm module-peers-view_en.qm module-peers-view_nl.qm module-peers-view_pl.qm module-peers-view_de.qm module-peers-view_es.qm module-peers-view_pt.qm + module-peers-view_ha.qm diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index b76d818..28a48e4 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -40,7 +40,7 @@ - + Add Destination Ziel hinzufügen @@ -92,78 +92,83 @@ %1 Sat/Byte - + Destination Ziel - + unset indication of empty ungesetzt - + invalid address is not correct ungültig - - + + Copy Address Adresse kopieren - + Edit Destination Ziel bearbeiten - + Send All all money in wallet Alles senden - + Bitcoin Cash Address Bitcoin Cash Adresse - + + Paste + Paste + + + Warning Warnung - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + I am certain Ich bin mir sicher - + Drag to Edit Ziehen zum Bearbeiten - + Drag to Delete Ziehen zum Löschen - - Prepare Payment... - Zahlung wird vorbereitet... - - - + Unlock Wallet Geldbörse entsperren + + + Prepare Payment... + Zahlung wird vorbereitet... + diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index 0078391..08c04ef 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -40,7 +40,7 @@ - + Add Destination Add Destination @@ -92,78 +92,83 @@ %1 sat/byte - + Destination Destination - + unset indication of empty unset - + invalid address is not correct invalid - - + + Copy Address Copy Address - + Edit Destination Edit Destination - + Send All all money in wallet Send All - + Bitcoin Cash Address Bitcoin Cash Address - + + Paste + Paste + + + Warning Warning - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + I am certain I am certain - + Drag to Edit Drag to Edit - + Drag to Delete Drag to Delete - - Prepare Payment... - Prepare Payment... - - - + Unlock Wallet Unlock Wallet + + + Prepare Payment... + Prepare Payment... + diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts index d545112..76be33d 100644 --- a/translations/module-build-transaction_es.ts +++ b/translations/module-build-transaction_es.ts @@ -40,7 +40,7 @@ - + Add Destination Añadir destino @@ -92,78 +92,83 @@ %1 sat/byte - + Destination Destino - + unset indication of empty no establecido - + invalid address is not correct inválido - - + + Copy Address Copiar dirección - + Edit Destination Editar destino - + Send All all money in wallet Enviar Todo - + Bitcoin Cash Address Dirección de Bitcoin Cash - + + Paste + Paste + + + Warning Advertencia - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + I am certain Estoy seguro - + Drag to Edit Arrastre para editar - + Drag to Delete Arrastre para eliminar - - Prepare Payment... - Preparar pago... - - - + Unlock Wallet Desbloquear Monedero + + + Prepare Payment... + Preparar pago... + diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts new file mode 100644 index 0000000..f11ae40 --- /dev/null +++ b/translations/module-build-transaction_ha.ts @@ -0,0 +1,174 @@ + + + + + BuildTransactionModuleInfo + + + Create Transactions + Create Transactions + + + + This module allows building more powerful transactions in one simple user interface. + This module allows building more powerful transactions in one simple user interface. + + + + Build Transaction + Build Transaction + + + + PayToOthers + + + Build Transaction + Build Transaction + + + + Building Error + error during build + Building Error + + + + Add Payment Detail + page title + Add Payment Detail + + + + + Add Destination + Ƙara madakata + + + + an address to send money to + an address to send money to + + + + Confirm Sending + confirm we want to send the transaction + Confirm Sending + + + + TXID + TXID + + + + Copy transaction-ID + Copy transaction-ID + + + + Fee + Kudin + + + + Transaction size + Girman ciniki + + + + %1 bytes + %1 bytes + + + + Fee per byte + Kudin kowane byte + + + + %1 sat/byte + fee + %1 sat/bytes + + + + Destination + Destination + + + + unset + indication of empty + unset + + + + invalid + address is not correct + invalid + + + + + Copy Address + Copy Address + + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Tura duka + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Paste + Wallafa + + + + Warning + Gargaɗi + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + I am certain + I am certain + + + + Drag to Edit + Drag to Edit + + + + Drag to Delete + Drag to Delete + + + + Unlock Wallet + Buɗe Asusu + + + + Prepare Payment... + Prepare Payment... + + + diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index 6541016..7f7c73e 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -40,7 +40,7 @@ - + Add Destination Voeg bestemming toe @@ -92,78 +92,83 @@ %1 sat/byte - + Destination Bestemming - + unset indication of empty niets - + invalid address is not correct ongeldig - - + + Copy Address Kopieer adres - + Edit Destination Bewerk Bestemming - + Send All all money in wallet Betaal alles - + Bitcoin Cash Address Bitcoin Cash-adres - + + Paste + Plak + + + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + I am certain Ik ben zeker - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - - Prepare Payment... - Betaling voorbereiden... - - - + Unlock Wallet Portemonnee ontgrendelen + + + Prepare Payment... + Betaling voorbereiden... + diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index e97aa66..f763470 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -40,7 +40,7 @@ - + Add Destination Dodaj Odbiorcę @@ -92,78 +92,83 @@ %1 sat/bajt - + Destination Odbiorca - + unset indication of empty dezaktywuj - + invalid address is not correct Nieprawidłowy - - + + Copy Address Skopiuj Adres - + Edit Destination Zmień Odbiorcę - + Send All all money in wallet Wyślij Wszystko - + Bitcoin Cash Address Adres Bitcoin Cash - + + Paste + Paste + + + Warning Uwaga - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + I am certain Na Pewno - + Drag to Edit Przeciągnij, aby edytować - + Drag to Delete Przeciągnij, aby usunąć - - Prepare Payment... - Przygotuj płatność... - - - + Unlock Wallet Odblokuj portfel + + + Prepare Payment... + Przygotuj płatność... + diff --git a/translations/module-build-transaction_pt.ts b/translations/module-build-transaction_pt.ts index dc0ad1e..86e50f7 100644 --- a/translations/module-build-transaction_pt.ts +++ b/translations/module-build-transaction_pt.ts @@ -1,6 +1,6 @@ - + BuildTransactionModuleInfo @@ -40,7 +40,7 @@ - + Add Destination Add Destination @@ -92,78 +92,83 @@ %1 sat/byte - + Destination Destination - + unset indication of empty unset - + invalid address is not correct invalid - - + + Copy Address Copy Address - + Edit Destination Edit Destination - + Send All all money in wallet Send All - + Bitcoin Cash Address Bitcoin Cash Address - + + Paste + Paste + + + Warning Warning - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + I am certain I am certain - + Drag to Edit Drag to Edit - + Drag to Delete Drag to Delete - - Prepare Payment... - Prepare Payment... - - - + Unlock Wallet Unlock Wallet + + + Prepare Payment... + Prepare Payment... + diff --git a/translations/module-peers-view_ha.ts b/translations/module-peers-view_ha.ts new file mode 100644 index 0000000..96efa04 --- /dev/null +++ b/translations/module-peers-view_ha.ts @@ -0,0 +1,66 @@ + + + + + NetView + + + Peers + Peers + + + + Address + network address (IP) + Adireshi + + + + Start-height: %1 + Fara-tsawo: %1 + + + + ban-score: %1 + Tsame-ci: %1 + + + + initializing connection + Farawa haɗi + + + + Verifying peer + Tabbatar da tsari + + + + Peer for wallet: %1 + Haɗin asusu %1 + + + + Peer for wallet + Peer for wallet + + + + PeersViewModuleInfo + + + Peers View + Peers View + + + + This module provides a view of network servers we connect to often called 'peers'. + This module provides a view of network servers we connect to often called 'peers'. + + + + Network Details + Network Details + + + diff --git a/translations/module-peers-view_pt.ts b/translations/module-peers-view_pt.ts index d8b41fe..e6a44fc 100644 --- a/translations/module-peers-view_pt.ts +++ b/translations/module-peers-view_pt.ts @@ -1,6 +1,6 @@ - + NetView -- 2.54.0 From 51a0c6ab461f93dfef765ba05ef0170683992728 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 3 Jan 2024 20:43:48 +0100 Subject: [PATCH 0737/1428] Fix cancelling the camera usage. --- src/CameraController.cpp | 4 +--- src/QRScanner.cpp | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index e07f92c..3c5b722 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -693,10 +693,8 @@ bool CameraController::visible() const void CameraController::qrScanFinished() { QString resultText; - if (d->m_scanningThread) { + if (d->m_scanningThread) resultText = d->m_scanningThread->text; - logFatal() << " -> " << d->m_scanningThread->text; - } delete d->m_scanningThread; d->m_scanningThread = nullptr; diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index e832d40..0d66c40 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -60,8 +60,6 @@ void QRScanner::setScanType(ScanType type) void QRScanner::finishedScan(const QString &result, ResultSource source) { - if (m_scanResult == result || result.isEmpty()) - return; m_scanResult = result; m_resultSource = source; emit scanResultChanged(); -- 2.54.0 From 0b7587b51b5302dc09d8715c6b2bdd6d18773f80 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 4 Jan 2024 20:37:46 +0100 Subject: [PATCH 0738/1428] Refactor the NetView (peers dialog) This is likely the oldest component in the app and it was really in need of a rewrite. The state of peers is shown much clearer now, we use a proper model in order to avoid the jumping and we use a more safe way of getting at the data. --- guis/desktop/NetView.qml | 60 +++++---- modules/peers-view/NetView.qml | 54 ++++---- src/CMakeLists.txt | 1 - src/NetDataProvider.cpp | 217 +++++++++++++++++++++------------ src/NetDataProvider.h | 58 ++++++--- src/NetPeer.cpp | 123 ------------------- src/NetPeer.h | 76 ------------ src/WalletEnums.h | 11 +- src/main_utils.cpp | 9 +- src/main_utils_android.cpp | 4 +- 10 files changed, 259 insertions(+), 354 deletions(-) delete mode 100644 src/NetPeer.cpp delete mode 100644 src/NetPeer.h diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index 36d0f81..8bbe769 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts import "../Flowee" as Flowee +import Flowee.org.pay; ApplicationWindow { id: root @@ -27,14 +28,14 @@ ApplicationWindow { minimumHeight: 200 width: 400 height: 500 - title: qsTr("Peers (%1)", "", net.peers.length).arg(net.peers.length) + title: qsTr("Peers (%1)", "", listView.count).arg(listView.count) modality: Qt.NonModal flags: Qt.Dialog ListView { id: listView - model: net.peers + model: net clip: true ScrollBar.vertical: ScrollBar { } @@ -51,55 +52,68 @@ ApplicationWindow { width: listView.width height: peerPane.height + 12 color: index % 2 === 0 ? palette.button : palette.base + opacity: { + let validity = model.validity; + if (validity === Wallet.Checking) + return 0.7; + return 1; + } + Label { + text: "(" + model.connectionId + ")" + anchors.right: parent.right + anchors.rightMargin: 10 + y: 20 + } + ColumnLayout { id: peerPane - width: listView.width - 20 + width: listView.width - 40 x: 10 y: 6 Label { - text: modelData.userAgent + text: model.userAgent } Label { - text: qsTr("Address", "network address (IP)") + ": " + modelData.address + text: qsTr("Address", "network address (IP)") + ": " + model.address } RowLayout { height: secondRow.height + spacing: 0 Label { id: secondRow - text: qsTr("Start-height: %1").arg(modelData.startHeight) + text: qsTr("Start-height: %1").arg(model.startHeight) } Label { - text: qsTr("ban-score: %1").arg(modelData.banScore) + text: ", " + qsTr("ban-score: %1").arg(model.banScore) } } Label { id : accountStatus font.bold: true text: { - var id = modelData.segmentId; + var id = model.segment; if (id === 0) { - // not peered yet. - if (modelData.services.Length === 0) - return qsTr("initializing connection") - if (!modelData.headersReceived) - return qsTr("Verifying peer") - return qsTr("Assigning...") + let validity = model.validity; + if (validity === Wallet.Checking) + return "Validating peer"; + if (validity === Wallet.CheckedOk) + return "Validated"; + if (validity === Wallet.KnownGood) + return "Good Peer"; + return ""; // unknown } - var accounts = portfolio.accounts; + var accounts = portfolio.rawAccounts; for (var i = 0; i < accounts.length; ++i) { if (accounts[i].id === id) return qsTr("Peer for wallet: %1").arg(accounts[i].name); } - return qsTr("Peer for initial wallet"); + return "Internal Error"; } - visible: text !== "" } - Flow { - Repeater { - model: modelData.services - delegate: Label { text: modelData } - } + Label { + text: "Downloading!" + visible: model.isDownloading } } } diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index b191823..1b54ac8 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,13 +19,14 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee -import "../mobile"; +import "../mobile" as Mobile; +import Flowee.org.pay; -Page { +Mobile.Page { headerText: qsTr("Peers") ListView { id: listView - model: net.peers + model: net anchors.fill: parent QQC2.ScrollBar.vertical: QQC2.ScrollBar { } @@ -41,7 +42,12 @@ Page { width: listView.width height: peerPane.height + 12 color: index % 2 === 0 ? palette.button : palette.base - opacity: modelData.headersReceived ? 1 : 0.5 + opacity: { + let validity = model.validity; + if (validity === Wallet.Checking) + return 0.7; + return 1; + } ColumnLayout { id: peerPane width: listView.width - 20 @@ -49,48 +55,48 @@ Page { y: 6 Flowee.Label { - text: modelData.userAgent + text: model.userAgent } Flowee.Label { - text: qsTr("Address", "network address (IP)") + ": " + modelData.address + text: qsTr("Address", "network address (IP)") + ": " + model.address } RowLayout { height: secondRow.height + spacing: 0 Flowee.Label { id: secondRow - text: qsTr("Start-height: %1").arg(modelData.startHeight) + text: qsTr("Start-height: %1").arg(model.startHeight) } Flowee.Label { - text: qsTr("ban-score: %1").arg(modelData.banScore) + text: ", " + qsTr("ban-score: %1").arg(model.banScore) } } Flowee.Label { id : accountStatus font.bold: true text: { - var id = modelData.segmentId; + var id = model.segment; if (id === 0) { - // not peered yet. - if (modelData.services.Length === 0) - return qsTr("initializing connection") - if (!modelData.headersReceived) - return qsTr("Verifying peer") - return "" + let validity = model.validity; + if (validity === Wallet.Checking) + return qsTr("Validating peer"); + if (validity === Wallet.CheckedOk) + return qsTr("Validated"); + if (validity === Wallet.KnownGood) + return qsTr("Good Peer"); + return ""; // unknown } - var accounts = portfolio.accounts; + var accounts = portfolio.rawAccounts; for (var i = 0; i < accounts.length; ++i) { if (accounts[i].id === id) return qsTr("Peer for wallet: %1").arg(accounts[i].name); } - return qsTr("Peer for wallet"); + return "Internal Error"; } - visible: text !== "" } - Flow { - Repeater { - model: modelData.services - delegate: Flowee.Label { text: modelData } - } + Flowee.Label { + text: "Downloading!" + visible: model.isDownloading } } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cad1fef..db8a539 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,7 +26,6 @@ set (PAY_SOURCES FloweePay.cpp MenuModel.cpp NetDataProvider.cpp - NetPeer.cpp NewWalletConfig.cpp NotificationManager.cpp Payment.cpp diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index b267fb2..679a81c 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,110 +16,165 @@ * along with this program. If not, see . */ #include "NetDataProvider.h" -#include "NetPeer.h" -#include "FloweePay.h" #include #include +#include #include #include NetDataProvider::NetDataProvider(QObject *parent) - : QObject(parent) + : QAbstractListModel(parent) { - connect (this, SIGNAL(peerDeleted(int)), this, SLOT(deleteNetPeer(int)), Qt::QueuedConnection); // Make this thread-safe + m_refreshTimer.setInterval(1200); + m_refreshTimer.setTimerType(Qt::VeryCoarseTimer); + connect (&m_refreshTimer, SIGNAL(timeout()), this, SLOT(updatePeers())); } - -void NetDataProvider::newPeer(int peerId, const std::string &userAgent, int startHeight, PeerAddress address) +void NetDataProvider::startTimer() { - QMutexLocker l(&m_peerMutex); - NetPeer *newPeer = new NetPeer(peerId, QString::fromStdString(userAgent), startHeight, address); - // assume this interface method is called in a thread that is not the Qt main one. - newPeer->moveToThread(thread()); - newPeer->setParent(this); - m_peers.append(newPeer); - emit peerListChanged(); - - if (m_refreshTimer) - QTimer::singleShot(0, m_refreshTimer, SLOT(start())); + m_refreshTimer.start(); } -void NetDataProvider::lostPeer(int peerId) +void NetDataProvider::newPeer(const std::shared_ptr &peer) { - // this callback is not guarenteed to be made in our thread. - emit peerDeleted(peerId); + QMutexLocker l(&m_peerMutex); + beginInsertRows(QModelIndex(), m_peers.size(), m_peers.size()); + PeerData pd; + pd.peer = peer; + m_peers.push_back(pd); + endInsertRows(); } -void NetDataProvider::deleteNetPeer(int peerId) +int NetDataProvider::rowCount(const QModelIndex &parent) const { - Q_ASSERT(QThread::currentThread() == thread()); // make sure we have no threading issues - QMutexLocker l(&m_peerMutex); - QList peers(m_peers); - for (int i = 0; i < peers.size(); ++i) { - if (peers.at(i)->connectionId() == peerId) { - auto oldPeer = peers.takeAt(i); - m_peers = peers; - emit peerListChanged(); - oldPeer->deleteLater(); - return; - } + if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree + return 0; + QMutexLocker l(&m_peerMutex); + return m_peers.size(); +} + +QVariant NetDataProvider::data(const QModelIndex &index_, int role) const +{ + if (!index_.isValid()) + return QVariant(); + + QMutexLocker l(&m_peerMutex); + const auto index = index_.row(); + if (index < 0 || index >= m_peers.size()) + return QVariant(); + + const auto &peerData = m_peers.at(index); + std::shared_ptr peer = peerData.peer.lock(); + if (peer.get() == nullptr) + return QVariant(); + + switch (role) { + case ConnectionId: + return QVariant::fromValue(peer->connectionId()); + case UserAgent: + return QVariant::fromValue(QString::fromStdString(peer->userAgent())); + case StartHeight: + return QVariant::fromValue(peer->startHeight()); + case NodeValidity: + return QVariant::fromValue(peerData.valid); + case Address: { + QString answer; + const auto &ep = peer->peerAddress().peerAddress(); + if (ep.ipAddress.is_unspecified()) + answer = QString::fromStdString(ep.hostname); + else + answer = QString::fromStdString(ep.ipAddress.to_string()); + answer += QString(":%1").arg(ep.announcePort); + return QVariant(answer); } + case SegmentId: + return QVariant::fromValue(peerData.segment); + case Downloading: + return QVariant::fromValue(peerData.isDownloading); + case PeerHeight: + return QVariant::fromValue(peer->peerHeight()); + case BanScore: + return QVariant::fromValue(peer->peerAddress().punishment()); + } + + return QVariant(); +} + +QHash NetDataProvider::roleNames() const +{ + QHash mapping; + mapping[ConnectionId] = "connectionId"; + mapping[UserAgent] = "userAgent"; + mapping[StartHeight] = "startHeight"; + mapping[NodeValidity] = "validity"; + mapping[Address] = "address"; + mapping[SegmentId] = "segment"; + mapping[Downloading] = "isDownloading"; + mapping[StartHeight] = "startHeight"; + mapping[PeerHeight] = "height"; + mapping[BanScore] = "banScore"; + return mapping; } void NetDataProvider::updatePeers() { - auto &conMan = FloweePay::instance()->p2pNet()->connectionManager(); - QList peers; - { - QMutexLocker l(&m_peerMutex); - peers = m_peers; - } - bool stopTimer = true; - for (auto &p : peers) { - auto peer = conMan.peer(p->connectionId()); - // update 'p' with up to date data from the peer. - p->setRelaysTransactions(peer->relaysTransactions()); - p->setHeadersReceived(peer->receivedHeaders()); + /* + * The peers are managed by the p2p layer and they don't send any events on + * changes, so we just do polling since that's cheap enough. An average wallet + * will not have even 100 connections, making this check very cheap. + * + * One of the main things is that a deleted peer needs to be removed from + * the list and we have some properties that may change over time (like + * its validation state) which we check for. + */ + QMutexLocker l(&m_peerMutex); + int index = 0; + auto iter = m_peers.begin(); + while (iter != m_peers.end()) { + try { + std::shared_ptr peer(iter->peer); - if (peer->privacySegment() == nullptr) - stopTimer = false; - } - if (stopTimer) - m_refreshTimer->stop(); -} + bool changed = false; -void NetDataProvider::punishmentChanged(int peerId) -{ - QMutexLocker l(&m_peerMutex); - QList peers(m_peers); - for (auto &p : peers) { - if (p->connectionId() == peerId) { - p->notifyPunishmentChanged(); - break; + WalletEnums::PeerValidity valid = WalletEnums::UnknownValidity; + if (peer->requestedHeader()) + valid = peer->receivedHeaders() ? WalletEnums::CheckedOk : WalletEnums::Checking; + else + valid = WalletEnums::KnownGood; + if (valid != iter->valid) { + iter->valid = valid; + changed = true; + } + + const bool isDownloading = peer->merkleDownloadInProgress(); + if (iter->isDownloading != isDownloading) { + iter->isDownloading = isDownloading; + changed = true; + } + + if (iter->segment == 0) { + auto segment = peer->privacySegment(); + if (segment) { + iter->segment = segment->segmentId(); + changed = true; + } + } + + if (changed) { + // to change a row, we delete and re-insert it. + beginRemoveRows(QModelIndex(), index, index); + endRemoveRows(); + beginInsertRows(QModelIndex(), index, index); + endInsertRows(); + } + ++index; + ++iter; + } catch (const std::exception &e) { + // the peer has been deleted. + beginRemoveRows(QModelIndex(), index, index); + iter = m_peers.erase(iter); + endRemoveRows(); } } } - -QList NetDataProvider::peers() const -{ - QList answer; - for (auto * const p : m_peers) { - answer.append(p); - } - return answer; -} - -void NetDataProvider::startRefreshTimer() -{ - /* - * Start a timer that checks every second if the peer has changed one of the not-broadcast status. - * When a segment is assigned we can stop the timer. - */ - QMutexLocker l(&m_peerMutex); - assert(m_refreshTimer == nullptr); // can be called only once - m_refreshTimer = new QTimer(this); - connect(m_refreshTimer, SIGNAL(timeout()), this, SLOT(updatePeers())); - m_refreshTimer->setTimerType(Qt::VeryCoarseTimer); - m_refreshTimer->setInterval(950); -} diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index c8f3efb..c4353cb 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,44 +18,62 @@ #ifndef NETDATAPROVIDER_H #define NETDATAPROVIDER_H -#include "NetPeer.h" - #include +#include "WalletEnums.h" -#include +#include #include +#include + +#include +#include class QTimer; -class NetDataProvider : public QObject, public P2PNetInterface +class NetDataProvider : public QAbstractListModel, public P2PNetInterface { Q_OBJECT - Q_PROPERTY(QList peers READ peers NOTIFY peerListChanged) public: explicit NetDataProvider(QObject *parent = nullptr); - // P2PNetInterface - void newPeer(int peerId, const std::string &userAgent, int startHeight, PeerAddress address) override; - void lostPeer(int peerId) override; - void punishmentChanged(int peerId) override; + // the refresh data polling timer. + void startTimer(); - QList peers() const; - void startRefreshTimer(); + // P2PNetInterface interface + void newPeer(const std::shared_ptr &peer) override; -signals: - void peerListChanged(); - void blockHeightChanged(); - void peerDeleted(int id); + enum { + ConnectionId = Qt::UserRole, + UserAgent, + StartHeight, + NodeValidity, + Address, + SegmentId, + Downloading, + PeerHeight, + BanScore + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; private slots: - void deleteNetPeer(int id); void updatePeers(); private: - mutable QMutex m_peerMutex; - QList m_peers; + mutable QRecursiveMutex m_peerMutex; + struct PeerData { + std::weak_ptr peer; + WalletEnums::PeerValidity valid = WalletEnums::UnknownValidity; + bool isDownloading = false; + uint16_t segment = 0; + }; - QTimer *m_refreshTimer = nullptr; + std::vector m_peers; + + // we use some polling to keep the data up-to-date + QTimer m_refreshTimer; }; #endif diff --git a/src/NetPeer.cpp b/src/NetPeer.cpp deleted file mode 100644 index 8a24a63..0000000 --- a/src/NetPeer.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "NetPeer.h" - -NetPeer::NetPeer(int connectionId, const QString &userAgent, int startHeight, PeerAddress address, QObject *parent) - : QObject(parent), - m_userAgent(userAgent), - m_connectionId(connectionId), - m_startHeight(startHeight), - m_address(address) -{ -} - -QString NetPeer::userAgent() const -{ - return m_userAgent; -} - -int NetPeer::connectionId() const -{ - return m_connectionId; -} - -int NetPeer::startHeight() const -{ - return m_startHeight; -} - -int NetPeer::banScore() const -{ - return m_address.punishment(); -} - -uint16_t NetPeer::segmentId() const -{ - return m_address.segment(); -} - -QString NetPeer::address() const -{ - QString answer; - const auto &ep = m_address.peerAddress(); - if (ep.ipAddress.is_unspecified()) - answer = QString::fromStdString(ep.hostname); - else - answer = QString::fromStdString(ep.ipAddress.to_string()); - answer += QString(":%1").arg(ep.announcePort); - return answer; -} - -void NetPeer::notifyPunishmentChanged() -{ - emit banScoreChanged(); -} - -void NetPeer::setHeadersReceived(bool received) -{ - if (received == m_headersReceived) - return; - m_headersReceived = received; - emit headersReceivedChanged(); -} - -bool NetPeer::headersReceived() const -{ - return m_headersReceived; -} - -bool NetPeer::relaysTransactions() const -{ - return m_relaysTransactions; -} - -void NetPeer::setRelaysTransactions(bool newRelaysTransactions) -{ - if (m_relaysTransactions == newRelaysTransactions) - return; - m_relaysTransactions = newRelaysTransactions; - emit relaysTransactionsChanged(); -} - -QStringList NetPeer::services() const -{ - QStringList answer; - if (m_services & 1) - answer << QLatin1String("NETWORK"); - if (m_services & (1 << 2)) - answer << QLatin1String("BLOOM"); - if (m_services & (1 << 4)) - answer << QLatin1String("XTHIN"); - if (m_services & (1 << 5)) - answer << QLatin1String("CASH"); - if (m_services & (1 << 8)) - answer << QLatin1String("CF"); - if (m_services & (1 << 10)) - answer << QLatin1String("NETWORK_LIMITED"); - if (m_services & (1 << 11)) // obsolete, but still used - answer << QLatin1String("EXTVERSION"); - return answer; -} - -void NetPeer::setServices(uint64_t services) -{ - if (m_services == services) - return; - m_services = services; - emit servicesChanged(); -} diff --git a/src/NetPeer.h b/src/NetPeer.h deleted file mode 100644 index 954de6d..0000000 --- a/src/NetPeer.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef PAY_PEER_H -#define PAY_PEER_H - -#include -#include - -class NetPeer : public QObject -{ - Q_OBJECT - Q_PROPERTY(QString userAgent READ userAgent CONSTANT) - Q_PROPERTY(int startHeight READ startHeight CONSTANT) - Q_PROPERTY(int banScore READ banScore NOTIFY banScoreChanged) - Q_PROPERTY(QString address READ address CONSTANT) - Q_PROPERTY(int segmentId READ segmentId CONSTANT) - Q_PROPERTY(QStringList services READ services CONSTANT) - Q_PROPERTY(bool headersReceived READ headersReceived NOTIFY headersReceivedChanged) - Q_PROPERTY(bool relaysTransactions READ relaysTransactions NOTIFY relaysTransactionsChanged) -public: - NetPeer(int connectionId, const QString &userAgent, int startHeight, PeerAddress address, QObject *parent = nullptr); - - QString userAgent() const; - - int connectionId() const; - int startHeight() const; - int banScore() const; - /// the ID that matches us with a certain wallet - uint16_t segmentId() const; - - QString address() const; // ip address, as string - - void notifyPunishmentChanged(); - - void setHeadersReceived(bool received); - bool headersReceived() const; - - bool relaysTransactions() const; - void setRelaysTransactions(bool newRelaysTransactions); - - QStringList services() const; - void setServices(uint64_t services); - -signals: - void banScoreChanged(); - void headersReceivedChanged(); - void relaysTransactionsChanged(); - void servicesChanged(); - -private: - const QString m_userAgent; - const int m_connectionId; - const int m_startHeight; - PeerAddress m_address; - - bool m_headersReceived = false; - bool m_relaysTransactions = false; - uint64_t m_services = 0; -}; - -#endif diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 9bcd2b3..f26a90b 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,6 +70,15 @@ public: Month }; Q_ENUM(GroupingPeriod) + + enum PeerValidity { + UnknownValidity, + KnownGood, + Checking, + CheckedOk + // there is no rejected as those just get kicked. + }; + Q_ENUM(PeerValidity) }; #endif diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 37005af..8e166b1 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -158,12 +158,15 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); - netData->startRefreshTimer(); + + const bool isOffline = cld->parser.isSet(cld->offline); + if (!isOffline) + netData->startTimer(); PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); - if (!cld->parser.isSet(cld->offline) && cld->parser.isSet(cld->connect)) { + if (!isOffline && cld->parser.isSet(cld->connect)) { const int port = cld->parser.isSet(cld->testnet4) ? 28333 : 8333; // add it to the DB, making sure there is at least one. app->p2pNet()->connectionManager().peerAddressDb().addOne( diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 41ba61d..b4dc3e5 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -104,7 +104,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c app->setPaymentProtocolRequest(cld->payRequest.first()); NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); - netData->startRefreshTimer(); + netData->startTimer(); PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("net", netData); -- 2.54.0 From 219f81763b395441e7cc8cd9992e5d7bdd14363f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 4 Jan 2024 22:13:51 +0100 Subject: [PATCH 0739/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 65632f9..f993ec6 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="21" android:versionName="2024.01.0"> diff --git a/src/main.cpp b/src/main.cpp index e8d5659..5ec0edb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2023.11.1"); + qapp.setApplicationVersion("2024.01.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From e7d3edd416e0c37c9702ee050d1fe7f513f0ddb7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 5 Jan 2024 18:19:06 +0100 Subject: [PATCH 0740/1428] Re-introduce the blur for balance After the Qt6 port we lost that, but now this is again supported with a standard QML component. --- guis/desktop/main.qml | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 9203046..cf3ae45 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors @@ -129,7 +130,13 @@ ApplicationWindow { color: "white" showFiat: false fontPixelSize: 28 - opacity: Pay.hideBalance ? 0.2 : 1 + layer.enabled: Pay.hideBalance + layer.effect: MultiEffect { + blurEnabled: true + blur: 1 + blurMultiplier: 0.4 + blurMax: 40 + } } } Label { @@ -142,11 +149,16 @@ ApplicationWindow { text: { if (Pay.hideBalance && Pay.isMainChain) - return "-- " + Fiat.currencyName + return Fiat.formattedPrice(100000000, Fiat.price) return Fiat.formattedPrice(totalBalance.value, Fiat.price) } visible: balanceInHeader.visible opacity: 0.6 + layer.enabled: Pay.hideBalance + layer.effect: MultiEffect { + blurEnabled: true + blur: 1 + } } } @@ -454,11 +466,10 @@ ApplicationWindow { id: balanceDetailsPane property bool showDetails: false width: parent.width - clip: true // avoid the balance overlapping the tabbar. + clip: !Pay.hideBalance // on to avoid the balance overlapping the tabbar. height: balance.height + (showDetails ? extraBalances.height + 10 : 0) Flowee.BitcoinAmountLabel { id: balance - opacity: Pay.hideBalance ? 0.2 : 1 value: { if (isLoading) return 0; @@ -477,6 +488,13 @@ ApplicationWindow { return leftColumn.width / 9 return 27; } + layer.enabled: Pay.hideBalance + layer.effect: MultiEffect { + blurEnabled: true + blur: 1 + blurMultiplier: 0.4 + blurMax: 40 + } } GridLayout { @@ -550,6 +568,11 @@ ApplicationWindow { return Fiat.formattedPrice(balance.value, Fiat.price); } opacity: 0.6 + layer.enabled: Pay.hideBalance + layer.effect: MultiEffect { + blurEnabled: true + blur: 1 + } } Item { width: 1; height: fiatValue.visible ? 10 : 0 } // spacer Item { -- 2.54.0 From 8181b702d2fb418804e6c1bf37ea8d3359abf6f0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 Jan 2024 19:11:20 +0100 Subject: [PATCH 0741/1428] Port this class as well The upsteam change to use smart pointers also applies to this one. We just didn't compile it yet. --- src/NetworkLogClient.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/NetworkLogClient.cpp b/src/NetworkLogClient.cpp index 3c4439e..c22cb1e 100644 --- a/src/NetworkLogClient.cpp +++ b/src/NetworkLogClient.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -85,21 +85,21 @@ void NetworkLogClient::pushLog(int64_t timeMillis, std::string *timestamp, const auto payload = payloadBuilder.message(); // then build the locator - auto &pool = Streaming::pool(m_logPath.size() + 20); - pool.write(m_logPath); - pool.write(std::to_string(timeMillis)); + auto pool = Streaming::pool(m_logPath.size() + 20); + pool->write(m_logPath); + pool->write(std::to_string(timeMillis)); if (timeMillis == m_lastTimestamp) { // if we have multiple items with the same timetamp, differntiate to avoid the remote thinking we repeat outselves - pool.write("/"); - pool.write(std::to_string(m_milliIndex++)); + pool->write("/"); + pool->write(std::to_string(m_milliIndex++)); } else { m_lastTimestamp = timeMillis; m_milliIndex = 1; } - auto locator = pool.commit(); + auto locator = pool->commit(); - pool.reserve(payload.body().size() + locator.size() + 20); + pool->reserve(payload.body().size() + locator.size() + 20); Streaming::MessageBuilder builder(pool); builder.add(Locator, locator); builder.add(Content, payload.body()); -- 2.54.0 From 52b33acb0c738fd86373ae80af712ccbe054c29d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 Jan 2024 21:33:00 +0100 Subject: [PATCH 0742/1428] [Android] when ipv6 available, connect to such peers This adds (the first!) an actual Java class to do the checking which interfaces are available and we then instruct the AddressDB to pick addresses matching that. In other words, when the Android device has a functional IPv4 network interface, we will try to connect to peers on that IP version. Same with IPv6. Both can be active at the same time. --- android/java/org/flowee/pay/Networks.java | 70 +++++++++++++++++++++++ src/FloweePay.cpp | 15 ++++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 android/java/org/flowee/pay/Networks.java diff --git a/android/java/org/flowee/pay/Networks.java b/android/java/org/flowee/pay/Networks.java new file mode 100644 index 0000000..f6930de --- /dev/null +++ b/android/java/org/flowee/pay/Networks.java @@ -0,0 +1,70 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.flowee.pay; + +import android.content.Context; +import java.net.*; +import java.util.Enumeration; +import java.util.List; + +public class Networks +{ + /** + * This method figures out if we seem to have support for certain networks. + * + * We return a flags object with 1 for IPv4 availability plus 2 for v6. + */ + public static int networkSupport() { + boolean ipv4Available = false; + boolean ipv6Available = false; + + try { + Enumeration networking = NetworkInterface.getNetworkInterfaces(); + while (networking.hasMoreElements()) { + NetworkInterface i = networking.nextElement(); + if (i.isLoopback()) + continue; + if (!i.isUp()) + continue; + List addresses = i.getInterfaceAddresses(); + for (InterfaceAddress address : addresses) { + InetAddress ip = address.getAddress(); + try { + Inet6Address ip6 = (Inet6Address) ip; + if (!ip6.isLinkLocalAddress()) ipv6Available = true; + } catch (ClassCastException e) { } + try { + Inet4Address ip4 = (Inet4Address) ip; + if (!ip4.isLinkLocalAddress()) ipv4Available = true; + } catch (ClassCastException e) { } + } + } + } catch (SocketException e) { + // sane default if we don't have permission to check stuff.. + ipv4Available = true; + } + + // simple bitfield. + int answer = 0; + if (ipv4Available) + answer += 1; + if (ipv6Available) + answer += 2; + return answer; + } +} diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 654de53..dbb618f 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1426,6 +1426,19 @@ DownloadManager *FloweePay::p2pNet() m_downloadManager->connectionManager().setUserAgent(useragent.toStdString()); emit headerChainHeightChanged(); emit expectedChainHeightChanged(); + +#ifdef TARGET_OS_Android + // ask the Android system which interfaces there are; + QJniEnvironment env; + jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); + quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); + if (flags != 0) { + logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; + auto &addressDb = p2pNet()->connectionManager().peerAddressDb(); + addressDb.setSupportIPv4Net((flags & 1) == 1); + addressDb.setSupportIPv6Net((flags & 2) == 2); + } +#endif } return m_downloadManager.get(); } -- 2.54.0 From 6161a43dacdc77eb967f2bf36b4ae53b82eca786 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 Jan 2024 21:33:28 +0100 Subject: [PATCH 0743/1428] Add a statistics page to the Netview --- guis/desktop.qrc | 1 + guis/desktop/AddressDbStats.qml | 106 +++++++++++++++++++++++++ guis/desktop/SettingsPane.qml | 10 +++ modules/peers-view/NetView.qml | 10 +++ modules/peers-view/StatsPage.qml | 66 +++++++++++++++ modules/peers-view/peers-page-data.qrc | 1 + src/NetDataProvider.cpp | 58 ++++++++++++++ src/NetDataProvider.h | 37 ++++++++- 8 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 guis/desktop/AddressDbStats.qml create mode 100644 modules/peers-view/StatsPage.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 86c3c54..c7ca36e 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -32,5 +32,6 @@ desktop/PaymentTweakingPanel.qml desktop/WalletEncryptionStatus.qml desktop/AccountConfigMenu.qml + desktop/AddressDbStats.qml diff --git a/guis/desktop/AddressDbStats.qml b/guis/desktop/AddressDbStats.qml new file mode 100644 index 0000000..4077b7d --- /dev/null +++ b/guis/desktop/AddressDbStats.qml @@ -0,0 +1,106 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee + +ApplicationWindow { + id: root + visible: false + minimumWidth: 200 + minimumHeight: 200 + width: 400 + height: 300 + title: qsTr("IP Addresses"); + modality: Qt.NonModal + flags: Qt.Dialog + + property QtObject stats: net.createStats(root); + + GridLayout { + columns: 2 + rowSpacing: 10 + columnSpacing: 6 + width: parent.width - 20 + x: 10 + y: 10 + + Label { + text: qsTr("Total found") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.count; + Layout.fillWidth: true + } + Label { + text: qsTr("Tried") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.everConnected; + } + Label { + text: qsTr("Punished count") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.partialBanned; + } + Label { + text: qsTr("Banned count") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.banned; + } + + Label { + text: qsTr("IP-v4 count") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.count - root.stats.ipv6Addresses + font.strikeout: root.stats.usesIPv4 === false + } + Label { + text: qsTr("IP-v6 count") + ":" + Layout.alignment: Qt.AlignRight + } + Label { + text: root.stats.ipv6Addresses + font.strikeout: root.stats.usesIPv6 === false + } + } + + footer: Item { + width: parent.width + height: closeButton.height + 20 + + Button { + id: closeButton + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + text: qsTr("Close") + onClicked: root.visible = false + } + } +} diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index 689f25d..fd1373d 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -173,5 +173,15 @@ Pane { netView.item.show(); } } + Item { width: 1; height: 1 } // empty row + + Button { + Layout.columnSpan: 2 + text: qsTr("Address Stats") + onClicked: { + netView.source = "./AddressDbStats.qml" + netView.item.show(); + } + } } } diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index 1b54ac8..dc6a7d7 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -23,7 +23,17 @@ import "../mobile" as Mobile; import Flowee.org.pay; Mobile.Page { + id: root headerText: qsTr("Peers") + + property QtObject statsAction : QQC2.Action { + text: qsTr("Statistics") + onTriggered: thePile.push("./StatsPage.qml", {"stats": net.createStats(root) }); + } + + menuItems: [statsAction]; + + ListView { id: listView model: net diff --git a/modules/peers-view/StatsPage.qml b/modules/peers-view/StatsPage.qml new file mode 100644 index 0000000..a7c3489 --- /dev/null +++ b/modules/peers-view/StatsPage.qml @@ -0,0 +1,66 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import "../mobile" as Mobile + +Mobile.Page { + id: root + headerText: qsTr("IP-Address Statistics"); + + required property QtObject stats; + + Column { + width: parent.width + spacing: 10 + + Mobile.PageTitledBox { + title: qsTr("Counts") + width: parent.width + Flowee.Label { + text: qsTr("Total found") + ": " + root.stats.count; + } + Flowee.Label { + text: qsTr("Tried") + ": " + root.stats.everConnected; + } + } + Mobile.PageTitledBox { + title: qsTr("Misbehaving IPs") + width: parent.width + Flowee.Label { + text: qsTr("Bad") + ": " + (root.stats.partialBanned - root.stats.banned); + } + Flowee.Label { + text: qsTr("Banned") + ": " + root.stats.banned; + } + } + + Mobile.PageTitledBox { + title: qsTr("Network") + width: parent.width + Flowee.Label { + text: "IP-v4" + ": " + (root.stats.count - root.stats.ipv6Addresses) + font.strikeout: root.stats.usesIPv4 === false + } + Flowee.Label { + text: "IP-v6" + ": " + root.stats.ipv6Addresses + font.strikeout: root.stats.usesIPv6 === false + } + } + } +} diff --git a/modules/peers-view/peers-page-data.qrc b/modules/peers-view/peers-page-data.qrc index 6b47bd1..2496c66 100644 --- a/modules/peers-view/peers-page-data.qrc +++ b/modules/peers-view/peers-page-data.qrc @@ -1,5 +1,6 @@ NetView.qml + StatsPage.qml diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 679a81c..1e6dc86 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "FloweePay.h" #include "NetDataProvider.h" #include #include @@ -23,6 +24,56 @@ #include #include +BasicAddressStats::BasicAddressStats(const AddressDBStats &stats, QObject *parent) + : QObject(parent), + m_count(stats.count), + m_banned(stats.banned), + m_partialBanned(stats.partialBanned), + m_ipv6Addresses(stats.ipv6Addresses), + m_everConnected(stats.everConnected), + m_usesIPv4(stats.usesIPv4), + m_usesIPv6(stats.usesIPv6) +{ +} + +int BasicAddressStats::count() const +{ + return m_count; +} + +int BasicAddressStats::banned() const +{ + return m_banned; +} + +int BasicAddressStats::partialBanned() const +{ + return m_partialBanned; +} + +int BasicAddressStats::ipv6Addresses() const +{ + return m_ipv6Addresses; +} + +int BasicAddressStats::everConnected() const +{ + return m_everConnected; +} + +bool BasicAddressStats::usesIPv4() const +{ + return m_usesIPv4; +} + +bool BasicAddressStats::usesIPv6() const +{ + return m_usesIPv6; +} + + +// //////////////////////////////////////////////////////////////////////////////////// + NetDataProvider::NetDataProvider(QObject *parent) : QAbstractListModel(parent) { @@ -117,6 +168,13 @@ QHash NetDataProvider::roleNames() const return mapping; } +QObject *NetDataProvider::createStats(QObject *parent) const +{ + return new BasicAddressStats( + FloweePay::instance()->p2pNet()->connectionManager().peerAddressDb().createStats(), + parent); +} + void NetDataProvider::updatePeers() { /* diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index c4353cb..a74a652 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -18,9 +18,11 @@ #ifndef NETDATAPROVIDER_H #define NETDATAPROVIDER_H -#include #include "WalletEnums.h" +#include +#include + #include #include #include @@ -30,6 +32,37 @@ class QTimer; +class BasicAddressStats : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count CONSTANT FINAL) + Q_PROPERTY(int banned READ banned CONSTANT FINAL) + Q_PROPERTY(int partialBanned READ partialBanned CONSTANT FINAL) + Q_PROPERTY(int ipv6Addresses READ ipv6Addresses CONSTANT FINAL) + Q_PROPERTY(int everConnected READ everConnected CONSTANT FINAL) + Q_PROPERTY(bool usesIPv4 READ usesIPv4 CONSTANT FINAL) + Q_PROPERTY(bool usesIPv6 READ usesIPv6 CONSTANT FINAL) +public: + explicit BasicAddressStats(const AddressDBStats &stats, QObject *parent = nullptr); + + int count() const; + int banned() const; + int partialBanned() const; + int ipv6Addresses() const; + int everConnected() const; + bool usesIPv4() const; + bool usesIPv6() const; + +private: + const int m_count; + const int m_banned; + const int m_partialBanned; + const int m_ipv6Addresses; + const int m_everConnected; + const bool m_usesIPv4; + const bool m_usesIPv6; +}; + class NetDataProvider : public QAbstractListModel, public P2PNetInterface { Q_OBJECT @@ -58,6 +91,8 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; + Q_INVOKABLE QObject *createStats(QObject *parent) const; + private slots: void updatePeers(); -- 2.54.0 From f1ad2bf6cccb3c802ef075feb9fad58afe710a76 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 6 Jan 2024 23:12:38 +0100 Subject: [PATCH 0744/1428] Starting 2024.01.1 --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index f993ec6..523acd8 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="22" android:versionName="2024.01.1"> diff --git a/src/main.cpp b/src/main.cpp index 5ec0edb..7741cf8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.01.0"); + qapp.setApplicationVersion("2024.01.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From f5e767afb4ca1b54a59ca6d96c6afb0ddb09f457 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 7 Jan 2024 12:30:57 +0100 Subject: [PATCH 0745/1428] Finished up Hausa translations (Crowdin) --- translations/floweepay-common_ha.ts | 40 ++--- translations/floweepay-desktop_ha.ts | 182 ++++++++++---------- translations/floweepay-mobile_ha.ts | 74 ++++---- translations/module-build-transaction_ha.ts | 42 ++--- translations/module-peers-view_ha.ts | 10 +- 5 files changed, 174 insertions(+), 174 deletions(-) diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts index 49412c6..b5e10a2 100644 --- a/translations/floweepay-common_ha.ts +++ b/translations/floweepay-common_ha.ts @@ -162,23 +162,23 @@ %1 minutes ago relative time stamp - Minti daya da yawuce - Minti 1 da ya wuce + %1 da yawuce + %1 minti da yawuce ½ hour ago timestamp - Rabin awa da ya wuce + ½ awa da ya wuce %1 hours ago timestamp - Awa daya da ya wuce - Awa 1 da ta wuce + %1 Awa da ya wuce + %1 Awa da ya wuce @@ -247,7 +247,7 @@ %1 new transactions across %2 wallets found (%3) - sabbin ma'amaloli a cikin asusun da aka samu + %1 sabbin ma'amaloli a cikin %2 asusun da aka samu (%3) @@ -258,8 +258,8 @@ %1 new transactions found (%2) - sababbin ma'amaloli akasamu - sababbin ma'amaloli aka samu + %1 sababbin ma'amaloli akasamu (%2) + %1 sababbin ma'amaloli aka samu (%2) @@ -324,13 +324,13 @@ Change #%1 - Chanji + Chanji #%1 Main #%1 - Mafarin + Mafarin #%1 @@ -338,15 +338,15 @@ Unconfirmed - Ba a tabbatar ba + Ba'a tabbatar ba %1 hours age, like: hours old - Awa - Awowi + %1 awowi + %1 Awowi @@ -354,8 +354,8 @@ %1 days age, like: days old - Ranaku - Ranaku + %1 Ranaku + %1 Ranaku @@ -363,8 +363,8 @@ %1 weeks age, like: weeks old - makonni - Makonni + %1 makonni + %1 Makonni @@ -372,14 +372,14 @@ %1 months age, like: months old - Watanni - Watanni + %1 Watanni + %1 Watanni Change #%1 - Chanji daya + Chanji #%1 diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts index 3c5945c..a09a641 100644 --- a/translations/floweepay-desktop_ha.ts +++ b/translations/floweepay-desktop_ha.ts @@ -131,7 +131,7 @@ Seed format - Seed format + Tsarin iri @@ -167,9 +167,9 @@ Peers (%1) - + + Takwarori (%1) Takwarori (%1) - Peers (%1) @@ -315,7 +315,7 @@ This ensures only one private key will need to be backed up BIP 39 seed-phrase (interpreted as Electrum format) description of type - BIP 39 seed-phrase (interpreted as Electrum format) + BIP 39 zuriyar jimla (an fassara shi azaman Electrum) @@ -327,7 +327,7 @@ This ensures only one private key will need to be backed up Electrum seed-phrase description of type - Electrum seed-phrase + Zuriyar jimlar Electrum @@ -359,12 +359,12 @@ Change will come back to the imported key. Old Electrum Phrase - Old Electrum Phrase + Tsohon Zuriyar jimlar Electrum When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Lokacin gano Electrum ya gaza, kuma kun tabbata an ƙirƙira shi a cikin wallet ɗin, kunna wannan zaɓi. @@ -634,34 +634,34 @@ Change will come back to the imported key. Send - Send + Aika Destination - Destination + Madakata Max available The maximum balance available - Max available + Mafi girman samuwa %1 to %2 summary text to pay X-euro to address M - %1 to %2 + %1 to %2 Enter Bitcoin Cash Address - Enter Bitcoin Cash Address + Shigar da Adireshin Kuɗi na Bitcoin Copy Address - Copy Address + Kwafi Adireshi @@ -671,22 +671,22 @@ Change will come back to the imported key. Max - Max + Matsakaici This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? Continue - Continue + Ci gaba Cancel - Cancel + Soke @@ -697,41 +697,41 @@ Change will come back to the imported key. Selected %1 %2 in %3 coins selected 2 BCH in 5 coins - - Selected %1 %2 in %3 coins - Selected %1 %2 in %3 coins + + An zaɓi %1 %2 a cikin %3 tsabar kudi + An zaɓi %1 %2 a cikin %3 tsabar kudi Total Number of coins - Total + Jimilla Needed - Needed + Ake Bukata Selected - Selected + An zaɓa Value - Value + Daraja Locked coins will never be used for payments. Right-click for menu. - Locked coins will never be used for payments. Right-click for menu. + Kullallun tsabar kudi ba za a taɓa amfani da su don biyan kuɗi ba. Danna-dama don menu. Age - Age + Shekaru @@ -822,84 +822,84 @@ Change will come back to the imported key. Protect your wallet with a password - Protect your wallet with a password + Kare asusun ku da kalmar sirri Pin to Pay - Pin to Pay + Matsa lamba don biya Protect your funds pin to pay - Protect your funds + Kare kuɗin ku Fully open, except for sending funds pin to pay - Fully open, except for sending funds + Cikakken buɗewa, Sai dai don aika kudi Keeps in sync pin to pay - Keeps in sync + Yana ci gaba da daidaitawa Pin to Open - Pin to Open + Pin don Buɗe Protect your entire wallet pin to open - Protect your entire wallet + Kare dukkan asusun ku Balance and history protected pin to open - Balance and history protected + Ma'auni da tarihi a tsare suke Requires Pin to view, sync or pay pin to open - Requires Pin to view, sync or pay + Da buƙatar Pin don dubawa, daidaitawa ko biya Make "%1" wallet require a pin to pay - Make "%1" wallet require a pin to pay + Yi "%1" asusu yana buƙatar Pin don biya Make "%1" wallet require a pin to open - Make "%1" wallet require a pin to open + Yi "%1" asusu yana buƙatar Pin don buɗewa Wallet already has pin to open applied - Wallet already has pin to open applied + Asusun tuni yana da Pin don buɗewa Wallet already has pin to pay applied - Wallet already has pin to pay applied + Asusun tuni yana da Pin don biya Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. - Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. + Asusun ku zai sami rufaffen ɓangarori kuma biyan kuɗi zai yi wuya ba tare da kalmar sirri ba. Idan babu ajiyar wannan asusun ɗin, fara yin ɗaya. Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. - Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. + Cikakken asusun ku yana ɓoye, domin buɗe shi za'a buƙaci kalmar sirri. Idan ba ku da ajiyar wannan asusun, fara yin ɗaya. @@ -909,17 +909,17 @@ Change will come back to the imported key. Wallet - Wallet + Asusu Encrypt - Encrypt + Rufewa Invalid password to open this wallet - Invalid password to open this wallet + Kalmar sirri mara inganci don buɗe wannan asusu @@ -929,17 +929,17 @@ Change will come back to the imported key. Repeat password - Repeat password + Maimaita kalmar sirri Please confirm the password by entering it again - Please confirm the password by entering it again + Da fatan za a tabbatar da kalmar wucewa ta hanyar sake shigar da shi Typed passwords do not match - Typed passwords do not match + Rubutun kalmomin shiga ba su dace ba @@ -947,18 +947,18 @@ Change will come back to the imported key. Pin to Pay - Pin to Pay + Matsa lamba don biya Pin to Open - Pin to Open + Pin don Buɗe (Opened) Wallet is decrypted - (Opened) + (An buɗe) @@ -966,37 +966,37 @@ Change will come back to the imported key. Miner Reward - Miner Reward + Ladan Ma'adinai Fused - Fused + Fuskanci Received - Received + An samu Moved - Moved + Motsa Sent - Sent + An aika rejected - rejected + an ƙii unconfirmed - unconfirmed + ba'a tabbatar ba @@ -1004,50 +1004,50 @@ Change will come back to the imported key. Copy transaction-ID - Copy transaction-ID + Kwafi shaidar ma'amala-ID Status - Status + Matsayi rejected - rejected + an ƙii unconfirmed - unconfirmed + ba'a tabbatar ba %1 confirmations (mined in block %2) - - %1 confirmations (mined in block %2) - %1 confirmations (mined in block %2) + + %1 tabbatarwa (ma'adinai a tubali %2) + %1 tabbatarwa (ma'adinai a tubali %2) Copy block height - Copy block height + Kwafi tsayin tubali Fees - Fees + Kudin Size - Size + Girman %1 bytes - + %1 bytes %1 bytes @@ -1055,18 +1055,18 @@ Change will come back to the imported key. Inputs - Inputs + Abubuwan shigarwa Copy Address - Copy Address + Kwafi Adireshi Outputs - Outputs + Abubuwan da aka fitar @@ -1074,32 +1074,32 @@ Change will come back to the imported key. Activity - Activity + Ayyuka Archived wallets do not check for activities. Balance may be out of date. - Archived wallets do not check for activities. Balance may be out of date. + Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya tsufa. Unarchive - Unarchive + Cire Takardu This wallet needs a password to open. - This wallet needs a password to open. + Wannan asusun tana buƙatar kalmar sirri don buɗewa. Password: - Password: + Kalmar wucewa: Invalid password - Invalid password + Kalmar shiga mara inganci @@ -1109,69 +1109,69 @@ Change will come back to the imported key. Send - Send + Aika Receive - Receive + Karɓa Balance - Balance + Sauran kudi Main balance (money), non specified - Main + Babban Unconfirmed balance (money) - Unconfirmed + Ba'a tabbatar ba Immature balance (money) - Immature + Rashin cika 1 BCH is: %1 - 1 BCH is: %1 + 1 BCH shi ne: %1 Network status - Network status + Matsayin hanyar sadarwa Offline - Offline + Baya kan na'ura Add Bitcoin Cash wallet - Add Bitcoin Cash wallet + Sanya asusun Bitcoin Cash Archived wallets [%1] Arg is wallet count - - Archived wallets [%1] - Archived wallets [%1] + + Asusun da aka adana [%1] + Asusun da aka adana [%1] Preparing... - Preparing... + Shiryawa... diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts index c27c6b0..d1e409f 100644 --- a/translations/floweepay-mobile_ha.ts +++ b/translations/floweepay-mobile_ha.ts @@ -953,7 +953,7 @@ This ensures only one private key will need to be backed up SLIDE TO SEND - TURA DON AIKA + Tura don Aika @@ -981,12 +981,12 @@ This ensures only one private key will need to be backed up OR - OR + Ko Add a different wallet - Add a different wallet + Ƙara wani asusu na daban @@ -999,7 +999,7 @@ This ensures only one private key will need to be backed up Transaction Hash - Transaction Hash + Zanta Ma'amala @@ -1009,81 +1009,81 @@ This ensures only one private key will need to be backed up Unconfirmed - Unconfirmed + Ba a tabbatar ba Mined at - Mined at + An haƙo daga %1 blocks ago - - %1 blocks ago - %1 blocks ago + + %1 tubalan da suka wuce + %1 Tubalin da suka gabata Transaction comment - Transaction comment + Sharhin Ma'amala Size: %1 bytes - Size: %1 bytes + Girma: %1 bytes Coinbase - Coinbase + Coinbase Fees paid - Fees paid + An biya kudade %1 Satoshi / 1000 bytes - %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes Fused from my addresses - Fused from my addresses + An haɗe daga adiresoshin na Sent from my addresses - Sent from my addresses + An aiko daga adiresoshin na Sent from addresses - Sent from addresses + An aiko daga adiresoshi Copy Address - Copy Address + Kwafi Adireshi Fused into my addresses - Fused into my addresses + An haɗe cikin adiresoshin na Received at addresses - Received at addresses + An karɓa a adiresoshi Received at my addresses - Received at my addresses + An karɓa a adireshi na @@ -1091,71 +1091,71 @@ This ensures only one private key will need to be backed up Transaction is rejected - Transaction is rejected + An ƙi ma'amalar ciniki Processing - Processing + Sarrafawa Mined - Mined + Haƙar ma'adinai %1 blocks ago Confirmations - - %1 blocks ago - %1 blocks ago + + %1 Tubalin da suka gabata + %1 tubalan da suka wuce Miner Reward - Miner Reward + Ladan Ma'adinai Fees - Fees + Kudin Received - Received + An samu Payment to self - Payment to self + Biyan kuɗi ga kai Sent - Sent + An aika Holds a token - Holds a token + Rike alama Sent to - Sent to + An aika zuwa Value now - Value now + Daraja yanzu Value then - Value then + Daraja zuwa ga @@ -1168,7 +1168,7 @@ This ensures only one private key will need to be backed up Enter your wallet passcode - Enter your wallet passcode + Shiga da lambar wucewar asusu diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts index f11ae40..9fcfb72 100644 --- a/translations/module-build-transaction_ha.ts +++ b/translations/module-build-transaction_ha.ts @@ -6,17 +6,17 @@ Create Transactions - Create Transactions + Ƙirƙiri Ma'amaloli This module allows building more powerful transactions in one simple user interface. - This module allows building more powerful transactions in one simple user interface. + Wannan tsarin yana ba da damar gina ƙarin ma'amaloli masu ƙarfi a cikin sauƙin mai amfani guda ɗaya. Build Transaction - Build Transaction + Gina Ma'amala @@ -24,19 +24,19 @@ Build Transaction - Build Transaction + Gina Ma'amala Building Error error during build - Building Error + Kuskuren gini Add Payment Detail page title - Add Payment Detail + Ƙara Bayanin Biyan Kuɗi @@ -47,23 +47,23 @@ an address to send money to - an address to send money to + Adireshin da za'a aika kudi zuwa Confirm Sending confirm we want to send the transaction - Confirm Sending + Tabbatar da Aika TXID - TXID + TXID Copy transaction-ID - Copy transaction-ID + Kwafi shaidar ma'amala-ID @@ -94,30 +94,30 @@ Destination - Destination + Madakata unset indication of empty - unset + Rashin saitawa invalid address is not correct - invalid + Ba daidai ba Copy Address - Copy Address + Kwafi Adireshi Edit Destination - Edit Destination + Gyara Madakata @@ -128,7 +128,7 @@ Bitcoin Cash Address - Bitcoin Cash Address + Adireshin Kuɗi na Bitcoin @@ -143,22 +143,22 @@ This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? I am certain - I am certain + Na tabbata Drag to Edit - Drag to Edit + Jawo don Gyarawa Drag to Delete - Drag to Delete + Jawo don gogewa @@ -168,7 +168,7 @@ Prepare Payment... - Prepare Payment... + Shirya Biya... diff --git a/translations/module-peers-view_ha.ts b/translations/module-peers-view_ha.ts index 96efa04..df60e4d 100644 --- a/translations/module-peers-view_ha.ts +++ b/translations/module-peers-view_ha.ts @@ -6,7 +6,7 @@ Peers - Peers + Takwarori @@ -42,7 +42,7 @@ Peer for wallet - Peer for wallet + Takwaran asusu @@ -50,17 +50,17 @@ Peers View - Peers View + Takwarorin duba This module provides a view of network servers we connect to often called 'peers'. - This module provides a view of network servers we connect to often called 'peers'. + Wannan tsarin yana ba da ra'ayi na sabar cibiyar sadarwa da muke haɗawa da yawa ana kiranta 'takwarori'. Network Details - Network Details + Matsayin hanyar sadarwa -- 2.54.0 From 39232e379c09afd3697e99c3575399ddebf82388 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 9 Jan 2024 21:04:28 +0100 Subject: [PATCH 0746/1428] Add the Hausa translator here --- guis/mobile/About.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 8c61b96..ede19e3 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -71,6 +71,8 @@ Deutsc
Georg Engelmann
Español
Gerard H.R.(devperate)
+
Hausa
+
Ibrahim Rabiu
Nederlands
Tom Zander
Polski
-- 2.54.0 From 4c573f88d6e3efa813c4b1d3815e79b24170fd82 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 13 Jan 2024 15:47:59 +0100 Subject: [PATCH 0747/1428] Minor fixes --- CMakeLists.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index feeef30..f19b5b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # This file is part of the Flowee project -# Copyright (C) 2020-2022 Tom Zander +# Copyright (C) 2020-2024 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ # along with this program. If not, see . cmake_minimum_required(VERSION 3.19) -project(flowee_pay VERSION 0.2 LANGUAGES CXX) +project(flowee_pay VERSION 1.0 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) @@ -49,9 +49,7 @@ else () find_package(ZXing REQUIRED) endif() find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) -if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) -endif() +include_directories(${Boost_INCLUDE_DIRS}) function(download_file url path) if (NOT EXISTS "${path}") -- 2.54.0 From f63765a70ca1555a14eb81e8fa89a334f9c3629c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 13 Jan 2024 17:39:46 +0100 Subject: [PATCH 0748/1428] Work harder to make headerSyncComplete get called A wallet might get stuck in silly situations like starting up when there is no block for a long time and then not actually being online at the moment a new block gets mined. This fix makes sure that at startup we 'unstuck' such a wallet without there being a need for a new block to be mined while the app is running. --- src/FloweePay.cpp | 3 +++ src/Wallet.cpp | 22 ++++++++++++++++++---- src/Wallet.h | 7 ++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index dbb618f..8a12e64 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -386,6 +386,9 @@ void FloweePay::init() connectToWallet(w); logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId(); lastOpened = w; + + // help make sure newly started wallets start synching. + w->checkHeaderSyncComplete(p2pNet()->blockchain()); } catch (const std::runtime_error &e) { logWarning() << "Wallet load failed:" << e; lastOpened = nullptr; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d7c8c04..b5a9d5f 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1411,12 +1411,12 @@ void Wallet::headerSyncComplete() * Find the matching block and update the bloom filter. */ + const Blockchain &blockchain = FloweePay::instance()->p2pNet()->blockchain(); bool changedOne = false; for (auto iter = m_walletSecrets.begin(); iter != m_walletSecrets.end(); ++iter) { if (iter->second.initialHeight > 10000000) { // this is a time based height, lets resolve it to a real height. - const Blockchain &blockchain = FloweePay::instance()->p2pNet()->blockchain(); - iter->second.initialHeight = blockchain.blockHeightAtTime(iter->second.initialHeight); + iter->second.initialHeight = blockchain.blockHeightAtTime(iter->second.initialHeight) - 1; m_secretsChanged = true; changedOne = true; } @@ -1427,7 +1427,21 @@ void Wallet::headerSyncComplete() rebuildBloom(ForceBuild); // make the wallet initial sync also show something sane. if (m_segment) - m_segment->blockSynched(FloweePay::instance()->p2pNet()->blockHeight()); + m_segment->blockSynched(blockchain.height()); + } +} + +// called on startup to ensure we don't get stuck waiting for tip. +void Wallet::checkHeaderSyncComplete(const Blockchain &blockchain) +{ + if (m_walletSecrets.empty()) + return; + auto timestamp = m_walletSecrets.begin()->second.initialHeight; + if (timestamp > 10000000) { + // is a time based height + auto block = blockchain.block(blockchain.height()); + if (block.nTime >= timestamp) + headerSyncComplete(); } } diff --git a/src/Wallet.h b/src/Wallet.h index 512b7a1..2c6bdb2 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ class WalletInfoObject; class TransactionInfo; class WalletKeyView; +class Blockchain; namespace P2PNet { struct Notification; @@ -111,6 +112,10 @@ public: /// Let the wallet know that it is up-to-date to \a height void setLastSynchedBlockHeight(int height) override; void headerSyncComplete() override; + /** + * Check if the headers are high enough for our usage. + */ + void checkHeaderSyncComplete(const Blockchain &blockchain); PrivacySegment *segment() const; -- 2.54.0 From 9d47c69b083c698077a752e3e0fee0fa415b0d30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 13 Jan 2024 18:08:39 +0100 Subject: [PATCH 0749/1428] Fix backup screen showing incorrect start-height. It now correctly shows the blockheight that the wallet was created at, which makes sense to backup. --- guis/mobile/AccountPageListItem.qml | 2 +- src/AccountInfo.cpp | 12 +++++++++++- src/AccountInfo.h | 14 +++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 3eb46cc..1a75144 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -155,7 +155,7 @@ QQC2.Control { PageTitledBox { title: qsTr("Starting Height", "height refers to block-height") - Flowee.LabelWithClipboard { text: root.account.initialBlockHeight } + Flowee.LabelWithClipboard { text: root.account.accountStartBlockHeight } } PageTitledBox { diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index bc01d7d..bd80557 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -33,13 +33,18 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) : QObject(parent), m_wallet(wallet), m_config(m_wallet), - m_initialBlockHeight(wallet->segment()->lastBlockSynched()) + m_initialBlockHeight(wallet->segment()->lastBlockSynched()), + m_accountStartBlockHeight(wallet->segment()->firstBlock()) { connect(wallet, SIGNAL(utxosChanged()), this, SIGNAL(utxosChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(balanceChanged()), this, SLOT(balanceHasChanged()), Qt::QueuedConnection); connect(wallet, &Wallet::lastBlockSynchedChanged, m_wallet, [=]() { if (m_initialBlockHeight < 0) m_initialBlockHeight = m_wallet->segment()->lastBlockSynched(); + if (m_accountStartBlockHeight < 0) { + m_accountStartBlockHeight = m_wallet->segment()->firstBlock(); + emit accountStartBlockHeightChanged(); + } emit lastBlockSynchedChanged(); emit timeBehindChanged(); }, Qt::QueuedConnection); @@ -223,6 +228,11 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +int AccountInfo::accountStartBlockHeight() const +{ + return m_accountStartBlockHeight; +} + bool AccountInfo::isPrivate() const { assert(m_config.isValid()); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 95cd6f6..2bd967e 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +37,13 @@ class AccountInfo : public QObject Q_PROPERTY(int historicalOutputCount READ historicalOutputCount NOTIFY utxosChanged) Q_PROPERTY(int id READ id CONSTANT) Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) + + /** + * This is the oldest block that this account ever might have seen transactions at. + * Essentially the creation date. + */ + Q_PROPERTY(int accountStartBlockHeight READ accountStartBlockHeight NOTIFY accountStartBlockHeightChanged FINAL) + /** * This is the first block that on this instantiation we need to sync. * This property is useful to determine how much we need to sync this session. @@ -174,6 +181,9 @@ public: bool isPrivate() const; void setIsPrivate(bool newIsPrivate); + // the first blockheight ever used for this account + int accountStartBlockHeight() const; + signals: void balanceChanged(); void utxosChanged(); @@ -189,6 +199,7 @@ signals: void instaPayAllowedChanged(); void instaPayLimitChanged(const QString ¤cyCode); void neverEmitted(); // to silence the lambs^Warnings + void accountStartBlockHeightChanged(); // for the benefit of the portfolio data provider void isPrivateChanged(); @@ -205,6 +216,7 @@ private: std::unique_ptr m_model; std::unique_ptr m_secretsModel; int m_lastTxHeight = -1; ///< last seen tx blockheight. + int m_accountStartBlockHeight; int m_initialBlockHeight; bool m_hasFreshTransactions = false; -- 2.54.0 From ac58fb2483d476f1cec1bfefd6678f38e996559c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 13 Jan 2024 19:39:48 +0100 Subject: [PATCH 0750/1428] Start new vesion --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 523acd8..502f496 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="23" android:versionName="2024.01.2"> diff --git a/src/main.cpp b/src/main.cpp index 7741cf8..35746a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.01.1"); + qapp.setApplicationVersion("2024.01.2"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From c802ca4b839baad3ca2cbcadfbf1bef344d01a44 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 14 Jan 2024 13:51:00 +0100 Subject: [PATCH 0751/1428] Add backup height to the UI for non-HD wallets --- guis/mobile/AccountPageListItem.qml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 1a75144..01a11ec 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -212,6 +212,7 @@ QQC2.Control { } PageTitledBox { + width: parent.width id: optionsBox Flowee.CheckBox { text: qsTr("Change Addresses") @@ -227,6 +228,19 @@ QQC2.Control { } } + PageTitledBox { + // since the non-HD wallet type has only the 'wallet-keys' as a backup page, + // for such wallets we additionally add the starting height here. + id: startingHeight + width: parent.width + visible: !root.account.isHDWallet + height: visible ? implicitHeight : 0 + anchors.top: optionsBox.bottom + title: qsTr("Starting Height", "height refers to block-height") + Flowee.LabelWithClipboard { text: root.account.accountStartBlockHeight } + } + + Item { // this is a horrible hack... // First, ListViews almost always require clipping on, @@ -237,7 +251,7 @@ QQC2.Control { // left and right margin) id: clipItem anchors { - top: optionsBox.bottom + top: startingHeight.bottom topMargin: 10 bottom: parent.bottom left: parent.left -- 2.54.0 From d1019450198b6d5ee1f917dfc30bb328d432ee9d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 14 Jan 2024 14:30:29 +0100 Subject: [PATCH 0752/1428] Mark rejected transactions clearer as such. --- guis/mobile/AccountHistory.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 880edbe..a3ec2de 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -200,6 +200,7 @@ ListView { var amount = model.fundsOut - model.fundsIn return amount < 0 && amount > -2500 // then the diff is likely just fees. } + property bool isRejected: model.height === -2 // special height as defined by the wallet width: root.width height: 80 @@ -256,6 +257,7 @@ ListView { x: 20 smooth: true anchors.verticalCenter: ruler.verticalCenter + opacity: isRejected ? 0.6 : 1 } Flowee.Label { @@ -265,6 +267,7 @@ ListView { anchors.left: parent.left anchors.leftMargin: 80 clip: true // future, maybe wordwrap? + font.strikeout: isRejected text: { var comment = model.comment if (comment !== "") @@ -285,7 +288,7 @@ ListView { id: dateLine anchors.top: ruler.bottom anchors.left: commentLabel.left - color: palette.text + color: isRejected ? mainWindow.errorRed : palette.text; text: { var minedHeight = model.height; if (minedHeight === -1) { // unconfirmed. @@ -341,6 +344,7 @@ ListView { } anchors.centerIn: parent opacity: Math.abs(amountBch) < 2000 ? 0.5 : 1 + font.strikeout: isRejected } } Flowee.Label { // plus or minus in front of the price -- 2.54.0 From 89ee66191ded3cdc934dd7db6400825672ba1e9f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 14 Jan 2024 17:08:22 +0100 Subject: [PATCH 0753/1428] Pardon old sinners after a while. --- src/FloweePay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8a12e64..df35297 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -363,6 +363,7 @@ void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ifica void FloweePay::init() { auto dl = p2pNet(); // this wil load the p2p layer. + dl->connectionManager().peerAddressDb().pardonOldCrimes(); QFile in(m_basedir + AppdataFilename); Wallet *lastOpened = nullptr; -- 2.54.0 From 57643c4585deacbd7cbf6ab2823973b125809648 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 13:38:02 +0100 Subject: [PATCH 0754/1428] Move init rows to be after the constructor This avoids using virtual methods on a not yet initialized object. --- src/WalletHistoryModel.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 2abfd9d..83442f3 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -92,7 +92,7 @@ WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) QMutexLocker locker(&m_wallet->m_lock); assert(wallet->segment()); resetLastSyncIndicator(); - createMap(); + QTimer::singleShot(0, this, SLOT(createMap())); connect(wallet, SIGNAL(appendedTransactions(int,int)), SLOT(appendTransactions(int,int)), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionConfirmed(int)), SLOT(transactionChanged(int)), Qt::QueuedConnection); @@ -333,24 +333,34 @@ void WalletHistoryModel::transactionChanged(int txIndex) void WalletHistoryModel::createMap() { m_recreateTriggered = false; - m_rowsProxy.clear(); - m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); m_groups.clear(); m_today = today(); + m_groupCache = { -1 , 0 }; + if (!m_rowsProxy.isEmpty()) { + beginRemoveRows(QModelIndex(), 0, m_rowsProxy.size() - 1); + m_rowsProxy.clear(); + endRemoveRows(); + } + m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. // This is oldest to newest, which is how our model is also structured. - QMutexLocker locker(&m_wallet->m_lock); - for (const auto &iter : m_wallet->m_walletTransactions) { - if (!filterTransaction(iter.second)) - continue; - m_rowsProxy.push_back(iter.first); - // Last, resolve grouping - addTxIndexToGroups(iter.first, iter.second.minedBlockHeight); + { + QMutexLocker locker(&m_wallet->m_lock); + for (const auto &iter : m_wallet->m_walletTransactions) { + if (!filterTransaction(iter.second)) + continue; + m_rowsProxy.push_back(iter.first); + // Last, resolve grouping + addTxIndexToGroups(iter.first, iter.second.minedBlockHeight); + } } - m_groupCache = { -1 , 0 }; + if (!m_rowsProxy.isEmpty()) { + beginInsertRows(QModelIndex(), 0, m_rowsProxy.size() - 1); + endInsertRows(); + } } bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) const -- 2.54.0 From bdd64679b67247dd0a8e7a68f8040a9530968f42 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 13:41:15 +0100 Subject: [PATCH 0755/1428] Make a tx show the creation time instead of mined This stores the time of a transaction in the store of the wallet, and indeed sets it when it is added to the wallet first time. For instance when we create it or when the tx is first sent to us at initial broadcast. We add some logic to the model in order to put a bit more effort into finding times of a transaction that did not get mined and (before this code) did not get its time of creation set. Mostly this is about rejected transactions. And there we guestimate the time instead. --- guis/mobile/AccountHistory.qml | 6 ---- guis/mobile/TransactionDetails.qml | 16 ++++++++-- guis/mobile/TxInfoSmall.qml | 2 +- src/FloweePay.cpp | 8 +++++ src/FloweePay.h | 3 +- src/Wallet.cpp | 26 +++++++++------ src/Wallet.h | 7 ++++ src/WalletHistoryModel.cpp | 51 ++++++++++++++++++++++++------ src/Wallet_p.h | 9 ++---- src/Wallet_spending.cpp | 4 +-- src/Wallet_support.cpp | 6 ++-- 11 files changed, 97 insertions(+), 41 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index a3ec2de..2e7f345 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -291,12 +291,6 @@ ListView { color: isRejected ? mainWindow.errorRed : palette.text; text: { var minedHeight = model.height; - if (minedHeight === -1) { // unconfirmed. - // We show a more 'in-progress' like string that indicates its not date-stamped yet. - if (model.fundsIn > 0) - return qsTr("Sending"); - return qsTr("Seen"); - } if (minedHeight === -2) return qsTr("Rejected") var date = model.date; diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index fbc21a3..d61f50e 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +63,18 @@ Page { } } } + PageTitledBox { + title: qsTr("First Seen") + Flowee.Label { + Layout.fillWidth: true + text: { + var tx = root.transaction; // delayed initialized + if (tx == null) + return ""; + return Qt.formatDateTime(tx.date); + } + } + } PageTitledBox { title: { @@ -87,7 +99,7 @@ Page { return ""; let txHeight = tx.height; - var answer = txHeight + "\n" + Pay.formatDateTime(tx.date) + var answer = txHeight + "\n" + Pay.formatBlockTime(tx.height) let blockAge = Pay.chainHeight - txHeight; answer += "\n"; answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TxInfoSmall.qml index 1491efd..5d36d71 100644 --- a/guis/mobile/TxInfoSmall.qml +++ b/guis/mobile/TxInfoSmall.qml @@ -60,7 +60,7 @@ ColumnLayout { text: { if (root.minedHeight <= 0) return ""; - var rc = Pay.formatDateTime(model.date); + var rc = Pay.formatBlockTime(model.height); var confirmations = Pay.headerChainHeight - root.minedHeight + 1; if (confirmations > 0 && confirmations < 100) rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index df35297..2bcd6b0 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -709,6 +709,14 @@ QString FloweePay::formatDateTime(QDateTime date) const return date.toString(format); } +QString FloweePay::formatBlockTime(int blockHeight) const +{ + // wrap this for convenience and also ensure that we never return an insanely old + // date (1970) just because we lack blockheader data. + return formatDateTime(QDateTime::fromSecsSinceEpoch(std::max(1250000000, + m_downloadManager->blockchain().block(blockHeight).nTime))); +} + Wallet *FloweePay::createWallet(const QString &name) { auto dl = p2pNet(); diff --git a/src/FloweePay.h b/src/FloweePay.h index 183a704..09833e2 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -150,6 +150,7 @@ public: Q_INVOKABLE QString formatDate(QDateTime date) const; Q_INVOKABLE QString formatDateTime(QDateTime datetime) const; + Q_INVOKABLE QString formatBlockTime(int blockHeight) const; /// create a new HD wallet with an optional name. Q_INVOKABLE NewWalletConfig* createNewWallet(const QString &derivationPath, const QString &password = QString(), const QString &walletName = QString()); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index b5a9d5f..2c72f67 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -373,7 +373,8 @@ void Wallet::newTransaction(const Tx &tx) } m_utxoDirty = true; setUserOwnedWallet(true); - wtx.minedBlockHeight = WalletPriv::Unconfirmed; + wtx.minedBlockHeight = Wallet::Unconfirmed; + wtx.transactionTime = time(nullptr); bool dummy = false; while (updateHDSignatures(wtx, dummy)) { // if we added a bunch of new private keys, then rerun the matching @@ -472,7 +473,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: walletTransactionId = oldTx->second; } m_utxoDirty = true; - const bool wasUnconfirmed = wtx.minedBlockHeight == WalletPriv::Unconfirmed; + const bool wasUnconfirmed = wtx.minedBlockHeight == Wallet::Unconfirmed; while (updateHDSignatures(wtx, needNewBloom)) { // if we added a bunch of new private keys, then rerun the matching // so we make sure we matched all we can @@ -574,7 +575,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: logDebug(LOG_WALLET) << "Confirmed transaction(s) in block" << blockHeight << "made invalid transaction:" << ejectedTx << tx->second.txid; auto &wtx = tx->second; - wtx.minedBlockHeight = WalletPriv::Rejected; + wtx.minedBlockHeight = Wallet::Rejected; // Any outputs we locked need to be unlocked for (auto i = wtx.inputToWTX.begin(); i != wtx.inputToWTX.end(); ++i) { auto iter = m_lockedOutputs.find(i->second); @@ -1161,7 +1162,7 @@ void Wallet::broadcastTxFinished(int txIndex, bool success) if (wtx != m_walletTransactions.end()) { logCritical(LOG_WALLET) << "Marking transaction invalid"; auto &tx = wtx->second; - if (tx.minedBlockHeight == WalletPriv::Unconfirmed) { + if (tx.minedBlockHeight == Wallet::Unconfirmed) { // a transaction that has been added before, but now marked // rejected means we should revert some stuff that newTransaction() did. // - locked output @@ -1187,7 +1188,7 @@ void Wallet::broadcastTxFinished(int txIndex, bool success) assert(false); // Can't imagine the usecase, so if this hits in a debug build lets fail-fast logWarning(LOG_WALLET) << "Transaction marked rejected that had blockHeight:" << tx.minedBlockHeight; } - tx.minedBlockHeight = WalletPriv::Rejected; + tx.minedBlockHeight = Wallet::Rejected; } } return; @@ -1457,7 +1458,7 @@ void Wallet::broadcastUnconfirmed() for (auto iter = m_walletTransactions.begin(); iter != m_walletTransactions.end(); ++iter) { - if (iter->second.minedBlockHeight == WalletPriv::Unconfirmed) { + if (iter->second.minedBlockHeight == Wallet::Unconfirmed) { auto tx = loadTransaction(iter->second.txid, Streaming::pool(0)); if (tx.data().size() > 64) { auto bc = std::make_shared(this, iter->first, tx); @@ -1974,6 +1975,9 @@ void Wallet::loadWallet() assert(parser.isString()); wtx.userComment = QString::fromUtf8(parser.stringData().c_str(), parser.dataLength()); } + else if (parser.tag() == WalletPriv::TransactionTime) { + wtx.transactionTime = parser.longData(); + } else if (parser.tag() == WalletPriv::LastSynchedBlock) { highestBlockHeight = std::max(parser.intData(), highestBlockHeight); } @@ -1987,9 +1991,9 @@ void Wallet::loadWallet() auto iter = m_walletTransactions.find(txIndex); assert(iter != m_walletTransactions.end()); - if (iter->second.minedBlockHeight == WalletPriv::Rejected) + if (iter->second.minedBlockHeight == Wallet::Rejected) continue; - if (iter->second.minedBlockHeight != WalletPriv::Unconfirmed) { + if (iter->second.minedBlockHeight != Wallet::Unconfirmed) { assert(iter->second.minedBlockHeight > 0); // remove UTXOs this Tx spent for (auto i = iter->second.inputToWTX.begin(); i != iter->second.inputToWTX.end(); ++i) { @@ -2142,6 +2146,8 @@ void Wallet::saveWallet() } if (!item.second.userComment.isEmpty()) builder.add(WalletPriv::UserComment, item.second.userComment.toStdString()); + if (item.second.transactionTime != 0) + builder.add(WalletPriv::TransactionTime, static_cast(item.second.transactionTime)); builder.add(WalletPriv::Separator, true); } builder.add(WalletPriv::LastSynchedBlock, m_segment->lastBlockSynched()); @@ -2180,7 +2186,7 @@ void Wallet::recalculateBalance() auto wtx = m_walletTransactions.find(OutputRef(utxo.first).txIndex()); Q_ASSERT(wtx != m_walletTransactions.end()); const int h = wtx->second.minedBlockHeight; - if (h == WalletPriv::Rejected) + if (h == Wallet::Rejected) continue; const auto loi = m_lockedOutputs.find(utxo.first); if (loi != m_lockedOutputs.end()) { @@ -2194,7 +2200,7 @@ void Wallet::recalculateBalance() continue; } } - if (h == WalletPriv::Unconfirmed) + if (h == Wallet::Unconfirmed) balanceUnconfirmed += utxo.second; else balanceConfirmed += utxo.second; diff --git a/src/Wallet.h b/src/Wallet.h index 2c6bdb2..01ecff5 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -47,6 +47,12 @@ class Wallet : public QObject, public DataListenerInterface, public HeaderSyncIn { Q_OBJECT public: + enum SpecialBlockHeight { + Genesis = 0, + Unconfirmed = -1, // a tx marked with this height is waiting to get confirmed + Rejected = -2 // a tx marked with this height can no longer be mined + }; + /** * Create an empty, new, wallet. */ @@ -493,6 +499,7 @@ private: int minedBlockHeight = 0; bool isCoinbase = false; bool isCashFusionTx = false; + uint32_t transactionTime = 0; QString userComment; bool isUnconfirmed() const; diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 83442f3..73d26c3 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -129,10 +129,9 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const case MinedHeight: return QVariant(item.minedBlockHeight); case MinedDate: { - if (item.minedBlockHeight <= 0) - return QVariant(); - - auto timestamp = secsSinceEpochFor(item.minedBlockHeight); + int64_t timestamp = item.transactionTime; + if (timestamp == 0) + timestamp = secsSinceEpochFor(item.minedBlockHeight); return QVariant(QDateTime::fromSecsSinceEpoch(timestamp)); } case FundsIn: { @@ -379,11 +378,45 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) if (m_groups.empty()) m_groups.push_back(TransactionGroup()); - uint32_t timestamp; - if (blockheight <= 0) { - timestamp = time(nullptr); - } else { + uint32_t timestamp = 0; + auto txIter = m_wallet->m_walletTransactions.find(txIndex); + if (txIter != m_wallet->m_walletTransactions.end()) + timestamp = txIter->second.transactionTime; + + if (timestamp == 0 && blockheight > 0) // We only know when it was mined timestamp = secsSinceEpochFor(blockheight); + + if (timestamp == 0) { + // we should really not get into this if(), but only in 2024.01 did we save the + // time of the transaction, so older ones are to be treated special. + if (blockheight == Wallet::Rejected) { + // we don't actually know when a rejected transaction was made which didn't + // save it yet, back then. We can only guesstimeate and show that. + + const auto nope = m_wallet->m_walletTransactions.end(); + auto txIter2 = txIter; + while (timestamp == 0 && txIter2 != nope) { + if (txIter2->second.minedBlockHeight > 0) { + timestamp = secsSinceEpochFor(txIter2->second.minedBlockHeight); + break; + } + txIter2++; + } + // other direction. + txIter2 = txIter; + while (timestamp == 0 && txIter2 != m_wallet->m_walletTransactions.begin()) { + if (txIter2->second.minedBlockHeight > 0) { + timestamp = secsSinceEpochFor(txIter2->second.minedBlockHeight); + break; + } + txIter2--; + } + } + if (timestamp == 0) + timestamp = time(nullptr); + + // update the wallet to avoid this whole calc next time around. + txIter->second.transactionTime = timestamp; } if (!m_groups.back().add(txIndex, timestamp, m_today)) { diff --git a/src/Wallet_p.h b/src/Wallet_p.h index e3a6b8e..dad64be 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -103,6 +103,7 @@ enum WalletDataSaveTags { // per tx metadata UserComment, // string + TransactionTime, // secs-since-epoch when the transaction was first seen. OutputIndex = 30, OutputValue, // in Satoshi @@ -118,12 +119,6 @@ enum OutputLockStateEnum { UserLocked // User locked this output }; -enum SpecialBlockHeight { - Genesis = 0, - Unconfirmed = -1, // a tx marked with this height is waiting to get confirmed - Rejected = -2 // a tx marked with this height can no longer be mined -}; - } // this is used to broadcast transactions diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index 4dbca6e..e1d656c 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -145,7 +145,7 @@ Wallet::OutputSet Wallet::findInputsFor(qint64 output, int feePerByte, int txSiz const auto &wtx = wtxIter->second; int h = wtx.minedBlockHeight; - if (h == WalletPriv::Unconfirmed) { + if (h == Wallet::Unconfirmed) { out.score = -10; // unconfirmed. } else if (wtx.isCoinbase && h + MATURATION_AGE >= m_lastBlockHeightSeen) { diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index b041eac..35bb920 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -184,12 +184,12 @@ uint16_t WalletInfoObject::privSegment() const bool Wallet::WalletTransaction::isUnconfirmed() const { - return minedBlockHeight == WalletPriv::Unconfirmed; + return minedBlockHeight == Wallet::Unconfirmed; } bool Wallet::WalletTransaction::isRejected() const { - return minedBlockHeight == WalletPriv::Rejected; + return minedBlockHeight == Wallet::Rejected; } -- 2.54.0 From 0f83fb7811209a1aabd2304216256c4640df6306 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 15:43:20 +0100 Subject: [PATCH 0756/1428] Import translations from crowdin --- src/WalletHistoryModel.cpp | 10 +++--- translations/floweepay-common_es.ts | 2 +- translations/floweepay-common_pt.ts | 6 ++-- translations/floweepay-desktop_de.ts | 12 ++++---- translations/floweepay-desktop_pt.ts | 14 ++++----- translations/floweepay-mobile_de.ts | 14 ++++----- translations/floweepay-mobile_en.ts | 2 +- translations/floweepay-mobile_es.ts | 2 +- translations/floweepay-mobile_pl.ts | 2 +- translations/floweepay-mobile_pt.ts | 34 ++++++++++----------- translations/module-build-transaction_de.ts | 2 +- translations/module-build-transaction_es.ts | 2 +- translations/module-build-transaction_pl.ts | 2 +- 13 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 73d26c3..24fd7e8 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -411,12 +411,12 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) } txIter2--; } - } - if (timestamp == 0) - timestamp = time(nullptr); - // update the wallet to avoid this whole calc next time around. - txIter->second.transactionTime = timestamp; + // update the wallet to avoid this whole calc next time around. + txIter->second.transactionTime = timestamp; + } + if (timestamp == 0) // probably an unconfirmed transaction. + timestamp = time(nullptr); } if (!m_groups.back().add(txIndex, timestamp, m_today)) { diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 4fdc1b6..9b335b7 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -91,7 +91,7 @@ Failed - Failed + Fallido diff --git a/translations/floweepay-common_pt.ts b/translations/floweepay-common_pt.ts index a784a6c..536800b 100644 --- a/translations/floweepay-common_pt.ts +++ b/translations/floweepay-common_pt.ts @@ -379,7 +379,7 @@ Change #%1 - Change #%1 + Alterar %1 @@ -387,12 +387,12 @@ Today - Today + Hoje Yesterday - Yesterday + Ontem diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 1e08e5f..a86d45d 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -131,7 +131,7 @@ Seed format - Seed format + Seed-Format @@ -316,7 +316,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss BIP 39 seed-phrase (interpreted as Electrum format) description of type - BIP 39 seed-phrase (interpreted as Electrum format) + BIP 39 Seed-Phrase (interpretiert als Electrum Format) @@ -328,7 +328,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Electrum seed-phrase description of type - Electrum seed-phrase + Electrum Seed-Phrase @@ -361,12 +361,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Old Electrum Phrase - Old Electrum Phrase + Alte Electrum Phrase When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Wenn die Electrum Erkennung fehlschlägt, und Sie sind sicher, dass sie in dieser Geldbörse erstellt wurde, aktivieren Sie diese Option. @@ -973,7 +973,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Fused - Fused + Fusioniert diff --git a/translations/floweepay-desktop_pt.ts b/translations/floweepay-desktop_pt.ts index d366af7..f51f121 100644 --- a/translations/floweepay-desktop_pt.ts +++ b/translations/floweepay-desktop_pt.ts @@ -229,7 +229,7 @@ Name - Name + Nome @@ -264,7 +264,7 @@ This ensures only one private key will need to be backed up Name - Name + Nome @@ -279,7 +279,7 @@ This ensures only one private key will need to be backed up Derivation - Derivation + Derivação @@ -304,7 +304,7 @@ This ensures only one private key will need to be backed up Name - Name + Nome @@ -376,7 +376,7 @@ Change will come back to the imported key. Derivation - Derivation + Derivação @@ -906,7 +906,7 @@ Change will come back to the imported key. Password - Password + Senha @@ -1106,7 +1106,7 @@ Change will come back to the imported key. Open - Open + Abrir diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index ee1aa15..f93fcda 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -70,7 +70,7 @@ Fused - Fused + Fusioniert @@ -133,7 +133,7 @@ Seed format - Seed format + Seed-Format @@ -339,7 +339,7 @@ Dark Theme - Dark Theme + Dunkles Design @@ -390,7 +390,7 @@ BIP 39 seed-phrase (interpreted as Electrum) description of type - BIP 39 seed-phrase (interpreted as Electrum) + BIP 39 Seed-Phrase (interpretiert als Electrum Format) @@ -402,7 +402,7 @@ Electrum seed-phrase description of type - Electrum seed-phrase + Electrum Seed-Phrase @@ -430,12 +430,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Old Electrum Phrase - Old Electrum Phrase + Alte Electrum Phrase When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + Wenn die Electrum Erkennung fehlschlägt, und Sie sind sicher, dass sie in dieser Geldbörse erstellt wurde, aktivieren Sie diese Option. diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 02d8920..b6d110f 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -1022,7 +1022,7 @@ This ensures only one private key will need to be backed up %1 blocks ago - %1 blocks ago + %1 block ago %1 blocks ago diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index 495c7bd..602370e 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -1122,7 +1122,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Fees - Fees + Comisiones diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index bb46342..394cfad 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -1125,7 +1125,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Fees - Fees + Koszt diff --git a/translations/floweepay-mobile_pt.ts b/translations/floweepay-mobile_pt.ts index b6a5cdd..3e373bd 100644 --- a/translations/floweepay-mobile_pt.ts +++ b/translations/floweepay-mobile_pt.ts @@ -108,7 +108,7 @@ Name - Name + Nome @@ -159,7 +159,7 @@ <b>Important</b>: Never share your seed-phrase with others! - <b>Important</b>: Never share your seed-phrase with others! + <b>Importante</b>: Nunca compartilhe sua frase secreta com outros! @@ -175,17 +175,17 @@ Change Addresses - Change Addresses + Alterar endereço Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. + Altera entre endereços para receber pagamentos e endereços que a carteira usa para mandar troco de volta para você. Used Addresses - Used Addresses + Endereços utilizados @@ -200,7 +200,7 @@ Sync Status - Sync Status + Status da sincronização @@ -210,7 +210,7 @@ Hide in private mode - Hide in private mode + Esconder no modo privado @@ -220,7 +220,7 @@ Archive Wallet - Archive Wallet + Arquivar Carteira @@ -264,7 +264,7 @@ Wallets - Wallets + Carteiras @@ -315,7 +315,7 @@ Explore - Explore + Explorar @@ -413,7 +413,7 @@ Name - Name + Nome @@ -445,7 +445,7 @@ Change will come back to the imported key. Derivation - Derivation + Derivação @@ -519,7 +519,7 @@ Change will come back to the imported key. Security - Security + Segurança @@ -667,7 +667,7 @@ Change will come back to the imported key. Name - Name + Nome @@ -700,7 +700,7 @@ This ensures only one private key will need to be backed up Derivation - Derivation + Derivação @@ -860,7 +860,7 @@ This ensures only one private key will need to be backed up Address Bitcoin Cash address - Address + Endereço @@ -1176,7 +1176,7 @@ This ensures only one private key will need to be backed up Open open wallet with PIN - Open + Abrir diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index 28a48e4..d1d943c 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -133,7 +133,7 @@ Paste - Paste + Einfügen diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts index 76be33d..ed6a078 100644 --- a/translations/module-build-transaction_es.ts +++ b/translations/module-build-transaction_es.ts @@ -133,7 +133,7 @@ Paste - Paste + Pegar diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index f763470..38eff14 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -133,7 +133,7 @@ Paste - Paste + Wklej -- 2.54.0 From 0284e81a047c8f6a7c95d19d0018122f92036594 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 19:27:33 +0100 Subject: [PATCH 0757/1428] Add various (UX) features This adds a context menu to open the transaction in the blockchair explorer. We also show a visual feedback on copying the txid. And we update the blocks past to be number of confirmations and avoid any confusion. --- guis/mobile/TransactionDetails.qml | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index d61f50e..235efa7 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -27,6 +27,12 @@ Page { property QtObject infoObject: null headerText: qsTr("Transaction Details") + property QtObject openInExplorer: QQC2.Action { + text: qsTr("Open in Explorer") + onTriggered: Pay.openInExplorer(root.transaction.txid); + } + menuItems: [ openInExplorer ] + Flickable { anchors.fill: parent contentHeight: content.height @@ -41,6 +47,7 @@ Page { Item { implicitHeight: txidLabel.implicitHeight width: parent.width + Flowee.Label { id: txidLabel text: root.transaction == null ? "" : root.transaction.txid @@ -48,6 +55,20 @@ Page { anchors.right: copyIcon.left anchors.rightMargin: 10 wrapMode: Text.WrapAnywhere + + Rectangle { + id: txidHighlight + anchors.fill: parent + color: palette.mid + opacity: 0 + visible: opacity > 0 + Timer { + running: parent.visible + onTriggered: parent.opacity = 0; + interval: 500 + } + Behavior on opacity { NumberAnimation { duration: 250 } } + } } Image { id: copyIcon @@ -58,7 +79,10 @@ Page { MouseArea { anchors.fill: parent anchors.margins: -15 - onClicked: Pay.copyToClipboard(txidLabel.text) + onClicked: { + txidHighlight.opacity = 0.6 + Pay.copyToClipboard(txidLabel.text) + } } } } @@ -100,7 +124,7 @@ Page { let txHeight = tx.height; var answer = txHeight + "\n" + Pay.formatBlockTime(tx.height) - let blockAge = Pay.chainHeight - txHeight; + let blockAge = Pay.chainHeight - txHeight + 1; answer += "\n"; answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); return answer; -- 2.54.0 From 3c9b1e7d322de4e5301cd8efc8e35c36f31c806f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 19:48:07 +0100 Subject: [PATCH 0758/1428] For a private key, allow showing QR. --- guis/Flowee/WalletSecretsView.qml | 44 +++++++++++++++++++++++++++++-- src/FloweePay.cpp | 5 ++++ src/FloweePay.h | 2 ++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 557a695..85e982a 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,31 @@ ListView { model: root.account.secrets property bool showHdIndex: true + Item { + // non-layoutable items. + + QQC2.Popup { + id: seedPopup + width: 260 + height: 260 + x: 55 + y: 100 + modal: true + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + QRWidget { + id: seedQr + qrSize: 250 + textVisible: false + useRawString: true + } + } + } header: Item { width: root.width @@ -131,12 +156,27 @@ ListView { id: ourMenu QQC2.Action { text: qsTr("Copy Address") - onTriggered: Pay.copyToClipboard(address) + onTriggered: Pay.copyToClipboard(Pay.chainPrefix + address) + } + QQC2.Action { + text: qsTr("QR of Address") + onTriggered: { + seedQr.qrText = Pay.chainPrefix + address + seedPopup.open(); + } } QQC2.Action { text: qsTr("Copy Private Key") onTriggered: Pay.copyToClipboard(privatekey) } + QQC2.Action { + text: qsTr("QR of Private Key") + onTriggered: { + console.log("-> " + privatekey) + seedQr.qrText = privatekey + seedPopup.open(); + } + } } onClicked: ourMenu.popup() } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 2bcd6b0..99e69c7 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -747,6 +747,11 @@ P2PNet::Chain FloweePay::chain() const return m_chain; } +QString FloweePay::qchainPrefix() const +{ + return QString("%1:").arg(QString::fromStdString(m_chainPrefix)); +} + void FloweePay::copyToClipboard(const QString &text) { QGuiApplication::clipboard()->setText(text); diff --git a/src/FloweePay.h b/src/FloweePay.h index 09833e2..a9cbc0b 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -73,6 +73,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa // unique helper property.. Q_PROPERTY(QString paymentProtocolRequest READ paymentProtocolRequest WRITE setPaymentProtocolRequest NOTIFY paymentProtocolRequestChanged FINAL) + Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) public: enum UnitOfBitcoin { BCH, @@ -271,6 +272,7 @@ public: */ P2PNet::Chain chain() const; const std::string &chainPrefix() const { return m_chainPrefix; } + QString qchainPrefix() const; // for the QML property /** * return if the user engaged the hide-balance feature to avoid people seeing the balance on-screen. -- 2.54.0 From 88dd71b9abd81b4ac096a4a413d4b9a8517ebae3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 15 Jan 2024 20:17:10 +0100 Subject: [PATCH 0759/1428] Add 'copy address' menu option in the coin-selector. --- guis/desktop/SendTransactionPane.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 8b9d9be..70a9e00 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -637,6 +637,10 @@ Item { coinsListView.menuIsOpen = false } } + MenuItem { + text: qsTr("Copy Address") + onClicked: Pay.copyToClipboard(Pay.chainPrefix + address); + } } } } -- 2.54.0 From c079a171d97e4dd8071b60b10898df1a544ec16c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 14 Jan 2024 22:15:37 +0100 Subject: [PATCH 0760/1428] Make the 'initial wallet' invisible The initial wallet is already made auto-archived after a couple of weeks not receiving any funds. Avoiding it allocating peers. This makes sure we also do not show it in the mobile UI in the wallets screen. --- src/PortfolioDataProvider.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 45b9299..3a18bf5 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -85,6 +85,10 @@ QList PortfolioDataProvider::rawAccounts() const { QList answer; for (auto *account : m_accountInfos) { + if (!account->userOwnedWallet() && account->isArchived()) { + // the initial wallet is hard ignored after a couple of weeks + continue; + } answer.append(account); } return answer; -- 2.54.0 From d92db6208a00d743044f663794082202a5dc5950 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 14 Jan 2024 22:50:42 +0100 Subject: [PATCH 0761/1428] Minor UX fixes around wallet management - Make sure we always set the proper wallet on create, this would fail on the very first after the initial wallet being created. - Don't allow marking the initial wallet as archived in the UI as that is a non-reversible action. The app will do it automatically after a couple of weeks. --- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/NewAccount.qml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 01a11ec..cf08a94 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -383,7 +383,7 @@ QQC2.Control { height: archiveButtonText.height + 20 color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" width: archiveButtonText.width + 30 - visible: !singleAccountSetup + visible: !singleAccountSetup && root.account.isUserOwned Text { id: archiveButtonText diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 905d915..11fe7a6 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -140,7 +140,7 @@ Page { onClicked: { var options = Pay.createNewBasicWallet(accountName.text); options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; + var accounts = portfolio.rawAccounts; portfolio.current = accounts[accounts.length - 1] thePile.pop(); thePile.pop(); @@ -194,7 +194,7 @@ Page { text: qsTr("Create") onClicked: { var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); - var accounts = portfolio.accounts; + var accounts = portfolio.rawAccounts; portfolio.current = accounts[accounts.length - 1] thePile.pop(); thePile.pop(); -- 2.54.0 From b1cbf458492328d43bbfb13f3e9a272218559183 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 12:45:05 +0100 Subject: [PATCH 0762/1428] Add cmake option 'skip-example' & skip that module The final release should not include the example module as we aim releases at normal people, not devs. This makes the skipping of the example module part of the build setup by simply passing in -Dskip_example=TRUE to cmake. --- CMakeLists.txt | 68 ++++++++++++++++++++++++------------------ android/build-pay.sh | 33 +++++++------------- modules/CMakeLists.txt | 12 ++++---- src/CMakeLists.txt | 3 -- 4 files changed, 56 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f19b5b2..7f5ff68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,21 @@ cmake_minimum_required(VERSION 3.19) project(flowee_pay VERSION 1.0 LANGUAGES CXX) +if (NOT ANDROID) + set (NOT_ANDROID TRUE) +endif() +option(skip_example "Make a release build for Android, with the release package name" OFF) +option(quick_deploy "Do not include the blockchain in the APK, for a smaller (developer ONLY) deployment" OFF) +option(build_desktop_pay "Build the desktop (dev) client of Pay" ${NOT_ANDROID}) +option(build_mobile_pay "Build the mobile client of Pay" ON) +option(build_pay_tools "Build the tools Pay" ${NOT_ANDROID}) + +# options from src/CMakeLists.txt +option(local_qml "Allow local QML loading" OFF) +option(NetworkLogClient "Include the network based logging in the executables" OFF) + + + set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) @@ -26,8 +41,8 @@ find_package(Qt6 COMPONENTS Core Quick Svg REQUIRED OPTIONAL_COMPONENTS Multimedia) find_package(flowee REQUIRED flowee_p2p) find_package(OpenSSL REQUIRED) + if (ANDROID) - option(quick_deploy "Do not include the blockchain in the APK, for a smaller (developer ONLY) deployment" OFF) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/android/cmake) @@ -148,14 +163,7 @@ endif() ###### Pay executable include_directories(${CMAKE_SOURCE_DIR}/src) -if (NOT ANDROID) - set (NOT_ANDROID TRUE) -endif() -option (BUILD_DESKTOP_PAY - "Build the desktop (dev) client of Pay" - ${NOT_ANDROID}) - -if (BUILD_DESKTOP_PAY) +if (build_desktop_pay) # The qm files are generated in the build tree, but the qrc file is inside the # source directory and the path to resources are relative to the location of # the qrc file itself. We copy the qrc file in the build @@ -183,15 +191,14 @@ if (BUILD_DESKTOP_PAY) endif() ###### Pay-mobile executable -option (BUILD_MOBILE_PAY "Build the mobile client of Pay" ON) if (NOT "${Qt6Multimedia_FOUND}") - set (BUILD_MOBILE_PAY OFF) + set (build_mobile_pay OFF) endif () set (PAY_MOBILE_LIBS "") set (PAY_MOBILE_RESOURCES "") -if (BUILD_MOBILE_PAY) # support modules, which get enabled currently for mobile only +if (build_mobile_pay) # support modules, which get enabled currently for mobile only add_subdirectory(modules) # find all modules present in the source-tree and make sure we link them into the @@ -202,10 +209,12 @@ if (BUILD_MOBILE_PAY) # support modules, which get enabled currently for mobile if (IS_DIRECTORY ${child} AND NOT IS_SYMLINK ${child}) file(GLOB subModule ${child}/*ModuleInfo.h) file(RELATIVE_PATH moduleName ${CMAKE_CURRENT_SOURCE_DIR}/modules ${child}) - list(APPEND PAY_MOBILE_LIBS "${moduleName}_module_lib") - file(GLOB resources ${child}/*qrc) - list(APPEND PAY_MOBILE_RESOURCES "${resources}") + if (NOT (${skip_example} AND ${moduleName} STREQUAL "example")) + list(APPEND PAY_MOBILE_LIBS "${moduleName}_module_lib") + file(GLOB resources ${child}/*qrc) + list(APPEND PAY_MOBILE_RESOURCES "${resources}") + endif() endif() endforeach() @@ -213,7 +222,7 @@ if (BUILD_MOBILE_PAY) # support modules, which get enabled currently for mobile endif() -if (ANDROID AND BUILD_MOBILE_PAY) +if (ANDROID AND build_mobile_pay) # blockheaders to be included in the APK set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) if (NOT quick_deploy) @@ -260,7 +269,7 @@ if (ANDROID AND BUILD_MOBILE_PAY) ) endif () -if(NOT ANDROID AND BUILD_MOBILE_PAY) +if(NOT ANDROID AND build_mobile_pay) if(local_qml) set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) endif() @@ -292,11 +301,7 @@ endif() ###### blockheaders-meta-extractor executable -option (BUILD_PAY_TOOLS - "Build the tools Pay" - ${NOT_ANDROID}) - -if (BUILD_PAY_TOOLS) +if (build_pay_tools) add_executable(blockheaders-meta-extractor src/MetaExtractor.cpp ) @@ -337,7 +342,7 @@ message("Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH} if (${local_qml}) message(" Using QML from source-dir. DO NOT DISTRIBUTE BINARIES!") endif () -if (${BUILD_DESKTOP_PAY}) +if (build_desktop_pay) message ("-> Building Desktop-Pay...") if (${Qt6DBus_FOUND}) message(" Found optional lib: QtDBus") @@ -348,8 +353,11 @@ endif() if (NOT ${Qt6Multimedia_FOUND}) message("ww Missing QtMultimedia libs, not building Pay for mobile ") endif () -if (${BUILD_MOBILE_PAY}) +if (${build_mobile_pay}) message ("-> Building Pay for mobile") + if (ANDROID AND ${skip_example}) + message (" With release package-ID!") + endif() if (ANDROID AND DEFINED MOBILE_PAY_I18N_QRC) message (" Found translation files, including in package") endif () @@ -357,20 +365,22 @@ endif() if (NetworkLogClient) message ("-> Including network-logging capability") endif() -if (${BUILD_PAY_TOOLS}) +if (build_pay_tools) message ("-> Building Pay tools") endif() -if (${BUILD_MOBILE_PAY}) +if (${build_mobile_pay}) file(GLOB sub_directories ${CMAKE_SOURCE_DIR}/modules/*) foreach (child ${sub_directories}) if (IS_DIRECTORY ${child}) file(GLOB subModule ${child}/*ModuleInfo.h) if (EXISTS ${subModule}) file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) - message("-> Including module '${module}'") - get_filename_component(className ${subModule} NAME_WE) - message(" ${className}") + if (NOT (${skip_example} AND ${module} STREQUAL "modules/example")) + message("-> Including module '${module}'") + get_filename_component(className ${subModule} NAME_WE) + message(" ${className}") + endif() endif() endif() endforeach() diff --git a/android/build-pay.sh b/android/build-pay.sh index e42cafe..bc74ee0 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -1,6 +1,6 @@ #!/bin/bash # This file is part of the Flowee project -# Copyright (C) 2022-2023 Tom Zander +# Copyright (C) 2022-2024 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,17 +19,17 @@ _thehub_dir_="$1" _pay_native_name_="$2" if test -f smartBuild.sh; then - ./smartBuild.sh noapk + ./smartBuild.sh exit fi -if test -z "$_pay_native_name_"; then +if test -z "$_thehub_dir_"; then echo "Usage:" - echo " build-pay " + echo " build-pay [PAY_NATIVE_builddir]" echo "" echo "Start this client in your builddir" echo "HUB-builddir is the dir where the android build of flowe-thehub is." - echo "Pay_NATIVE-builddir for a native build of flowee-pay." + echo "Pay_NATIVE-builddir for a native build of flowee-pay, for translations." exit fi @@ -51,13 +51,10 @@ if test -z "$_docker_name_"; then _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.3" fi -if ! test -f "$_pay_native_name_/blockheaders-meta-extractor"; then - echo "Invalid or not compiled for Android Pay-native dir." - exit -fi - mkdir -p imports -cp -f "$_pay_native_name_"/*qm imports/ +if test -d "$_pay_native_name_"; then + cp -f "$_pay_native_name_"/*qm imports/ +fi floweePaySrcDir=`dirname $0`/.. if test -f $floweePaySrcDir/android/netlog.conf; then @@ -119,15 +116,6 @@ if test "\$1" = "distclean"; then mv blockheaders android-build/assets/ fi -if test "\$1" = "sign" -o "\$2" = "sign" -then - MAKE_SIGNED_APK=1 -else - if test "\$1" != "noapk"; then - MAKE_UNSIGNED_APK=apk - fi -fi - if test -f .docker; then DOCKERID=\`cat .docker\` if test -n "\$DOCKERID"; then @@ -158,10 +146,9 @@ if test -f $floweePaySrcDir/android/netlog.conf; then cp $floweePaySrcDir/android/netlog.conf android-build/assets/ fi -\$execInDocker /usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir \$MAKE_UNSIGNED_APK +\$execInDocker /usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir -if test -n "\$MAKE_SIGNED_APK" -then +if test "\$1" = "sign" -o "\$2" = "sign"; then \$execInDocker build/.sign fi diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index acbc427..15f515d 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,5 +1,5 @@ # This file is part of the Flowee project -# Copyright (C) 2023 Tom Zander +# Copyright (C) 2023-2024 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,13 +25,15 @@ foreach (child ${sub_directories}) if (IS_DIRECTORY ${child}) file(GLOB subModule ${child}/*ModuleInfo.h) if (EXISTS ${subModule}) - add_subdirectory(${child}) file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) get_filename_component(className ${subModule} NAME_WE) - list (APPEND CLASSES_LIST ${className}) - file (RELATIVE_PATH include_file ${CMAKE_CURRENT_SOURCE_DIR} ${subModule}) - list (APPEND INCLUDES_LIST ${include_file}) + if (NOT (${skip_example} AND ${className} STREQUAL "ExampleModuleInfo")) + add_subdirectory(${child}) + list (APPEND CLASSES_LIST ${className}) + file (RELATIVE_PATH include_file ${CMAKE_CURRENT_SOURCE_DIR} ${subModule}) + list (APPEND INCLUDES_LIST ${include_file}) + endif() endif() endif() endforeach() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db8a539..de3a609 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,9 +16,6 @@ project(flowee_lib) -option(local_qml "Allow local QML loading" OFF) -option(NetworkLogClient "Include the network based logging in the executables" OFF) - set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp -- 2.54.0 From 539d0fa22924b2f6390a76dd05bfabf6a2efc95b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 12:54:32 +0100 Subject: [PATCH 0763/1428] Simplify the local_qml feature --- CMakeLists.txt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f5ff68..e2cb368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,14 @@ endif() ###### Pay executable include_directories(${CMAKE_SOURCE_DIR}/src) +# local QML means we hardcode the full path to the 'guis' dir for this build. +# and instead of pointing to the QRC, we point here. Avoding re-compile / linking +# after updating the QML files. +if (local_qml) + set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) +endif() +configure_file(src/qml_path_helper.cpp.in src/qml_path_helper.cpp) + if (build_desktop_pay) # The qm files are generated in the build tree, but the qrc file is inside the # source directory and the path to resources are relative to the location of @@ -170,13 +178,9 @@ if (build_desktop_pay) # directory such that it can find the qm translations files. The qrc file is # copied if it doesn't exist in the destination or if it is modified. file(COPY translations/desktop-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) - if (local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/guis/) - endif() - configure_file(src/qml_path_helper.cpp.in guis/desktop/qml_path_helper.cpp) set (SOURCES_PAY src/main.cpp - guis/desktop/qml_path_helper.cpp + ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp src/ModuleManager_empty.cpp # because we don't have modules in the desktop one ) qt6_add_resources(SOURCES_PAY @@ -241,12 +245,11 @@ if (ANDROID AND build_mobile_pay) ${CMAKE_SOURCE_DIR}/data/bip39-spanish DESTINATION ${ASSETS_DIR}) - configure_file(src/qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) set (SOURCES_PAY_MOBILE src/main.cpp src/CameraController.cpp src/QRScanner.cpp - guis/mobile/qml_path_helper.cpp + ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp ${CMAKE_BINARY_DIR}/modules/modules-load.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE @@ -270,10 +273,6 @@ if (ANDROID AND build_mobile_pay) endif () if(NOT ANDROID AND build_mobile_pay) - if(local_qml) - set (QML_PATH ${CMAKE_SOURCE_DIR}/guis) - endif() - configure_file(src/qml_path_helper.cpp.in guis/mobile/qml_path_helper.cpp) file(COPY translations/mobile-i18n.qrc DESTINATION ${CMAKE_BINARY_DIR}) set (SOURCES_PAY_MOBILE @@ -281,7 +280,7 @@ if(NOT ANDROID AND build_mobile_pay) src/CameraController.cpp src/QRScanner.cpp ${CMAKE_BINARY_DIR}/modules/modules-load.cpp - guis/mobile/qml_path_helper.cpp + ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp ) qt6_add_resources(SOURCES_PAY_MOBILE guis/mobile.qrc -- 2.54.0 From 5b8e5ecf098bbdf987e6fc2f7ecc6092a898609e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 14:30:38 +0100 Subject: [PATCH 0764/1428] cleanups of QR showing --- guis/Flowee/WalletSecretsView.qml | 14 +++++++------- guis/mobile/AccountPageListItem.qml | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index 85e982a..a1a8505 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -28,10 +28,10 @@ ListView { // non-layoutable items. QQC2.Popup { - id: seedPopup - width: 260 - height: 260 - x: 55 + id: qrPopup + width: 270 + height: 270 + x: (root.width - width) / 2 y: 100 modal: true closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent @@ -46,6 +46,7 @@ ListView { qrSize: 250 textVisible: false useRawString: true + anchors.centerIn: parent } } } @@ -162,7 +163,7 @@ ListView { text: qsTr("QR of Address") onTriggered: { seedQr.qrText = Pay.chainPrefix + address - seedPopup.open(); + qrPopup.open(); } } QQC2.Action { @@ -172,9 +173,8 @@ ListView { QQC2.Action { text: qsTr("QR of Private Key") onTriggered: { - console.log("-> " + privatekey) seedQr.qrText = privatekey - seedPopup.open(); + qrPopup.open(); } } } diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index cf08a94..984b423 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -90,9 +90,9 @@ QQC2.Control { QQC2.Popup { id: seedPopup - width: 260 - height: 260 - x: 55 + width: 270 + height: 270 + x: (root.width - width) / 2 y: 100 modal: true closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent @@ -107,6 +107,7 @@ QQC2.Control { qrSize: 250 textVisible: false useRawString: true + anchors.centerIn: parent } } } -- 2.54.0 From 19fd943484dcf0370571938d82bffefc2a725a5c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 15:26:08 +0100 Subject: [PATCH 0765/1428] Add comment about the zxing deprecation warning. --- src/CameraController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 3c5b722..0bc51a3 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -96,6 +96,9 @@ private: std::vector readBarcodes(const QVideoFrame &frame) const; CameraControllerPrivate *m_parent; + // notice that since ZXIng 2.2.0 this is renamed to 'ReaderOptions'. + // this was released in December 2023, so we'll be using the old stuff + // for quite a bit longer to keep stuff compiling on older systems. ZXing::DecodeHints m_decodeHints; QRScanner::ScanType m_scanType; }; -- 2.54.0 From 115c0be7176c6ded0e394040be228ba8d812842a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 16:42:05 +0100 Subject: [PATCH 0766/1428] Redo the checkbox and show tooltip on mobile Use the proper configuration options of the class we're inheriting from and make the title label be the 'contentItem'. --- guis/Flowee/CheckBox.qml | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index a354f9f..c6b1f5f 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,14 +25,14 @@ T.CheckBox { property bool sliderOnIndicator: true property string toolTipText: "" - implicitWidth: slider.width + 6 + title.implicitWidth + (toolTipText === "" ? 0 : (questionMarkIcon.width + 16)) - implicitHeight: Math.max(slider.implicitHeight, title.implicitHeight) + implicitWidth: indicator.width + contentItem.implicitWidth + (toolTipText === "" ? 0 : (questionMarkIcon.width + 10)) + implicitHeight: Math.max(indicator.implicitHeight, contentItem.implicitHeight) clip: true + spacing: 6 indicator: Item { - id: slider + implicitHeight: titleLabel.implicitHeight / titleLabel.lineCount implicitWidth: implicitHeight * 2.1 - implicitHeight: title.implicitHeight / title.lineCount Rectangle { anchors.fill: parent @@ -75,26 +75,23 @@ T.CheckBox { } cursorShape: Qt.PointingHandCursor } - - Label { - id: title + contentItem: Label { + id: titleLabel text: control.text - anchors.left: slider.right + opacity: enabled ? 1.0 : 0.7 anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 6 - anchors.right: parent.right color: enabled ? palette.windowText : "darkgray" wrapMode: Text.WrapAtWordBoundaryOrAnywhere + leftPadding: text === "" ? 0 : (control.indicator.width + control.spacing) } Rectangle { id: questionMarkIcon visible: control.toolTipText !== "" && control.enabled - width: title.implicitWidth + 14 - height: title.implicitHeight - anchors.left: title.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 16 + width: q.implicitWidth + 14 + height: q.implicitHeight + x: titleLabel.x + titleLabel.implicitWidth + 10 + anchors.verticalCenter: contentItem.verticalCenter radius: width color: palette.windowText Label { @@ -106,7 +103,6 @@ T.CheckBox { anchors.fill: parent anchors.margins: -7 cursorShape: Qt.PointingHandCursor - id: clicky onClicked: QQC2.ToolTip.show(control.toolTipText, 15000); } } -- 2.54.0 From bebb53b670495c46488bab5eb5da3a1340e8b1dc Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 16:42:41 +0100 Subject: [PATCH 0767/1428] Add ability to scan a private key on import --- guis/mobile/ImportWalletPage.qml | 4 +++- src/CameraController.cpp | 21 +++++++++++++++++---- src/QRScanner.h | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 4b690f0..226cb20 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -80,7 +80,7 @@ Page { QRScanner { id: scanner - scanType: QRScanner.Seed + scanType: QRScanner.SeedOrPrivKey onFinished: if (scanResult !== "") secrets.text = scanResult; } } @@ -119,6 +119,7 @@ Page { Flowee.CheckBox { id: singleAddress + Layout.fillWidth: true text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true @@ -131,6 +132,7 @@ Page { checked: importAccount.isElectrumMnemonic visible: importAccount.isMnemonic enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic + Layout.fillWidth: true property string prevDerPath: "" onCheckedChanged: { derivationLabel.enabled = !checked; diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 0bc51a3..4ce2cdd 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * Copyright (C) 2020 Axel Waggershauser * * This program is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ */ #include "CameraController.h" #include "QRScanner.h" -#include "FloweePay.h" +#include "base58.h" #include #include @@ -295,10 +295,23 @@ void QRScanningThread::run() auto results = readBarcodes(frame); for (const auto &result : results) { const auto &bytes = result.bytes(); - logFatal() << "result:" << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + // logInfo() << "result:" << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); switch (m_scanType) { - case QRScanner::Seed: { + case QRScanner::SeedOrPrivKey: { + // we expect WIF encoded private keys heree + if (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L')) { + // might be one!! + const std::string str(reinterpret_cast(bytes.data()), bytes.size()); + std::vector dummy; + if (Base58::decodeCheck(str, dummy)) { + // good enough for me. Further checking is done by the app, we just exit scanning now. + text = QString::fromUtf8(str); + return; + } + } + // no privkey, ok. Then check if its a seed :-) + /* * The Seed QR would obviously just use the 12 or 24 words sentence in a QR. * Simple. diff --git a/src/QRScanner.h b/src/QRScanner.h index 23cac74..65166c2 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -37,7 +37,7 @@ public: Q_INVOKABLE void abort(); enum ScanType { - Seed, + SeedOrPrivKey, PaymentDetails, PaymentDetailsTestnet // others like private key or cashId -- 2.54.0 From 50c144278263bf7cce163472261701fba8d9b235 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 17:27:11 +0100 Subject: [PATCH 0768/1428] Use actually unique ID for selecting new wallet When the user creates a new wallet, use the unique ID of a wallet to find it and use it afterwards. --- guis/desktop/NewAccountCreateBasicAccount.qml | 12 ++---------- guis/desktop/NewAccountCreateHDAccount.qml | 6 ++---- guis/desktop/NewAccountImportAccount.qml | 7 ++----- guis/mobile/ImportWalletPage.qml | 7 ++----- guis/mobile/NewAccount.qml | 17 +++++++++++++---- src/NewWalletConfig.cpp | 7 +++++++ src/NewWalletConfig.h | 3 +++ 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/guis/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml index 2b053e2..f8f4ed1 100644 --- a/guis/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -48,10 +48,8 @@ ColumnLayout { onClicked: { var options = Pay.createNewBasicWallet(accountName.text); options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { portfolio.current = a; break; } @@ -73,11 +71,5 @@ ColumnLayout { text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") } - /* - Flowee.CheckBox { - id: schnorr - text: qsTr("Default to signing using ECDSA"); - toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") - } */ } } diff --git a/guis/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml index 5cc48f4..78debfd 100644 --- a/guis/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -48,10 +48,8 @@ ColumnLayout { anchors.right: parent.right onClicked: { var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { portfolio.current = a; break; } diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 056a045..645de65 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -108,13 +108,10 @@ GridLayout { var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); else options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { portfolio.current = a; break; } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 226cb20..ba01614 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -216,13 +216,10 @@ Page { var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); else options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.accounts; - for (var i = 0; i < accounts.length; ++i) { - var a = accounts[i]; - if (a.name === options.name) { + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { portfolio.current = a; break; } diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 11fe7a6..97ad817 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -140,8 +140,13 @@ Page { onClicked: { var options = Pay.createNewBasicWallet(accountName.text); options.forceSingleAddress = singleAddress.checked; - var accounts = portfolio.rawAccounts; - portfolio.current = accounts[accounts.length - 1] + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } + thePile.pop(); thePile.pop(); } @@ -194,8 +199,12 @@ Page { text: qsTr("Create") onClicked: { var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); - var accounts = portfolio.rawAccounts; - portfolio.current = accounts[accounts.length - 1] + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } thePile.pop(); thePile.pop(); } diff --git a/src/NewWalletConfig.cpp b/src/NewWalletConfig.cpp index 1da9a37..336023a 100644 --- a/src/NewWalletConfig.cpp +++ b/src/NewWalletConfig.cpp @@ -57,3 +57,10 @@ void NewWalletConfig::setForceSingleAddress(bool on) emit SIChanged(); } } + +int NewWalletConfig::accountId() const +{ + assert(m_wallet); + assert(m_wallet->segment()); + return m_wallet->segment()->segmentId(); +} diff --git a/src/NewWalletConfig.h b/src/NewWalletConfig.h index 585af07..709b8ae 100644 --- a/src/NewWalletConfig.h +++ b/src/NewWalletConfig.h @@ -37,6 +37,7 @@ class NewWalletConfig : public QObject Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(bool forceSingleAddress READ forceSingleAddress WRITE setForceSingleAddress NOTIFY SIChanged) + Q_PROPERTY(int accountId READ accountId CONSTANT FINAL) public: NewWalletConfig() {} explicit NewWalletConfig(Wallet *wallet); @@ -48,6 +49,8 @@ public: bool forceSingleAddress() const; void setForceSingleAddress(bool on); + int accountId() const; + signals: void SIChanged(); -- 2.54.0 From e8a74673bad2721b4eadee9d6a4843f690669347 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 16 Jan 2024 17:46:47 +0100 Subject: [PATCH 0769/1428] Fix 'back' after scan closing the app. The scanner takes the focus away from the current page, so 'back' would not see the page and we'd close the app... --- guis/mobile/ImportWalletPage.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index ba01614..8fa1d62 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -81,7 +81,12 @@ Page { QRScanner { id: scanner scanType: QRScanner.SeedOrPrivKey - onFinished: if (scanResult !== "") secrets.text = scanResult; + onFinished: { + if (scanResult !== "") + secrets.text = scanResult; + // make sure to give focus back to this page after the camera took it. + startScan.forceActiveFocus(); + } } } } -- 2.54.0 From 32c8cf7fcf551d14990fa9d3ab7fcc948391fb4a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 Jan 2024 11:12:47 +0100 Subject: [PATCH 0770/1428] Instead of open as tx, search for one. This helps the user understand better what happened if the transaction isn't known (yet) by the network, since the blockchair service gives a weird (buggy) page in that case. --- src/FloweePay.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 99e69c7..b502401 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -759,11 +759,7 @@ void FloweePay::copyToClipboard(const QString &text) void FloweePay::openInExplorer(const QString &txid) { - if (txid.size() == 64) { // assume this is a txid - QDesktopServices::openUrl(QUrl("https://blockchair.com/bitcoin-cash/transaction/" + txid)); - return; - } - // Add maybe other types? + QDesktopServices::openUrl(QUrl("https://blockchair.com/search?q=" + txid)); } FloweePay::UnitOfBitcoin FloweePay::unit() const -- 2.54.0 From b916079b639ce1feee13e6ca24734f00227e68f8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 17 Jan 2024 15:54:13 +0100 Subject: [PATCH 0771/1428] Rename to have consistent naming. --- guis/mobile.qrc | 2 +- guis/mobile/AccountHistory.qml | 2 +- guis/mobile/{TxInfoSmall.qml => TransactionInfoSmall.qml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename guis/mobile/{TxInfoSmall.qml => TransactionInfoSmall.qml} (100%) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c8954ff..90bd2c2 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -68,7 +68,7 @@ mobile/VisualSeparator.qml mobile/PopupOverlay.qml mobile/TransactionDetails.qml - mobile/TxInfoSmall.qml + mobile/TransactionInfoSmall.qml mobile/AccountSelectorPopup.qml mobile/CurrencySelector.qml mobile/StartupScreen.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 2e7f345..898848c 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -389,7 +389,7 @@ ListView { } Component { id: selectedItem - TxInfoSmall { } + TransactionInfoSmall { } } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } diff --git a/guis/mobile/TxInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml similarity index 100% rename from guis/mobile/TxInfoSmall.qml rename to guis/mobile/TransactionInfoSmall.qml -- 2.54.0 From c63460c6f69392125b758d5baffc61b4423540f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 18 Jan 2024 19:54:51 +0100 Subject: [PATCH 0772/1428] Refactor the broadcast of transactions This changes two implementations of the BroadcastTxData baseclass to now use one which is a lot more robust and thread-safe as well as fixing various smaller issues. --- src/CMakeLists.txt | 1 + src/Payment.cpp | 95 +++++------------------ src/Payment.h | 8 +- src/Payment_p.h | 45 ----------- src/TxInfoObject.cpp | 166 +++++++++++++++++++++++++++++++++++++++++ src/TxInfoObject.h | 99 ++++++++++++++++++++++++ src/Wallet.cpp | 82 ++++++++++---------- src/Wallet.h | 8 +- src/Wallet_p.h | 29 ------- src/Wallet_support.cpp | 55 -------------- 10 files changed, 336 insertions(+), 252 deletions(-) delete mode 100644 src/Payment_p.h create mode 100644 src/TxInfoObject.cpp create mode 100644 src/TxInfoObject.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de3a609..dfe729e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ set (PAY_SOURCES QRCreator.cpp QMLClipboardHelper.cpp TransactionInfo.cpp + TxInfoObject.cpp Wallet.cpp WalletCoinsModel.cpp AccountConfig.cpp diff --git a/src/Payment.cpp b/src/Payment.cpp index 4f37a22..e5ef3c4 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,6 @@ */ #include "Payment.h" -#include "Payment_p.h" #include "PaymentDetailInputs_p.h" #include "PaymentDetailOutput_p.h" #include "FloweePay.h" @@ -25,6 +24,7 @@ #include "PriceDataProvider.h" #include "AccountConfig.h" #include "PaymentProtocol.h" +#include "TxInfoObject.h" #include #include @@ -372,7 +372,14 @@ void Payment::broadcast() if (!m_userComment.isEmpty()) m_wallet->setTransactionComment(m_tx, m_userComment); - m_infoObject = std::make_shared(this, m_tx); + m_infoObject = std::make_shared(this, m_tx); + connect(m_infoObject.get(), &TxInfoObject::sentOne, this, [=]() { + emit broadcastStatusChanged(); + // we wait a second and force a recalc in the following singleShot. + QTimer::singleShot(1000, this, SIGNAL(broadcastStatusChanged())); + }, Qt::QueuedConnection); + // notice that rejections are automatically forwarded to the wallet from the TxInfoObject + connect(m_infoObject.get(), SIGNAL(rejectionSeen()), this, SIGNAL(broadcastStatusChanged()), Qt::QueuedConnection); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); m_txBroadcastStarted = true; emit broadcastStatusChanged(); @@ -388,8 +395,6 @@ void Payment::reset() m_tx = Tx(); m_assignedFee = 0; m_infoObject.reset(); - m_sentPeerCount = 0; - m_rejectedPeerCount = 0; m_userComment.clear(); m_wallet = nullptr; @@ -502,8 +507,6 @@ void Payment::confirmReceivedOk() if (m_infoObject.get() == nullptr) return; m_infoObject.reset(); - m_sentPeerCount = 1; - m_rejectedPeerCount = 0; emit broadcastStatusChanged(); } @@ -634,11 +637,18 @@ Payment::BroadcastStatus Payment::broadcastStatus() const #endif if (!m_txBroadcastStarted) return NotStarted; - if (m_sentPeerCount == 0) + auto infoObject = m_infoObject; + if (infoObject.get() == nullptr) + return TxBroadcastSuccess; + // The 'calcStatus' arg should match (or be slightly less than) the wait time before we do the + // emit making QML re-fetch this variable. + auto status = infoObject->calcStatus(950); + logDebug() << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; + if (status.sent == 0) return TxOffered; - if (m_rejectedPeerCount - m_sentPeerCount >= 0) + if (status.failed) return TxRejected; - if (m_infoObject.get() == nullptr) + if (status.sent - status.inProgress >= 2) return TxBroadcastSuccess; return TxWaiting; } @@ -705,40 +715,6 @@ Wallet *Payment::wallet() const return m_wallet; } -// this callback happens when one of our peers did a getdata for the transaction. -void Payment::sentToPeer() -{ - /* - * We offered the Tx to all peers (typically 3) and we need at least one to have - * downloaded it from us. - * Possibly more peers will download it from us, but it is more likely that they - * download it from each other and we won't be able to see progress. - * - * On the other hand, when a transaction is invalid we will get a rejection message - * and then likely other nodes will download it from us as well and we'll get more - * rejections that way. For this reason we need to wait a seconds after download - * started to see if we have more peers we sent it to than that rejected it. - */ - ++m_sentPeerCount; - - QTimer::singleShot(1000, this, [=]() { - if (m_rejectedPeerCount == 0 || m_sentPeerCount > 1) { - // When enough peers received the transaction stop broadcasting it. - m_infoObject.reset(); - } - emit broadcastStatusChanged(); - }); - emit broadcastStatusChanged(); -} - -void Payment::txRejected(short reason, const QString &message) -{ - // reason is hinted using BroadcastTxData::RejectReason - logCritical() << "Transaction rejected" << reason << message; - ++m_rejectedPeerCount; - emit broadcastStatusChanged(); -} - void Payment::recalcAmounts() { // Find the output that has 'max' and give it a change to recalculate the effective values. @@ -854,36 +830,6 @@ int Payment::txSize() const } -// ////////////////////////////////////////////////// - -PaymentInfoObject::PaymentInfoObject(Payment *payment, const Tx &tx) - : BroadcastTxData(tx), - m_parent(payment) -{ - connect(this, SIGNAL(sentOneFired()), payment, SLOT(sentToPeer()), Qt::QueuedConnection); - connect(this, SIGNAL(txRejectedFired(short,QString)), payment, - SLOT(txRejected(short,QString)), Qt::QueuedConnection); -} - -void PaymentInfoObject::txRejected(RejectReason reason, const std::string &message) -{ - emit txRejectedFired(reason, QString::fromStdString(message)); -} - -void PaymentInfoObject::sentOne() -{ - emit sentOneFired(); -} - -uint16_t PaymentInfoObject::privSegment() const -{ - assert(m_parent); - assert(m_parent->wallet()); - assert(m_parent->wallet()->segment()); - return m_parent->wallet()->segment()->segmentId(); -} - - // /////////////////////////////// PaymentDetail::PaymentDetail(Payment *parent, Payment::DetailType type) @@ -939,4 +885,3 @@ bool PaymentDetail::valid() const void PaymentDetail::setWallet(Wallet *) { } - diff --git a/src/Payment.h b/src/Payment.h index a5596c8..0d1a45a 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -31,6 +30,7 @@ class PaymentDetail; class PaymentDetailOutput; class PaymentDetailInputs; class AccountInfo; +class TxInfoObject; class Payment : public QObject @@ -261,8 +261,6 @@ public: void setSimpleAddressTarget(bool newSimpleAddressTarget); private slots: - void sentToPeer(); - void txRejected(short reason, const QString &message); void recalcAmounts(); void broadcast(); @@ -312,9 +310,7 @@ private: int m_fee; // in sats per byte int m_assignedFee; int m_fiatPrice = 50000; // price for one whole BCH - std::shared_ptr m_infoObject; - short m_sentPeerCount; - short m_rejectedPeerCount; + std::shared_ptr m_infoObject; Wallet *m_wallet = nullptr; QString m_error; QString m_userComment; diff --git a/src/Payment_p.h b/src/Payment_p.h deleted file mode 100644 index be7cb21..0000000 --- a/src/Payment_p.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2020 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef PAYMENTINFOOBJECT_P_H -#define PAYMENTINFOOBJECT_P_H - -#include -#include - -class Tx; -class Payment; - -class PaymentInfoObject : public QObject, public BroadcastTxData -{ - Q_OBJECT -public: - PaymentInfoObject(Payment *payment, const Tx &tx); - - void txRejected(RejectReason reason, const std::string &message) override; - void sentOne() override; - uint16_t privSegment() const override; - -signals: - void sentOneFired(); - void txRejectedFired(short reason, const QString &message); - -private: - Payment *m_parent; -}; - -#endif diff --git a/src/TxInfoObject.cpp b/src/TxInfoObject.cpp new file mode 100644 index 0000000..e54c1b1 --- /dev/null +++ b/src/TxInfoObject.cpp @@ -0,0 +1,166 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TxInfoObject.h" + +#include "Wallet.h" +#include "Payment.h" + +#include +#include + +#include + +constexpr int SECTION = 10004; + +TxInfoObject::TxInfoObject(Payment *payment, const Tx &tx) + : BroadcastTxData(tx), + m_wallet(payment->wallet()) +{ + assert(payment); + assert(m_wallet); + auto iter = m_wallet->m_txidCache.find(hash()); + assert(iter != m_wallet->m_txidCache.end()); + m_txIndex = iter->second; + + connect(this, SIGNAL(finished(int,bool)), + m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); +} + +TxInfoObject::TxInfoObject(Wallet *wallet, int txIndex, const Tx &tx) + : BroadcastTxData(tx), + m_wallet(wallet), + m_txIndex(txIndex) +{ + connect(this, SIGNAL(finished(int,bool)), + m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); +} + +void TxInfoObject::txRejected(int connectionId, RejectReason reason, const std::string &message) +{ + // reason is hinted using BroadcastTxData::RejectReason + logCritical(SECTION) << "Transaction rejected" << reason << message; + + /* + * This is called directly from a peer's network processing code. Each peer may + * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, + * and thus this method needs to be thread-safe. + */ + QMutexLocker l(&m_lock); + for (auto iter = m_broadcasts.begin(); iter != m_broadcasts.end(); ++iter) { + if (iter->connectionId == connectionId) { + assert(iter->failed == false); // the p2p lib should avoid this + iter->failed = true; + iter->rejected = reason; + iter->rejectMsg = message; + break; + } + } + emit rejectionSeen(); +} + +uint16_t TxInfoObject::privSegment() const +{ + assert(m_wallet); + assert(m_wallet->segment()); + return m_wallet->segment()->segmentId(); +} + +int TxInfoObject::txIndex() const +{ + return m_txIndex; +} + +TxInfoObject::Status TxInfoObject::calcStatus(int waitingTimeoutMS) const +{ + Status rc; + auto copy = m_broadcasts; + rc.sent = copy.size(); + rc.inProgress = 0; + + const auto time = std::chrono::system_clock::now(); + const auto milliTime = std::chrono::duration_cast(time.time_since_epoch()); + const uint64_t cutoff = milliTime.count() - waitingTimeoutMS; + + for (const auto &broadcast : copy) { + if (broadcast.failed) + rc.failed++; + else if (broadcast.sentTime > cutoff) + rc.inProgress++; + } + return rc; +} + +/* + * The broadcast happens in INV style, and we wait for the peer to then get the data, + * which we notice with the 'sentOne' call. Called once per peer. + * But likely only one or maybe two actually call this as the more likely scenario + * (especially on slower networks) is that they get the transaction from each other. + * + * Except when the transaction is bad, and we get a rejected message (but only once) + * and naturally that once is from each peer. + * And that is where this method is most useful, to detect that the network rejects + * our transaction. + */ +void TxInfoObject::checkState() +{ + assert(thread() == QThread::currentThread()); + Status status = calcStatus(3000); + logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; + // a typical wallet has 3 peers. + // we react when at least 2 downloaded our transaction and didn't report a failure + if (status.sent - status.inProgress >= 2) { + emit finished(m_txIndex, + /* bool success = */ status.sent - status.inProgress - status.failed >= 1); + } +} + +void TxInfoObject::sentVia(const std::shared_ptr &peer) +{ + assert(peer.get()); + /* + * This is called directly from a peer's network processing code. Each peer may + * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, + * and thus this method needs to be thread-safe. + */ + QMutexLocker l(&m_lock); + const auto id = peer->connectionId(); + for (auto iter = m_broadcasts.begin(); iter != m_broadcasts.end(); ++iter) { + if (iter->connectionId == id) + return; + } + + Broadcast broadcast; + broadcast.connectionId = id; + broadcast.ep = peer->peerAddress().peerAddress(); + const auto time = std::chrono::system_clock::now(); + const auto millis = std::chrono::duration_cast(time.time_since_epoch()); + broadcast.sentTime = millis.count(); + m_broadcasts.append(broadcast); + + if (m_broadcasts.size() >= 2) { + // Two stage singleshots. First (Qt requires that to be zero ms) to move + // to a thread that Qt owns. + // Second (in the lambda) to actually wait several seconds. + QTimer::singleShot(0, this, [=]() { + assert(thread() == QThread::currentThread()); + QTimer::singleShot(4 * 1000, this, SLOT(checkState())); + }); + } + + emit sentOne(); +} diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h new file mode 100644 index 0000000..401fd4f --- /dev/null +++ b/src/TxInfoObject.h @@ -0,0 +1,99 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TXINFOOBJECT_H +#define TXINFOOBJECT_H + +#include +#include +#include +#include +#include + + +class Wallet; +class Payment; + + +/** + * This is used to broadcast transactions. + * + * The transaction is given to the p2p library where it will be send to all peers + * associated with the wallet. It will be a 2 stage effort, first an INV then as the + * peer asks the actual tx-data. + * + * The actual creator of the transaction data should have a shared pointer to this + * instance and deleting it will stop the broadcast and us learning about reject + * messages. + * + * The Payment object creates a transaction and creates an instance to let the + * rest of the system know. + * The wallet will go through a similar process whenever a new block is found and + * a transaction it holds is not mined (nor made invalid), broadcasting the unconfirmed + * transaction to all peers. + * The p2p layer will also send it to new peers the moment they connect (and get + * associated with this wallet). + * + * @see ConnectionManager::broadcastTransaction() + */ +class TxInfoObject : public QObject, public BroadcastTxData +{ + Q_OBJECT +public: + TxInfoObject(Payment *payment, const Tx &tx); + TxInfoObject(Wallet *parent, int txIndex, const Tx &tx); + + void sentVia(const std::shared_ptr &peer) override; + void txRejected(int connectionId, RejectReason reason, const std::string &message) override; + uint16_t privSegment() const override; + + int txIndex() const; + + struct Status { + int sent = 0; // number of peers we sent this transaction to. + int inProgress = 0; // number of peers in progress. + int failed = 0; // number of peers reported failure. + }; + Status calcStatus(int waitingTimeoutMS = 3000) const; + +private slots: + // called some seconds after the request from a peer for data + void checkState(); + +signals: + void finished(int txIndex, bool success); + void sentOne(); + void rejectionSeen(); + +private: + mutable QMutex m_lock; + const Wallet *m_wallet; + int m_txIndex; + + struct Broadcast { + int connectionId = -1; + EndPoint ep; + bool failed = false; + RejectReason rejected = NoReason; + std::string rejectMsg; + uint64_t sentTime = 0; // ms since epoch. + }; + QList m_broadcasts; +}; + + +#endif diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 2c72f67..d28cffe 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -18,6 +18,7 @@ #include "Wallet.h" #include "Wallet_p.h" #include "FloweePay.h" +#include "TxInfoObject.h" #include #include @@ -34,6 +35,7 @@ #include #include +#include // #define DEBUGUTXO @@ -364,7 +366,7 @@ void Wallet::newTransaction(const Tx &tx) std::map signatureTypes; WalletTransaction wtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); - Q_ASSERT(wtx.isCoinbase == false); + assert(wtx.isCoinbase == false); if (wtx.outputs.empty() && wtx.inputToWTX.empty()) { // no connection to our UTXOs if (++m_bloomScore > 25) @@ -544,7 +546,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: // and remember the transaction if (oldTx == m_txidCache.end()) { - Q_ASSERT(walletTransactionId == m_nextWalletTransactionId); + assert(walletTransactionId == m_nextWalletTransactionId); m_txidCache.insert(std::make_pair(wtx.txid, m_nextWalletTransactionId)); m_walletTransactions.insert(std::make_pair(m_nextWalletTransactionId++, wtx)); transactionsToSave.push_back(tx); @@ -571,7 +573,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: // We actually mark it Rejected (in the blockHeight). for (auto ejectedTx : ejectedTransactions) { auto tx = m_walletTransactions.find(ejectedTx); - Q_ASSERT(tx != m_walletTransactions.end()); + assert(tx != m_walletTransactions.end()); logDebug(LOG_WALLET) << "Confirmed transaction(s) in block" << blockHeight << "made invalid transaction:" << ejectedTx << tx->second.txid; auto &wtx = tx->second; @@ -1042,7 +1044,7 @@ void Wallet::rebuildBloom(RebuildBloomOption option) std::set secretsWithBalance; for (auto utxo : m_unspentOutputs) { auto i = m_walletTransactions.find(OutputRef(utxo.first).txIndex()); - Q_ASSERT(i != m_walletTransactions.end()); + assert(i != m_walletTransactions.end()); const auto &wtx = i->second; if (i->second.minedBlockHeight > 1) { for (const auto &output : wtx.outputs) { @@ -1155,43 +1157,45 @@ void Wallet::broadcastTxFinished(int txIndex, bool success) QMutexLocker locker(&m_lock); for (int i = 0; i < m_broadcastingTransactions.size(); ++i) { if (m_broadcastingTransactions.at(i)->txIndex() == txIndex) { + // delete the TxInfoObject first, stopping broadcasts. m_broadcastingTransactions.removeAt(i); + break; + } + } - if (!success) { - auto wtx = m_walletTransactions.find(txIndex); - if (wtx != m_walletTransactions.end()) { - logCritical(LOG_WALLET) << "Marking transaction invalid"; - auto &tx = wtx->second; - if (tx.minedBlockHeight == Wallet::Unconfirmed) { - // a transaction that has been added before, but now marked - // rejected means we should revert some stuff that newTransaction() did. - // - locked output - // - utxo - // - balance + if (!success) { + auto wtx = m_walletTransactions.find(txIndex); + if (wtx != m_walletTransactions.end()) { + logCritical(LOG_WALLET) << "Marking transaction invalid"; + auto &tx = wtx->second; + if (tx.minedBlockHeight == Wallet::Unconfirmed) { + // a transaction that has been added before, but now marked + // rejected means we should revert some stuff that newTransaction() did. + // - locked output + // - utxo + // - balance - auto j = m_lockedOutputs.begin(); - while (j != m_lockedOutputs.end()) { - if (j->second == txIndex) - j = m_lockedOutputs.erase(j); - else - ++j; - } - for (auto out = tx.outputs.begin(); out != tx.outputs.end(); ++out) { - auto utxo = m_unspentOutputs.find(OutputRef(txIndex, out->first).encoded()); - assert(utxo != m_unspentOutputs.end()); - m_unspentOutputs.erase(utxo); - } - - recalculateBalance(); - } - else { - assert(false); // Can't imagine the usecase, so if this hits in a debug build lets fail-fast - logWarning(LOG_WALLET) << "Transaction marked rejected that had blockHeight:" << tx.minedBlockHeight; - } - tx.minedBlockHeight = Wallet::Rejected; + auto j = m_lockedOutputs.begin(); + while (j != m_lockedOutputs.end()) { + if (j->second == txIndex) + j = m_lockedOutputs.erase(j); + else + ++j; } + for (auto out = tx.outputs.begin(); out != tx.outputs.end(); ++out) { + auto utxo = m_unspentOutputs.find(OutputRef(txIndex, out->first).encoded()); + assert(utxo != m_unspentOutputs.end()); + m_unspentOutputs.erase(utxo); + } + + recalculateBalance(); } - return; + else if (tx.minedBlockHeight > 0) { + assert(false); // Can't imagine the usecase, so if this hits in a debug build lets fail-fast + logWarning(LOG_WALLET) << "Transaction marked rejected that had blockHeight:" << tx.minedBlockHeight; + } + tx.minedBlockHeight = Wallet::Rejected; + emit transactionChanged(txIndex); // notify the UI } } } @@ -1448,7 +1452,7 @@ void Wallet::checkHeaderSyncComplete(const Blockchain &blockchain) void Wallet::broadcastUnconfirmed() { - Q_ASSERT(thread() == QThread::currentThread()); + assert(thread() == QThread::currentThread()); // we are (again) up-to-date. // Lets broadcast any transactions that have not yet been confirmed. @@ -1461,7 +1465,7 @@ void Wallet::broadcastUnconfirmed() if (iter->second.minedBlockHeight == Wallet::Unconfirmed) { auto tx = loadTransaction(iter->second.txid, Streaming::pool(0)); if (tx.data().size() > 64) { - auto bc = std::make_shared(this, iter->first, tx); + auto bc = std::make_shared(this, iter->first, tx); bc->moveToThread(thread()); logDebug(LOG_WALLET) << " broadcasting transaction" << tx.createHash() << tx.size(); m_broadcastingTransactions.append(bc); @@ -2184,7 +2188,7 @@ void Wallet::recalculateBalance() qint64 balanceUnconfirmed = 0; for (auto utxo : m_unspentOutputs) { auto wtx = m_walletTransactions.find(OutputRef(utxo.first).txIndex()); - Q_ASSERT(wtx != m_walletTransactions.end()); + assert(wtx != m_walletTransactions.end()); const int h = wtx->second.minedBlockHeight; if (h == Wallet::Rejected) continue; diff --git a/src/Wallet.h b/src/Wallet.h index 01ecff5..b77a8d9 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -34,7 +34,7 @@ #include #include -class WalletInfoObject; +class TxInfoObject; class TransactionInfo; class WalletKeyView; class Blockchain; @@ -386,9 +386,10 @@ public slots: void delayedSave(); protected slots: - void broadcastTxFinished(int txIndex, bool success); /// find all not-yet-confirmed transactions and start a broadcast void broadcastUnconfirmed(); + /// callback from the broadcast code: TxInfoObject + void broadcastTxFinished(int txIndex, bool success); void recalculateBalance(); void delayedSaveTimeout(); @@ -600,6 +601,7 @@ private: friend class WalletSecretsModel; friend class WalletCoinsModel; friend class WalletKeyView; + friend class TxInfoObject; // auto-detected, causes us to send bigger gaps for bloom filters. bool m_walletStoresCashFusions = false; @@ -623,7 +625,7 @@ private: std::vector> m_encryptionKey; std::vector> m_encryptionIR; - QList> m_broadcastingTransactions; + QList> m_broadcastingTransactions; }; #endif diff --git a/src/Wallet_p.h b/src/Wallet_p.h index dad64be..ebbcb81 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -121,33 +121,4 @@ enum OutputLockStateEnum { } -// this is used to broadcast transactions -// see ConnectionManager::broadcastTransaction() -class WalletInfoObject : public QObject, public BroadcastTxData -{ - Q_OBJECT -public: - WalletInfoObject(Wallet *parent, int txIndex, const Tx &tx); - void txRejected(RejectReason reason, const std::string &message); - void sentOne(); - uint16_t privSegment() const; - - int txIndex() const; - -private slots: - // scheduled from a non-Qt thread to start a timer. - void startCheckState(); - // called 5 seonds after every request from a peer for data - void checkState(); - -signals: - void finished(int txIndex, bool success); - -private: - const Wallet *m_wallet; - const int m_txIndex; - short m_sentPeerCount = 0; - short m_rejectedPeerCount = 0; -}; - #endif diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 35bb920..7f4c7b5 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -125,61 +125,6 @@ uint64_t Wallet::OutputRef::encoded() const } -// ////////////////////////////////////////////////// - -WalletInfoObject::WalletInfoObject(Wallet *wallet, int txIndex, const Tx &tx) - : BroadcastTxData(tx), - m_wallet(wallet), - m_txIndex(txIndex) -{ - connect(this, SIGNAL(finished(int,bool)), - m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); -} - -void WalletInfoObject::txRejected(RejectReason reason, const std::string &message) -{ - // reason is hinted using BroadcastTxData::RejectReason - logCritical(LOG_WALLET) << "Transaction rejected" << reason << message; - ++m_rejectedPeerCount; -} - -void WalletInfoObject::sentOne() -{ - if (++m_sentPeerCount >= 2) { - // Two stage singleshots. First (Qt requires that to be zero ms) to move - // to a thread that Qt owns. - // Second (in startCheckState()) to actually wait several seconds. - QTimer::singleShot(0, this, SLOT(startCheckState())); - } -} - -void WalletInfoObject::startCheckState() -{ - Q_ASSERT(thread() == QThread::currentThread()); - QTimer::singleShot(5 * 1000, this, SLOT(checkState())); -} - -void WalletInfoObject::checkState() -{ - Q_ASSERT(thread() == QThread::currentThread()); - if (m_sentPeerCount >= 2) { - emit finished(m_txIndex, m_sentPeerCount - m_rejectedPeerCount >= 1); - } -} - -int WalletInfoObject::txIndex() const -{ - return m_txIndex; -} - -uint16_t WalletInfoObject::privSegment() const -{ - assert(m_wallet); - assert(m_wallet->segment()); - return m_wallet->segment()->segmentId(); -} - - // ////////////////////////////////////////////////// bool Wallet::WalletTransaction::isUnconfirmed() const -- 2.54.0 From 0011698e637099d3e29d28ce306cbfc138872217 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 19 Jan 2024 12:33:32 +0100 Subject: [PATCH 0773/1428] Minor fixes and adding asserts. --- src/PaymentRequest.cpp | 11 +++++++---- src/Wallet.cpp | 1 + src/WalletKeyView.cpp | 11 ++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index 0938a0b..d134f35 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -126,14 +126,17 @@ void PaymentRequest::setAccount(QObject *account) assert(QThread::currentThread() == thread()); m_view = new WalletKeyView(m_account->wallet(), this); connect (m_view, &WalletKeyView::walletEncrypted, this, [=]() { + assert(QThread::currentThread() == thread()); updateFailReason(); }); connect (m_view, &WalletKeyView::importFinished, this, [=]() { + assert(QThread::currentThread() == thread()); updateFailReason(); start(); }); connect (m_view, &WalletKeyView::transactionMatch, this, [=]() { - uint64_t seen = 0; + assert(QThread::currentThread() == thread()); + int64_t seen = 0; for (const auto &tx : m_view->transactions()) { if (tx.state != WalletKeyView::UTXORejected) { seen += tx.amount; @@ -184,9 +187,9 @@ void PaymentRequest::start() #if 0 // by enabling this you can simulate the payment request being fulfilled - QTimer::singleShot(5000, [=]() { + QTimer::singleShot(5000, this, [=]() { setPaymentState(PaymentSeen); - QTimer::singleShot(3000, [=]() { + QTimer::singleShot(3000, this, [=]() { setPaymentState(PaymentSeenOk); // setPaymentState(DoubleSpentSeen); }); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d28cffe..c9ef648 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -720,6 +720,7 @@ void Wallet::setTransactionComment(const Tx &transaction, const QString &comment void Wallet::setTransactionComment(int txIndex, const QString &comment) { + QMutexLocker locker(&m_lock); auto wtxIter = m_walletTransactions.find(txIndex); assert(wtxIter != m_walletTransactions.end()); if (wtxIter == m_walletTransactions.end()) diff --git a/src/WalletKeyView.cpp b/src/WalletKeyView.cpp index 7b7ea1e..9753b6e 100644 --- a/src/WalletKeyView.cpp +++ b/src/WalletKeyView.cpp @@ -1,7 +1,6 @@ - /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +19,8 @@ #include "WalletKeyView.h" #include "Wallet.h" +#include + WalletKeyView::WalletKeyView(Wallet *wallet, QObject *parent) : QObject(parent), m_wallet(wallet) @@ -49,8 +50,9 @@ void WalletKeyView::appendedTransactions(const int firstNew, const int count) { if (m_walletIsImporting) return; + assert(QThread::currentThread() == thread()); QMutexLocker locker(&m_wallet->m_lock); - // logDebug() << " getting" << index.row() << "=>" << txIndex; + logInfo() << " getting new tx. Index:" << firstNew << "count:" << count; for (int txIndex = firstNew; txIndex < firstNew + count; ++txIndex) { auto itemIter = m_wallet->m_walletTransactions.find(txIndex); if (itemIter == m_wallet->m_walletTransactions.end()) // the wallet changed, we're probably waiting for a queued connection @@ -84,6 +86,7 @@ void WalletKeyView::appendedTransactions(const int firstNew, const int count) void WalletKeyView::lastBlockSynchedChanged() { + assert(QThread::currentThread() == thread()); if (m_walletIsImporting && !m_wallet->walletIsImporting()) { m_walletIsImporting = false; emit importFinished(); @@ -97,6 +100,7 @@ QList WalletKeyView::transactions() const void WalletKeyView::transactionConfirmed(int txIndex) { + assert(QThread::currentThread() == thread()); if (m_walletIsImporting) return; auto refBase = Wallet::OutputRef(txIndex, 0).encoded(); @@ -112,6 +116,7 @@ void WalletKeyView::transactionConfirmed(int txIndex) void WalletKeyView::encryptionChanged() { + assert(QThread::currentThread() == thread()); if (m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted()) emit walletEncrypted(); } -- 2.54.0 From 19ff7fde1013741718c2585211d7c8d405b91173 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 22 Jan 2024 15:30:40 +0100 Subject: [PATCH 0774/1428] Forgot this one --- android/build-pay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index bc74ee0..f8a0d4d 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -156,4 +156,4 @@ HERE chmod 700 smartBuild.sh fi -./smartBuild.sh noapk +./smartBuild.sh -- 2.54.0 From 667f8166d352a548ce2cb70fec45312aea0d1cf2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 22 Jan 2024 17:34:28 +0100 Subject: [PATCH 0775/1428] Complete the transform from blocktime to tx-time. We rename the enum and add some asserts indicating the new purpose. This also makes the code more robust with a try/catch and fixes a possible crash when a transaction doesn't have either a date or a blockheight. --- src/WalletHistoryModel.cpp | 22 +++++++++++++++++----- src/WalletHistoryModel.h | 4 ++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 24fd7e8..28430cc 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -128,10 +128,15 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const return QVariant(QString::fromStdString(item.txid.ToString())); case MinedHeight: return QVariant(item.minedBlockHeight); - case MinedDate: { + case TxDate: { int64_t timestamp = item.transactionTime; + if (timestamp == 0) { + // lets see if it was mined, we then take the blocktime + if (item.minedBlockHeight > 0) + timestamp = secsSinceEpochFor(item.minedBlockHeight); + } if (timestamp == 0) - timestamp = secsSinceEpochFor(item.minedBlockHeight); + return QVariant(); // undefined. return QVariant(QDateTime::fromSecsSinceEpoch(timestamp)); } case FundsIn: { @@ -236,7 +241,7 @@ QHash WalletHistoryModel::roleNames() const answer[NewTransaction] = "isNew"; // Deprecated answer[TxId] = "txid"; answer[MinedHeight] = "height"; - answer[MinedDate] = "date"; + answer[TxDate] = "date"; answer[FundsIn] = "fundsIn"; answer[FundsOut] = "fundsOut"; answer[WalletIndex] = "walletIndex"; @@ -484,10 +489,17 @@ bool WalletHistoryModel::isModelFrozen() const uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const { + assert(blockHeight > 0); // wrap this for convenience and also ensure that we never return an insanely old // date (1970) just because we lack blockheader data. - return std::max(1250000000, - FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime); + try { + return std::max(1250000000, + FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime); + } catch (const std::exception &e) { + // a blockheight we don't have. + assert(false); // for debug builds, please figure out if the caller can be improved + return 0; + } } QDate WalletHistoryModel::today() const diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 3c5b60c..14b4f36 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,7 +45,7 @@ public: TxId = Qt::UserRole, NewTransaction, ///< Deprecated MinedHeight, ///< int, height of block this tx was mined at. - MinedDate, ///< A date-time object when the item was mined + TxDate, ///< A date-time object when the item was created FundsIn, ///< value (in sats) of the funds we own being spent FundsOut, ///< value (in sats) of the outputs created we own WalletIndex, ///< wallet-internal index for this transaction. -- 2.54.0 From 4fbfb51f6b65c828d8f4f5feda0ba54761421f90 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 22 Jan 2024 17:36:09 +0100 Subject: [PATCH 0776/1428] Make unconfirmed transactions show properly After we changed the TX to always have a date at creation, the desktop GUI code's assumptions were undermined and it would be able to show weird stuff. This fixes it properly and shows the right data. --- guis/desktop/WalletTransaction.qml | 3 ++- guis/desktop/WalletTransactionDetails.qml | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/WalletTransaction.qml index 8f716e8..4d5e3fe 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/WalletTransaction.qml @@ -30,7 +30,8 @@ Rectangle { width: mainLabel.width + bitcoinAmountLabel.width + 30 color: (index % 2) == 0 ? palette.light : palette.alternateBase - property bool isRejected: model.height === -2 // -2 is the magic block-height indicating 'rejected' + property int minedHeight: model.height + property bool isRejected: minedHeight === -2 // -2 is the magic block-height indicating 'rejected' /* we have diff --git a/guis/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml index b4c6e50..07d5288 100644 --- a/guis/desktop/WalletTransactionDetails.qml +++ b/guis/desktop/WalletTransactionDetails.qml @@ -24,7 +24,7 @@ GridLayout { id: root property QtObject infoObject: null - property var minedDate: model.date + property var createDate: model.date columns: 2 Flowee.LabelWithClipboard { @@ -42,9 +42,11 @@ GridLayout { text: { if (txRoot.isRejected) return qsTr("rejected") - if (typeof root.minedDate === "undefined") + if (txRoot.minedHeight === -1) return qsTr("unconfirmed") - var confirmations = Pay.headerChainHeight - model.height + 1; + var confirmations = Pay.headerChainHeight - txRoot.minedHeight + 1; + if (confirmations < 0) + return ""; return qsTr("%1 confirmations (mined in block %2)", "", confirmations) .arg(confirmations).arg(model.height); } -- 2.54.0 From 2414c3457f11c39525755a084eba4129526ef86e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 Jan 2024 19:47:55 +0100 Subject: [PATCH 0777/1428] Update the blocktime faster on active sync. --- guis/desktop/AccountDetails.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 79b4a72..80a289d 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -113,11 +113,24 @@ Item { time = " (" + syncLabel.time + ")"; return qsTr("Sync Status") + ": " + height + " / " + Pay.chainHeight + time; } + Connections { + target: root.account; + function onLastBlockSynchedChanged() { + if (timeTimer.interval === 30000) { + // if it just changed, fetch the new time shortly thereafter. + // this makes us show the latest time much more often when doing a sync. + timeTimer.stop(); + timeTimer.interval = 500; + timeTimer.start(); + } + } + } Timer { // the lastBlockSynchedTime does not change, // but since we render it as '12 minutes ago' // we need to actually re-interpret that // ever so often to keep the relative time. + id: timeTimer running: !root.account.isArchived interval: 30000 // 30 sec repeat: true @@ -125,6 +138,7 @@ Item { onTriggered: { syncLabel.time = Pay.formatDateTime( root.account.lastBlockSynchedTime); + interval = 30000; // 30 sec } } } -- 2.54.0 From 875438f0e92929597232252f8bb88ebf589d2b58 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 Jan 2024 19:48:21 +0100 Subject: [PATCH 0778/1428] Make sure the build stops on error. --- android/build-pay.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index f8a0d4d..c878fab 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -146,8 +146,7 @@ if test -f $floweePaySrcDir/android/netlog.conf; then cp $floweePaySrcDir/android/netlog.conf android-build/assets/ fi -\$execInDocker /usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir - +\$execInDocker /usr/bin/ninja -C build pay_mobile pay_mobile_prepare_apk_dir && \ if test "\$1" = "sign" -o "\$2" = "sign"; then \$execInDocker build/.sign fi -- 2.54.0 From 5c3642fd3b05efdc45bf36c1ed7c520e2ef8bf09 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 Jan 2024 19:48:56 +0100 Subject: [PATCH 0779/1428] Swap declaration, following linter suggestion. --- src/AccountInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index bd80557..020e52f 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -33,8 +33,8 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) : QObject(parent), m_wallet(wallet), m_config(m_wallet), - m_initialBlockHeight(wallet->segment()->lastBlockSynched()), - m_accountStartBlockHeight(wallet->segment()->firstBlock()) + m_accountStartBlockHeight(wallet->segment()->firstBlock()), + m_initialBlockHeight(wallet->segment()->lastBlockSynched()) { connect(wallet, SIGNAL(utxosChanged()), this, SIGNAL(utxosChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(balanceChanged()), this, SLOT(balanceHasChanged()), Qt::QueuedConnection); -- 2.54.0 From 7e8c3d58241dae64322795922c4b4cec7a97f498 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 Jan 2024 21:05:59 +0100 Subject: [PATCH 0780/1428] Cover older unconfirmed transaction display This makes sure that we update the last-mined-date also when a transaction is simply confirmed that was already in the wallet. This only really shows if you were offline and only later broadcast that transaction, which was unusual enough for me to never notice before :-) --- src/AccountInfo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 020e52f..54afbf8 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,6 +50,7 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) }, Qt::QueuedConnection); connect(wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(userOwnedChanged()), this, SIGNAL(userOwnedChanged()), Qt::QueuedConnection); + connect(wallet, SIGNAL(transactionConfirmed(int)), this, SIGNAL(balanceChanged()), Qt::QueuedConnection); connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); } -- 2.54.0 From 4bf81d0db741a8efd92ae8510f2903e84b637d35 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 25 Jan 2024 21:24:47 +0100 Subject: [PATCH 0781/1428] Fix bug: decrypting a wallet doesn't start sync --- src/Wallet_encryption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet_encryption.cpp b/src/Wallet_encryption.cpp index 21dfc76..a7592ae 100644 --- a/src/Wallet_encryption.cpp +++ b/src/Wallet_encryption.cpp @@ -296,7 +296,7 @@ bool Wallet::decrypt(const QString &password) // the enabled flag is used purely for disabling network sync while the wallet is fully encrypted if (m_segment) m_segment->setEnabled(true); } - rebuildBloom(); + rebuildBloom(ForceBuild); recalculateBalance(); emit encryptionChanged(); emit balanceChanged(); -- 2.54.0 From efc4e7c7d4035c886e4d05862315fb3fc6ae60bb Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 12:12:45 +0100 Subject: [PATCH 0782/1428] Make peers show up faster in netView A peer that has not yet handshaked is now show in this view, giving a better indication of what is going on on platforms that do not have easy access to a log file. --- guis/desktop/NetView.qml | 2 ++ modules/peers-view/NetView.qml | 2 ++ src/NetDataProvider.cpp | 6 ++++-- src/NetDataProvider.h | 2 +- src/WalletEnums.h | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index 8bbe769..a1ca8a5 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -95,6 +95,8 @@ ApplicationWindow { var id = model.segment; if (id === 0) { let validity = model.validity; + if (validity === Wallet.OpeningConnection) + return "Opening Connection"; if (validity === Wallet.Checking) return "Validating peer"; if (validity === Wallet.CheckedOk) diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index dc6a7d7..d3d761f 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -88,6 +88,8 @@ Mobile.Page { var id = model.segment; if (id === 0) { let validity = model.validity; + if (validity === Wallet.OpeningConnection) + return qsTr("Opening Connection"); if (validity === Wallet.Checking) return qsTr("Validating peer"); if (validity === Wallet.CheckedOk) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 1e6dc86..c1b5162 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -87,7 +87,7 @@ void NetDataProvider::startTimer() m_refreshTimer.start(); } -void NetDataProvider::newPeer(const std::shared_ptr &peer) +void NetDataProvider::newConnection(const std::shared_ptr &peer) { QMutexLocker l(&m_peerMutex); beginInsertRows(QModelIndex(), m_peers.size(), m_peers.size()); @@ -196,7 +196,9 @@ void NetDataProvider::updatePeers() bool changed = false; WalletEnums::PeerValidity valid = WalletEnums::UnknownValidity; - if (peer->requestedHeader()) + if (peer->status() == Peer::Connecting) + valid = WalletEnums::OpeningConnection; + else if (peer->requestedHeader()) valid = peer->receivedHeaders() ? WalletEnums::CheckedOk : WalletEnums::Checking; else valid = WalletEnums::KnownGood; diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index a74a652..0fa3a2f 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -73,7 +73,7 @@ public: void startTimer(); // P2PNetInterface interface - void newPeer(const std::shared_ptr &peer) override; + void newConnection(const std::shared_ptr &peer) override; enum { ConnectionId = Qt::UserRole, diff --git a/src/WalletEnums.h b/src/WalletEnums.h index f26a90b..449d9b9 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -73,6 +73,7 @@ public: enum PeerValidity { UnknownValidity, + OpeningConnection, KnownGood, Checking, CheckedOk -- 2.54.0 From e8adc7f94b728d223957802138afad215a721f70 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 13:34:19 +0100 Subject: [PATCH 0783/1428] Remove monthly pardon. This has been solved better in flowee libs in commit dc9ef827b. --- src/FloweePay.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b502401..094b127 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -363,8 +363,6 @@ void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ifica void FloweePay::init() { auto dl = p2pNet(); // this wil load the p2p layer. - dl->connectionManager().peerAddressDb().pardonOldCrimes(); - QFile in(m_basedir + AppdataFilename); Wallet *lastOpened = nullptr; QString currencyCode; // for wallet config -- 2.54.0 From 519b6d7532a326bd9070547ed3936e40541a104b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 13:37:08 +0100 Subject: [PATCH 0784/1428] Add button to pardon the banned. This allows a user to re-connect to formerly banned peers. --- guis/desktop/AddressDbStats.qml | 46 ++++++++++++++-------- modules/peers-view/StatsPage.qml | 19 +++++++++- src/NetDataProvider.cpp | 65 +++++++++++++++++++++++++++++++- src/NetDataProvider.h | 51 ++++++++++++++++++------- 4 files changed, 149 insertions(+), 32 deletions(-) diff --git a/guis/desktop/AddressDbStats.qml b/guis/desktop/AddressDbStats.qml index 4077b7d..ff132a9 100644 --- a/guis/desktop/AddressDbStats.qml +++ b/guis/desktop/AddressDbStats.qml @@ -16,12 +16,11 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls import QtQuick.Layouts import QtQuick.Controls as QQC2 import "../Flowee" as Flowee -ApplicationWindow { +QQC2.ApplicationWindow { id: root visible: false minimumWidth: 200 @@ -42,59 +41,76 @@ ApplicationWindow { x: 10 y: 10 - Label { + QQC2.Label { text: qsTr("Total found") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.count; Layout.fillWidth: true } - Label { + QQC2.Label { text: qsTr("Tried") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.everConnected; } - Label { + QQC2.Label { text: qsTr("Punished count") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.partialBanned; } - Label { + QQC2.Label { text: qsTr("Banned count") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.banned; } - Label { + QQC2.Label { text: qsTr("IP-v4 count") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.count - root.stats.ipv6Addresses font.strikeout: root.stats.usesIPv4 === false } - Label { + QQC2.Label { text: qsTr("IP-v6 count") + ":" Layout.alignment: Qt.AlignRight } - Label { + QQC2.Label { text: root.stats.ipv6Addresses font.strikeout: root.stats.usesIPv6 === false } + + Flowee.Button { + Layout.columnSpan: 2 + text: qsTr("Pardon the Banned") + onClicked: { + net.pardonBanned(); + var newStats = net.createStats(root); + root.stats.count = newStats.count; + root.stats.everConnected = newStats.everConnected; + root.stats.partialBanned = newStats.partialBanned; + root.stats.banned = newStats.banned; + root.stats.ipv6Addresses = newStats.ipv6Addresses; + root.stats.usesIPv6 = newStats.usesIPv6; + root.stats.usesIPv4 = newStats.usesIPv4; + enabled = false; + } + } } footer: Item { width: parent.width height: closeButton.height + 20 - Button { + Flowee.Button { id: closeButton anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/modules/peers-view/StatsPage.qml b/modules/peers-view/StatsPage.qml index a7c3489..da4db5b 100644 --- a/modules/peers-view/StatsPage.qml +++ b/modules/peers-view/StatsPage.qml @@ -16,6 +16,7 @@ * along with this program. If not, see . */ import QtQuick +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import "../mobile" as Mobile @@ -43,7 +44,7 @@ Mobile.Page { title: qsTr("Misbehaving IPs") width: parent.width Flowee.Label { - text: qsTr("Bad") + ": " + (root.stats.partialBanned - root.stats.banned); + text: qsTr("Bad") + ": " + root.stats.partialBanned; } Flowee.Label { text: qsTr("Banned") + ": " + root.stats.banned; @@ -62,5 +63,21 @@ Mobile.Page { font.strikeout: root.stats.usesIPv6 === false } } + + Flowee.Button { + text: qsTr("Pardon the Banned") + onClicked: { + net.pardonBanned(); + var newStats = net.createStats(root); + root.stats.count = newStats.count; + root.stats.everConnected = newStats.everConnected; + root.stats.partialBanned = newStats.partialBanned; + root.stats.banned = newStats.banned; + root.stats.ipv6Addresses = newStats.ipv6Addresses; + root.stats.usesIPv6 = newStats.usesIPv6; + root.stats.usesIPv4 = newStats.usesIPv4; + enabled = false; + } + } } } diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index c1b5162..4804182 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -41,24 +41,51 @@ int BasicAddressStats::count() const return m_count; } +void BasicAddressStats::setCount(int newCount) +{ + if (m_count == newCount) + return; + m_count = newCount; + emit countChanged(); +} + int BasicAddressStats::banned() const { return m_banned; } +void BasicAddressStats::setBanned(int newBanned) +{ + if (m_banned == newBanned) + return; + m_banned = newBanned; + emit bannedChanged(); +} + int BasicAddressStats::partialBanned() const { return m_partialBanned; } +void BasicAddressStats::setPartialBanned(int newPartialBanned) +{ + if (m_partialBanned == newPartialBanned) + return; + m_partialBanned = newPartialBanned; + emit partialBannedChanged(); +} + int BasicAddressStats::ipv6Addresses() const { return m_ipv6Addresses; } -int BasicAddressStats::everConnected() const +void BasicAddressStats::setIpv6Addresses(int newIpv6Addresses) { - return m_everConnected; + if (m_ipv6Addresses == newIpv6Addresses) + return; + m_ipv6Addresses = newIpv6Addresses; + emit ipv6AddressesChanged(); } bool BasicAddressStats::usesIPv4() const @@ -66,11 +93,40 @@ bool BasicAddressStats::usesIPv4() const return m_usesIPv4; } +void BasicAddressStats::setUsesIPv4(bool newUsesIPv4) +{ + if (m_usesIPv4 == newUsesIPv4) + return; + m_usesIPv4 = newUsesIPv4; + emit usesIPv4Changed(); +} + bool BasicAddressStats::usesIPv6() const { return m_usesIPv6; } +void BasicAddressStats::setUsesIPv6(bool newUsesIPv6) +{ + if (m_usesIPv6 == newUsesIPv6) + return; + m_usesIPv6 = newUsesIPv6; + emit usesIPv6Changed(); +} + +int BasicAddressStats::everConnected() const +{ + return m_everConnected; +} + +void BasicAddressStats::setEverConnected(int newEverConnected) +{ + if (m_everConnected == newEverConnected) + return; + m_everConnected = newEverConnected; + emit everConnectedChanged(); +} + // //////////////////////////////////////////////////////////////////////////////////// @@ -175,6 +231,11 @@ QObject *NetDataProvider::createStats(QObject *parent) const parent); } +void NetDataProvider::pardonBanned() +{ + FloweePay::instance()->p2pNet()->connectionManager().peerAddressDb().pardonOldCrimes(0); +} + void NetDataProvider::updatePeers() { /* diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index 0fa3a2f..9a5e163 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -35,32 +35,54 @@ class QTimer; class BasicAddressStats : public QObject { Q_OBJECT - Q_PROPERTY(int count READ count CONSTANT FINAL) - Q_PROPERTY(int banned READ banned CONSTANT FINAL) - Q_PROPERTY(int partialBanned READ partialBanned CONSTANT FINAL) - Q_PROPERTY(int ipv6Addresses READ ipv6Addresses CONSTANT FINAL) - Q_PROPERTY(int everConnected READ everConnected CONSTANT FINAL) - Q_PROPERTY(bool usesIPv4 READ usesIPv4 CONSTANT FINAL) - Q_PROPERTY(bool usesIPv6 READ usesIPv6 CONSTANT FINAL) + Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged FINAL) + Q_PROPERTY(int banned READ banned WRITE setBanned NOTIFY bannedChanged FINAL) + Q_PROPERTY(int partialBanned READ partialBanned WRITE setPartialBanned NOTIFY partialBannedChanged FINAL) + Q_PROPERTY(int ipv6Addresses READ ipv6Addresses WRITE setIpv6Addresses NOTIFY ipv6AddressesChanged FINAL) + Q_PROPERTY(int everConnected READ everConnected WRITE setEverConnected NOTIFY everConnectedChanged FINAL) + Q_PROPERTY(bool usesIPv4 READ usesIPv4 WRITE setUsesIPv4 NOTIFY usesIPv4Changed FINAL) + Q_PROPERTY(bool usesIPv6 READ usesIPv6 WRITE setUsesIPv6 NOTIFY usesIPv6Changed FINAL) public: explicit BasicAddressStats(const AddressDBStats &stats, QObject *parent = nullptr); int count() const; + void setCount(int newCount); + int banned() const; + void setBanned(int newBanned); + int partialBanned() const; + void setPartialBanned(int newPartialBanned); + int ipv6Addresses() const; + void setIpv6Addresses(int newIpv6Addresses); + int everConnected() const; + void setEverConnected(int newEverConnected); + bool usesIPv4() const; + void setUsesIPv4(bool newUsesIPv4); + bool usesIPv6() const; + void setUsesIPv6(bool newUsesIPv6); + +signals: + void countChanged(); + void bannedChanged(); + void partialBannedChanged(); + void ipv6AddressesChanged(); + void everConnectedChanged(); + void usesIPv4Changed(); + void usesIPv6Changed(); private: - const int m_count; - const int m_banned; - const int m_partialBanned; - const int m_ipv6Addresses; - const int m_everConnected; - const bool m_usesIPv4; - const bool m_usesIPv6; + int m_count; + int m_banned; + int m_partialBanned; + int m_ipv6Addresses; + int m_everConnected; + bool m_usesIPv4; + bool m_usesIPv6; }; class NetDataProvider : public QAbstractListModel, public P2PNetInterface @@ -92,6 +114,7 @@ public: QHash roleNames() const override; Q_INVOKABLE QObject *createStats(QObject *parent) const; + Q_INVOKABLE void pardonBanned(); private slots: void updatePeers(); -- 2.54.0 From 1455d90b304ff9506123d19ae31b8922216ff18b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 14:26:40 +0100 Subject: [PATCH 0785/1428] Add ban / disconnect UI elements on NetView --- guis/desktop/NetView.qml | 45 +++++++++++++++++++++++++--------- modules/peers-view/NetView.qml | 28 +++++++++++++++++++++ src/NetDataProvider.cpp | 24 ++++++++++++++++++ src/NetDataProvider.h | 10 ++++++++ 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index a1ca8a5..1218fe8 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -16,12 +16,12 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2; import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; -ApplicationWindow { +QQC2.ApplicationWindow { id: root visible: false minimumWidth: 200 @@ -37,7 +37,7 @@ ApplicationWindow { id: listView model: net clip: true - ScrollBar.vertical: ScrollBar { } + QQC2.ScrollBar.vertical: QQC2.ScrollBar { } anchors.fill: parent focus: true @@ -51,6 +51,8 @@ ApplicationWindow { delegate: Rectangle { width: listView.width height: peerPane.height + 12 + border.width: net.selectedId === model.connectionId ? 2 : 0 + border.color: palette.highlight color: index % 2 === 0 ? palette.button : palette.base opacity: { let validity = model.validity; @@ -58,7 +60,7 @@ ApplicationWindow { return 0.7; return 1; } - Label { + QQC2.Label { text: "(" + model.connectionId + ")" anchors.right: parent.right anchors.rightMargin: 10 @@ -71,24 +73,24 @@ ApplicationWindow { x: 10 y: 6 - Label { + QQC2.Label { text: model.userAgent } - Label { + QQC2.Label { text: qsTr("Address", "network address (IP)") + ": " + model.address } RowLayout { height: secondRow.height spacing: 0 - Label { + QQC2.Label { id: secondRow text: qsTr("Start-height: %1").arg(model.startHeight) } - Label { + QQC2.Label { text: ", " + qsTr("ban-score: %1").arg(model.banScore) } } - Label { + QQC2.Label { id : accountStatus font.bold: true text: { @@ -113,11 +115,32 @@ ApplicationWindow { return "Internal Error"; } } - Label { + QQC2.Label { text: "Downloading!" visible: model.isDownloading } } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (event)=> { + // for mouse + net.selectedId = model.connectionId; + if (event.button === Qt.RightButton) + peerContextMenu.popup(parent, event.x, event.y) + } + } + QQC2.Menu { + id: peerContextMenu + QQC2.MenuItem { + text: qsTr("Disconnect Peer") + onClicked: net.disconnectPeer(model.connectionId); + } + QQC2.MenuItem { + text: qsTr("Ban Peer") + onClicked: net.banPeer(model.connectionId); + } + } } Keys.forwardTo: Flowee.ListViewKeyHandler { target: listView @@ -128,7 +151,7 @@ ApplicationWindow { width: parent.width height: closeButton.height + 20 - Button { + QQC2.Button { id: closeButton anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index d3d761f..a360d69 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -52,6 +52,8 @@ Mobile.Page { width: listView.width height: peerPane.height + 12 color: index % 2 === 0 ? palette.button : palette.base + border.width: net.selectedId === model.connectionId ? 2 : 0 + border.color: palette.highlight opacity: { let validity = model.validity; if (validity === Wallet.Checking) @@ -111,6 +113,32 @@ Mobile.Page { visible: model.isDownloading } } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (event)=> { + // for mouse + net.selectedId = model.connectionId; + if (event.button === Qt.RightButton) + peerContextMenu.popup(parent, event.x, event.y) + } + onPressAndHold: (event) => { + // for touch + net.selectedId = model.connectionId; + peerContextMenu.popup(parent, event.x, event.y) + } + } + QQC2.Menu { + id: peerContextMenu + QQC2.MenuItem { + text: qsTr("Disconnect Peer") + onClicked: net.disconnectPeer(model.connectionId); + } + QQC2.MenuItem { + text: qsTr("Ban Peer") + onClicked: net.banPeer(model.connectionId); + } + } } Keys.forwardTo: Flowee.ListViewKeyHandler { target: listView diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 4804182..fa4c42b 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -236,6 +236,16 @@ void NetDataProvider::pardonBanned() FloweePay::instance()->p2pNet()->connectionManager().peerAddressDb().pardonOldCrimes(0); } +void NetDataProvider::disconnectPeer(int connectionId) +{ + FloweePay::instance()->p2pNet()->connectionManager().disconnect(connectionId); +} + +void NetDataProvider::banPeer(int connectionId) +{ + FloweePay::instance()->p2pNet()->connectionManager().punish(connectionId, 1000); +} + void NetDataProvider::updatePeers() { /* @@ -299,3 +309,17 @@ void NetDataProvider::updatePeers() } } } + +int NetDataProvider::selectedId() const +{ + return m_selectedId; +} + +void NetDataProvider::setSelectedId(int newSelectedId) +{ + QMutexLocker l(&m_peerMutex); + if (m_selectedId == newSelectedId) + return; + m_selectedId = newSelectedId; + emit selectedIdChanged(); +} diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index 9a5e163..4cb2942 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -88,6 +88,7 @@ private: class NetDataProvider : public QAbstractListModel, public P2PNetInterface { Q_OBJECT + Q_PROPERTY(int selectedId READ selectedId WRITE setSelectedId NOTIFY selectedIdChanged FINAL) public: explicit NetDataProvider(QObject *parent = nullptr); @@ -115,6 +116,14 @@ public: Q_INVOKABLE QObject *createStats(QObject *parent) const; Q_INVOKABLE void pardonBanned(); + Q_INVOKABLE void disconnectPeer(int connectionId); + Q_INVOKABLE void banPeer(int connectionId); + + int selectedId() const; + void setSelectedId(int newSelectedId); + +signals: + void selectedIdChanged(); private slots: void updatePeers(); @@ -129,6 +138,7 @@ private: }; std::vector m_peers; + int m_selectedId = -1; // we use some polling to keep the data up-to-date QTimer m_refreshTimer; -- 2.54.0 From 5554636c71cb21e9efd6702f03d5fa8f92e40d6a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 14:44:34 +0100 Subject: [PATCH 0786/1428] Show number in header --- modules/peers-view/NetView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index a360d69..a49024c 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -24,7 +24,7 @@ import Flowee.org.pay; Mobile.Page { id: root - headerText: qsTr("Peers") + headerText: qsTr("Peers") + " (" + listView.count +")"; property QtObject statsAction : QQC2.Action { text: qsTr("Statistics") -- 2.54.0 From 6905ac43b1590c47ab28d38d89433e5aef68a644 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 18:55:36 +0100 Subject: [PATCH 0787/1428] Copyright to 2024. --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index ede19e3..bc2dffe 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -51,7 +51,7 @@ Page { } TextButton { text: qsTr("Credits") - subtext: qsTr("© 2020-2023 Tom Zander and contributors") + subtext: qsTr("© 2020-2024 Tom Zander and contributors") showPageIcon: true onClicked: thePile.push(creditsPage) -- 2.54.0 From eba05ebb4543c9adbfe8fa7f473e51bbcc6a1e92 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 26 Jan 2024 20:50:06 +0100 Subject: [PATCH 0788/1428] Import bugfixes --- .../docker/scripts/android-composing.patch | 46 +++++ android/docker/scripts/android-deployqt.patch | 170 ++++++++++++++++++ android/docker/scripts/buildQt.sh | 5 +- 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 android/docker/scripts/android-composing.patch create mode 100644 android/docker/scripts/android-deployqt.patch diff --git a/android/docker/scripts/android-composing.patch b/android/docker/scripts/android-composing.patch new file mode 100644 index 0000000..4a6b7f1 --- /dev/null +++ b/android/docker/scripts/android-composing.patch @@ -0,0 +1,46 @@ +From f5be35240b561c84f8b2b5d85bfe922e1c7391eb Mon Sep 17 00:00:00 2001 +From: Andreas Buhr +Date: Thu, 14 Oct 2021 05:20:23 +0200 +Subject: [PATCH 1/2] Android: Fix handling of cursor position when stop + composing + +--- + .../android/qandroidinputcontext.cpp | 22 +++++++++++++------ + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp +index d2eb05c24d..6798ad585c 100644 +--- a/src/plugins/platforms/android/qandroidinputcontext.cpp ++++ b/src/plugins/platforms/android/qandroidinputcontext.cpp +@@ -1144,13 +1144,21 @@ bool QAndroidInputContext::focusObjectStopComposing() + + m_composingCursor = -1; + +- // commit composing text and cursor position +- QList attributes; +- attributes.append( +- QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); +- QInputMethodEvent event(QString(), attributes); +- event.setCommitString(m_composingText); +- sendInputMethodEvent(&event); ++ { ++ // commit the composing test ++ QList attributes; ++ QInputMethodEvent event(QString(), attributes); ++ event.setCommitString(m_composingText); ++ sendInputMethodEvent(&event); ++ } ++ { ++ // Moving Qt's cursor to where the preedit cursor used to be ++ QList attributes; ++ attributes.append( ++ QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); ++ QInputMethodEvent event(QString(), attributes); ++ sendInputMethodEvent(&event); ++ } + + return true; + } +-- +2.43.0 + diff --git a/android/docker/scripts/android-deployqt.patch b/android/docker/scripts/android-deployqt.patch new file mode 100644 index 0000000..48db8de --- /dev/null +++ b/android/docker/scripts/android-deployqt.patch @@ -0,0 +1,170 @@ +From 1b92ca6a08e69cd9a9870487ff548edc0193e789 Mon Sep 17 00:00:00 2001 +From: Alexey Edelev +Date: Wed, 3 Jan 2024 13:16:41 +0100 +Subject: [PATCH 2/2] Add the support of the qt_import_plugins functionality to + androiddeployqt + +qt_import_plugins allows to control application deployment on +non-Android platforms. This adds support for the pre-defined plugin list +that is computed using the qt_import_plugins input. + +Task-number: QTBUG-118829 +Change-Id: Iaa9c3f600533a4b5a3079ab228fabf212d9ce5a5 +Reviewed-by: Assam Boudjelthia +--- + cmake/QtAndroidHelpers.cmake | 3 +- + src/corelib/Qt6AndroidMacros.cmake | 6 ++- + src/tools/androiddeployqt/main.cpp | 70 ++++++++++++++++++++---------- + 3 files changed, 55 insertions(+), 24 deletions(-) + +diff --git a/cmake/QtAndroidHelpers.cmake b/cmake/QtAndroidHelpers.cmake +index 857a029991..ba72e21e84 100644 +--- a/cmake/QtAndroidHelpers.cmake ++++ b/cmake/QtAndroidHelpers.cmake +@@ -252,7 +252,8 @@ function(qt_internal_android_dependencies target) + # Module plugins + if(module_plugin_types) + foreach(plugin IN LISTS module_plugin_types) +- string(APPEND file_contents "\n") ++ string(APPEND file_contents ++ "\n") + endforeach() + endif() + +diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake +index 9a44308a0f..0a270b96bb 100644 +--- a/src/corelib/Qt6AndroidMacros.cmake ++++ b/src/corelib/Qt6AndroidMacros.cmake +@@ -236,6 +236,10 @@ function(qt6_android_generate_deployment_settings target) + _qt_internal_add_android_deployment_property(file_contents "android-no-deploy-qt-libs" + ${target} "QT_ANDROID_NO_DEPLOY_QT_LIBS") + ++ __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets) ++ __qt_internal_collect_plugin_library_files("${target}" "${plugin_targets}" plugin_targets) ++ string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n") ++ + # App binary + string(APPEND file_contents + " \"application-binary\": \"${target_output_name}\",\n") +@@ -303,7 +307,7 @@ function(qt6_android_generate_deployment_settings target) + # content end + string(APPEND file_contents "}\n") + +- file(GENERATE OUTPUT ${deploy_file} CONTENT ${file_contents}) ++ file(GENERATE OUTPUT ${deploy_file} CONTENT "${file_contents}") + + set_target_properties(${target} + PROPERTIES +diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp +index ec33fd354b..f3581de0e6 100644 +--- a/src/tools/androiddeployqt/main.cpp ++++ b/src/tools/androiddeployqt/main.cpp +@@ -144,6 +144,7 @@ struct Options + QString qtQmlDirectory; + QString qtHostDirectory; + std::vector extraPrefixDirs; ++ QStringList androidDeployPlugins; + // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder + // when looking for dependencies. + std::vector extraLibraryDirs; +@@ -1011,6 +1012,11 @@ bool readInputFile(Options *options) + } + } + ++ { ++ const auto androidDeployPlugins = jsonObject.value("android-deploy-plugins"_L1).toString(); ++ options->androidDeployPlugins = androidDeployPlugins.split(";"_L1, Qt::SkipEmptyParts); ++ } ++ + { + const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray(); + options->extraLibraryDirs.reserve(extraLibraryDirs.size()); +@@ -1883,6 +1889,32 @@ QList findFilesRecursively(const Options &options, const QString & + return deps; + } + ++void readDependenciesFromFiles(Options *options, const QList &files, ++ QSet &usedDependencies, ++ QSet &remainingDependencies) ++{ ++ for (const QtDependency &fileName : files) { ++ if (usedDependencies.contains(fileName.absolutePath)) ++ continue; ++ ++ if (fileName.absolutePath.endsWith(".so"_L1)) { ++ if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies, ++ &remainingDependencies)) { ++ fprintf(stdout, "Skipping file dependency: %s\n", ++ qPrintable(fileName.relativePath)); ++ continue; ++ } ++ } ++ usedDependencies.insert(fileName.absolutePath); ++ ++ if (options->verbose) { ++ fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath)); ++ } ++ ++ options->qtDependencies[options->currentArchitecture].append(fileName); ++ } ++} ++ + bool readAndroidDependencyXml(Options *options, + const QString &moduleName, + QSet *usedDependencies, +@@ -1913,29 +1945,15 @@ bool readAndroidDependencyXml(Options *options, + + QString file = reader.attributes().value("file"_L1).toString(); + +- const QList fileNames = findFilesRecursively(*options, file); +- +- for (const QtDependency &fileName : fileNames) { +- if (usedDependencies->contains(fileName.absolutePath)) +- continue; +- +- if (fileName.absolutePath.endsWith(".so"_L1)) { +- QSet remainingDependencies; +- if (!readDependenciesFromElf(options, fileName.absolutePath, +- usedDependencies, +- &remainingDependencies)) { +- fprintf(stdout, "Skipping dependencies from xml: %s\n", +- qPrintable(fileName.relativePath)); +- continue; +- } +- } +- usedDependencies->insert(fileName.absolutePath); +- +- if (options->verbose) +- fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath)); +- +- options->qtDependencies[options->currentArchitecture].append(fileName); ++ if (reader.attributes().hasAttribute("type"_L1) ++ && reader.attributes().value("type"_L1) == "plugin_dir"_L1 ++ && !options->androidDeployPlugins.isEmpty()) { ++ continue; + } ++ ++ const QList fileNames = findFilesRecursively(*options, file); ++ readDependenciesFromFiles(options, fileNames, *usedDependencies, ++ *remainingDependencies); + } else if (reader.name() == "jar"_L1) { + int bundling = reader.attributes().value("bundling"_L1).toInt(); + QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString()); +@@ -2412,6 +2430,14 @@ bool readDependencies(Options *options) + if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies)) + return false; + ++ QList pluginDeps; ++ for (const auto &pluginPath : options->androidDeployPlugins) { ++ pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath), ++ options->qtInstallDirectory + "/"_L1)); ++ } ++ ++ readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies); ++ + while (!remainingDependencies.isEmpty()) { + QSet::iterator start = remainingDependencies.begin(); + QString fileName = absoluteFilePath(options, *start); +-- +2.43.0 + diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 4601458..f21aa74 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -25,10 +25,13 @@ function checkout ( ) # The QtBase builds are different. -checkout qtbase +TAG=6.5 checkout qtbase cd ~builduser curl 'https://code.qt.io/cgit/qt/qtbase.git/patch/?id=8af35d27' > libxkbcommon-1.6.patch patch -d qtbase -p1 < libxkbcommon-1.6.patch # Fix build with libxkbcommon 1.6 +patch -d qtbase -p1 < /usr/local/bin/android-composing.patch +patch -d qtbase -p1 < /usr/local/bin/android-deployqt.patch + mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ -- 2.54.0 From 9758ead829763348386d97c75571fd877c727f2f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 27 Jan 2024 23:09:58 +0100 Subject: [PATCH 0789/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 502f496..510515f 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="23" android:versionName="2024.01.3"> diff --git a/src/main.cpp b/src/main.cpp index 35746a8..3cdd748 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,7 +90,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.01.2"); + qapp.setApplicationVersion("2024.01.3"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 4e1c6ced07559b33aa4d1c454cf23eedbcd18e5f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 27 Jan 2024 23:10:16 +0100 Subject: [PATCH 0790/1428] Skip including not used plugins --- CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2cb368..5804cb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,6 +264,14 @@ if (ANDROID AND build_mobile_pay) endif() qt6_add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) + qt_import_plugins(pay_mobile + EXCLUDE_BY_TYPE + imageformats + qmltooling + networkinformation + INCLUDE + Qt::QSvgPlugin + ) target_link_libraries(pay_mobile PRIVATE pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES -- 2.54.0 From 5ef8860492ea655fc6d2e8b3d7912f0268712af9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 12:16:12 +0100 Subject: [PATCH 0791/1428] Import translations --- translations/module-peers-view_de.ts | 96 ++++++++++++++++++++++++---- translations/module-peers-view_es.ts | 96 ++++++++++++++++++++++++---- translations/module-peers-view_nl.ts | 96 ++++++++++++++++++++++++---- translations/module-peers-view_pt.ts | 4 +- 4 files changed, 248 insertions(+), 44 deletions(-) diff --git a/translations/module-peers-view_de.ts b/translations/module-peers-view_de.ts index 50d68c3..75b0680 100644 --- a/translations/module-peers-view_de.ts +++ b/translations/module-peers-view_de.ts @@ -4,45 +4,65 @@ NetView - + Peers Peers - + + Statistics + Statistiken + + + Address network address (IP) Adresse - + Start-height: %1 Starthöhe: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - Initialisiere Verbindung + + Opening Connection + Öffne Verbindung - - Verifying peer - Überprüfe Peer + + Validating peer + Validiere Knoten - + + Validated + Validiert + + + + Good Peer + Guter Knoten + + + Peer for wallet: %1 Peer für Geldbörse: %1 - - Peer for wallet - Peer für Geldbörse + + Disconnect Peer + Trenne Knoten + + + + Ban Peer + Sperre Knoten @@ -63,4 +83,52 @@ Netzwerkdetails + + StatsPage + + + IP-Address Statistics + IP-Adressstatistiken + + + + Counts + Anzahl + + + + Total found + Gesamt gefunden + + + + Tried + Versuchte + + + + Misbehaving IPs + IPs mit Fehlverhalten + + + + Bad + Schlecht + + + + Banned + Gesperrt + + + + Network + Netzwerk + + + + Pardon the Banned + Gesperrte Knoten entsperren + + diff --git a/translations/module-peers-view_es.ts b/translations/module-peers-view_es.ts index bd4952a..4ec0277 100644 --- a/translations/module-peers-view_es.ts +++ b/translations/module-peers-view_es.ts @@ -4,45 +4,65 @@ NetView - + Peers Pares - + + Statistics + Estadísticas + + + Address network address (IP) Dirección - + Start-height: %1 Altura de inicio: %1 - + ban-score: %1 calificación de no confiabilidad: %1 - - initializing connection - Inicializando conexión + + Opening Connection + Abriendo conexión - - Verifying peer - Verificando par + + Validating peer + Validando par - + + Validated + Validado + + + + Good Peer + Par bueno + + + Peer for wallet: %1 Par para el monedero: %1 - - Peer for wallet - Par para el monedero + + Disconnect Peer + Desconectar par + + + + Ban Peer + Banear par @@ -63,4 +83,52 @@ Detalles de la red + + StatsPage + + + IP-Address Statistics + Estadísticas de dirección IP + + + + Counts + Cuentas + + + + Total found + Total encontrado + + + + Tried + Intentado + + + + Misbehaving IPs + IPs Mal comportados + + + + Bad + Malo + + + + Banned + Baneado + + + + Network + Red + + + + Pardon the Banned + Perdonar los Baneados + + diff --git a/translations/module-peers-view_nl.ts b/translations/module-peers-view_nl.ts index ae756f0..bab104d 100644 --- a/translations/module-peers-view_nl.ts +++ b/translations/module-peers-view_nl.ts @@ -4,45 +4,65 @@ NetView - + Peers Peers - + + Statistics + Statistieken + + + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - verbinding initialiseren + + Opening Connection + Openen van verbinding - - Verifying peer - Peer verifiëren + + Validating peer + Controleren peer - + + Validated + Gecontroleerd + + + + Good Peer + Goede Peer + + + Peer for wallet: %1 Peer voor portemonnee: %1 - - Peer for wallet - Peer voor portemonnee + + Disconnect Peer + Verbreek verbinding met peer + + + + Ban Peer + Verban Peer @@ -63,4 +83,52 @@ Netwerk Details + + StatsPage + + + IP-Address Statistics + IP-Adres statistieken + + + + Counts + Aantal + + + + Total found + Totaal gevonden + + + + Tried + Geprobeerd + + + + Misbehaving IPs + IP's misdragen + + + + Bad + Slecht + + + + Banned + Verbannen + + + + Network + Netwerk + + + + Pardon the Banned + Pardon de verbannen + + diff --git a/translations/module-peers-view_pt.ts b/translations/module-peers-view_pt.ts index e6a44fc..0a8761a 100644 --- a/translations/module-peers-view_pt.ts +++ b/translations/module-peers-view_pt.ts @@ -12,12 +12,12 @@ Address network address (IP) - Address + Endereço
Start-height: %1 - Start-height: %1 + %1 Bloco inicial -- 2.54.0 From 0fadec043b6eb9f3bb9db5df64aaec81647e79ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 13:04:23 +0100 Subject: [PATCH 0792/1428] Make this method also work for transactions I receive This now finds if there is a single output address we can honstly point at and say "this is the beneficiary of this transaction" and we return this. This will be in the context of the wallet it is in, naturally. --- src/TransactionInfo.cpp | 11 ++++------- src/TransactionInfo.h | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index b062929..6c97533 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -103,21 +103,18 @@ bool TransactionInfo::createdByUs() const QString TransactionInfo::receiver() const { - if (!m_createdByUs) // the receiver, is us! - return QString(); + QString answer; /* - * We iterate over the outputs and reject all outputs that we own. + * We iterate over the outputs and reject all outputs that make no sense. * If exactly one is left we have a 'receiver'. */ - QString answer; for (auto out : m_outputs) { - if (!out->forMe()) { + if (out && out->forMe() != m_createdByUs) { if (answer.isEmpty()) answer = out->address(); else return QString(); // more than one address } - } return answer; } diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 7fed7f6..6654092 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -121,6 +121,7 @@ public: bool isFused() const; bool createdByUs() const; + /// The 'receiver' address of this transaction. QString receiver() const; /** -- 2.54.0 From 483b8455b263a5aa9e91c7b91564186bf55ce69d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 14:42:53 +0100 Subject: [PATCH 0793/1428] Improve TransactionDetails screen * make it much faster for large transactions to open by not drawing and then hiding the inputs/outputs that are not relevant to us. * Allow expanding cloaked addresses by clicking on them. * instead of a right mouse button menu, show a copy icon next to each address. * Add the chainprefix to the clipboard on copy. --- guis/Flowee/AddressLabel.qml | 90 ++++++++++++++++++++++++++++++ guis/Flowee/LabelWithClipboard.qml | 4 +- guis/mobile/TransactionDetails.qml | 70 +++++------------------ guis/widgets.qrc | 1 + src/TransactionInfo.cpp | 18 ++++++ src/TransactionInfo.h | 4 ++ 6 files changed, 129 insertions(+), 58 deletions(-) create mode 100644 guis/Flowee/AddressLabel.qml diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml new file mode 100644 index 0000000..359be4b --- /dev/null +++ b/guis/Flowee/AddressLabel.qml @@ -0,0 +1,90 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 + +QQC2.Control { + id: root + required property QtObject txInfo; + property alias highlight: highlight.visible + + visible: txInfo !== null + width: implicitWidth + height: implicitHeight + implicitHeight: Math.max(theLabel.implicitHeight, copyIcon.height) + implicitWidth: theLabel.implicitWidth + 10 + copyIcon.width + + Label { + background: Rectangle { + color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" + id: highlight + radius: height / 3 + opacity: 0.2 + } + width: parent.width - copyIcon.width - 10 + height: parent.height + + id: theLabel + elide: wrapMode === Text.NoWrap ? Text.ElideMiddle : Text.ElideNone + property bool allowCloak: true + + text: { + if (allowCloak) { + var cloaked = root.txInfo.cloakedAddress + if (cloaked !== "") + return cloaked; + } + return root.txInfo.address; + } + font.pixelSize: root.font.pixelSize * 0.9 + + MouseArea { + anchors.fill: parent + anchors.margins: -6 // a bit bigger + onClicked: parent.allowCloak = !parent.allowCloak + } + } + Rectangle { + id: copiedFeedback + anchors.fill: theLabel + color: palette.mid + opacity: 0 + visible: opacity > 0 + Timer { + running: parent.visible + onTriggered: parent.opacity = 0; + interval: 500 + } + Behavior on opacity { NumberAnimation { duration: 250 } } + } + Image { + id: copyIcon + anchors.right: parent.right + width: 20 + height: 20 + source: "qrc:/edit-copy" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + copiedFeedback.opacity = 0.6 + Pay.copyToClipboard(Pay.chainPrefix + root.txInfo.address) + } + } + } +} diff --git a/guis/Flowee/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml index a622503..e69062f 100644 --- a/guis/Flowee/LabelWithClipboard.qml +++ b/guis/Flowee/LabelWithClipboard.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 Label { id: root diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 235efa7..7efe987 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -189,7 +189,7 @@ Page { * no details about which address or anything else these funds come from if we didn't * create the Tx. So just don't show anything. */ - model: parent.visible ? infoObject.inputs : 0 + model: parent.visible ? infoObject.knownInputs : 0 delegate: Item { Layout.alignment: Qt.AlignRight width: content.width @@ -200,32 +200,12 @@ Page { return inAddress.height + 10; return inAddress.height + amount.height + 16; } - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: inAddress.visible - x: inAddress.x - 3 - y: inAddress.y -3 - height: inAddress.height + 6 - width: Math.min(inAddress.width, inAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { + Flowee.AddressLabel { id: inAddress - menuText: qsTr("Copy Address") - text: { - if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; - } - width: parent.width - clipboardText: modelData === null ? "" : modelData.address - visible: modelData !== null - font.pixelSize: root.font.pixelSize * 0.9 + txInfo: modelData + width: Math.min(implicitWidth, parent.width) } + Flowee.BitcoinAmountLabel { id: amount visible: modelData !== null @@ -250,51 +230,29 @@ Page { return qsTr("Received at addresses"); return qsTr("Received at my addresses"); } + Repeater { - model: root.infoObject == null ? 0 : infoObject.outputs + model: root.infoObject == null ? 0 : infoObject.knownOutputs delegate: Item { Layout.alignment: Qt.AlignRight width: content.width height: { - if (modelData === null) - return 0; if (outAddress.implicitWidth + 10 + outAmount.implicitWidth < width) return outAmount.height + 10; return outAddress.height + outAmount.height + 16; } - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: modelData !== null && modelData.forMe - x: outAddress.x - 3 - y: outAddress.y -3 - height: outAddress.height + 6 - width: Math.min(outAddress.width, outAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { + Flowee.AddressLabel { id: outAddress - visible: modelData !== null - elide: Text.ElideMiddle - menuText: qsTr("Copy Address") - text: { - if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; - } - clipboardText: modelData === null ? "" : modelData.address - width: parent.width - font.pixelSize: root.font.pixelSize * 0.9 + txInfo: modelData + highlight: modelData.forMe + width: Math.min(implicitWidth, parent.width) } + Flowee.BitcoinAmountLabel { id: outAmount - visible: modelData !== null - value: modelData === null ? 0 : modelData.value + value: modelData.value fiatTimestamp: root.transaction.date - colorize: modelData !== null && modelData.forMe + colorize: modelData.forMe anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 10 diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 38ad9bf..63d1b43 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -21,6 +21,7 @@ Flowee/CloseIcon.qml Flowee/GroupBox.qml Flowee/LabelWithClipboard.qml + Flowee/AddressLabel.qml Flowee/ListViewKeyHandler.qml Flowee/MultilineTextField.qml Flowee/RadioButton.qml diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 6c97533..6010b95 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -71,6 +71,24 @@ QList TransactionInfo::inputs() const return answer; } +QList TransactionInfo::knownOutputs() const +{ + QList answer; + for (auto o : m_outputs) { + if (o) answer.append(o); + } + return answer; +} + +QList TransactionInfo::knownInputs() const +{ + QList answer; + for (auto i : m_inputs) { + if (i) answer.append(i); + } + return answer; +} + const QString &TransactionInfo::userComment() const { return m_userComment; diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 6654092..0739359 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -98,6 +98,8 @@ class TransactionInfo : public QObject Q_PROPERTY(double fees READ fees CONSTANT) Q_PROPERTY(QList inputs READ inputs CONSTANT) Q_PROPERTY(QList outputs READ outputs CONSTANT) + Q_PROPERTY(QList knownInputs READ knownInputs CONSTANT) + Q_PROPERTY(QList knownOutputs READ knownOutputs CONSTANT) Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY commentChanged) /** * The recipient of the transaction. Typically just an address. @@ -115,6 +117,8 @@ public: double fees() const; QList outputs() const; QList inputs() const; + QList knownOutputs() const; + QList knownInputs() const; const QString &userComment() const; void setUserComment(const QString &comment); bool isCoinbase() const; -- 2.54.0 From 34b676d0437e122f26b98f644958af8d4ac8ac55 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 15:28:51 +0100 Subject: [PATCH 0794/1428] Make the un-finished connections show lighter too --- modules/peers-view/NetView.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index a49024c..882291e 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -56,9 +56,9 @@ Mobile.Page { border.color: palette.highlight opacity: { let validity = model.validity; - if (validity === Wallet.Checking) - return 0.7; - return 1; + if (validity === Wallet.CheckedOk || validity === Wallet.KnownGood) + return 1; + return 0.7; } ColumnLayout { id: peerPane -- 2.54.0 From 2eff679495b5979c1c67c7074879ed3bbeea9885 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 19:58:27 +0100 Subject: [PATCH 0795/1428] Improve list UX We add animations on remove and give an indication of connectionid --- modules/peers-view/NetView.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index 882291e..6a296a8 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -47,6 +47,10 @@ Mobile.Page { event.accepted = true } } + // make removals animated to avoid the list looking 'shokking' + removeDisplaced: Transition { + NumberAnimation { properties: "y"; duration: 300; easing.type: Easing.InQuad } + } delegate: Rectangle { width: listView.width @@ -67,7 +71,12 @@ Mobile.Page { y: 6 Flowee.Label { - text: model.userAgent + text: { + var t = model.userAgent; + if (t === "") + t = model.connectionId + return t; + } } Flowee.Label { text: qsTr("Address", "network address (IP)") + ": " + model.address -- 2.54.0 From 2d55f1b508b30caf29c63dadf787b3652c7e0c4e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 20:01:56 +0100 Subject: [PATCH 0796/1428] Avoid exception. Simplify the loop by using lock() on the std::shared_ptr, which doesn't throw. Also follow the API change in the libs and some deeper introspection for the actual connection status. --- src/NetDataProvider.cpp | 87 +++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index fa4c42b..9ec043e 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -233,7 +233,7 @@ QObject *NetDataProvider::createStats(QObject *parent) const void NetDataProvider::pardonBanned() { - FloweePay::instance()->p2pNet()->connectionManager().peerAddressDb().pardonOldCrimes(0); + FloweePay::instance()->p2pNet()->connectionManager().peerAddressDb().resetAllStats(); } void NetDataProvider::disconnectPeer(int connectionId) @@ -261,52 +261,55 @@ void NetDataProvider::updatePeers() int index = 0; auto iter = m_peers.begin(); while (iter != m_peers.end()) { - try { - std::shared_ptr peer(iter->peer); - - bool changed = false; - - WalletEnums::PeerValidity valid = WalletEnums::UnknownValidity; - if (peer->status() == Peer::Connecting) - valid = WalletEnums::OpeningConnection; - else if (peer->requestedHeader()) - valid = peer->receivedHeaders() ? WalletEnums::CheckedOk : WalletEnums::Checking; - else - valid = WalletEnums::KnownGood; - if (valid != iter->valid) { - iter->valid = valid; - changed = true; - } - - const bool isDownloading = peer->merkleDownloadInProgress(); - if (iter->isDownloading != isDownloading) { - iter->isDownloading = isDownloading; - changed = true; - } - - if (iter->segment == 0) { - auto segment = peer->privacySegment(); - if (segment) { - iter->segment = segment->segmentId(); - changed = true; - } - } - - if (changed) { - // to change a row, we delete and re-insert it. - beginRemoveRows(QModelIndex(), index, index); - endRemoveRows(); - beginInsertRows(QModelIndex(), index, index); - endInsertRows(); - } - ++index; - ++iter; - } catch (const std::exception &e) { + auto peer = iter->peer.lock(); + if (peer.get() == nullptr) { // the peer has been deleted. beginRemoveRows(QModelIndex(), index, index); iter = m_peers.erase(iter); endRemoveRows(); + continue; } + bool changed = false; + + WalletEnums::PeerValidity valid = WalletEnums::OpeningConnection; + bool isDownloading = false; + if (peer->status() == Peer::Connected) { + const auto address = peer->peerAddress(); + if (address.hasEverConnected()) { + // p2p level handshake some time in the past successeded + valid = WalletEnums::KnownGood; + } + if (peer->requestedHeader()) + valid = peer->receivedHeaders() ? WalletEnums::CheckedOk : WalletEnums::Checking; + isDownloading = peer->merkleDownloadInProgress(); + } + if (valid != iter->valid) { + iter->valid = valid; + changed = true; + } + + if (iter->isDownloading != isDownloading) { + iter->isDownloading = isDownloading; + changed = true; + } + + if (peer->status() == Peer::Connected && iter->segment == 0) { + auto segment = peer->privacySegment(); + if (segment) { + iter->segment = segment->segmentId(); + changed = true; + } + } + + if (changed) { + // to change a row, we delete and re-insert it. + beginRemoveRows(QModelIndex(), index, index); + endRemoveRows(); + beginInsertRows(QModelIndex(), index, index); + endInsertRows(); + } + ++index; + ++iter; } } -- 2.54.0 From 437ee5634d00a276f8eeb6f383eb768f7caa40fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 20:02:36 +0100 Subject: [PATCH 0797/1428] Don't assert that isn't really a problem. Hit this assert when I had the app open in the debugger for some minutes. --- src/PriceDataProvider.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 34b3362..fce9c9b 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -211,7 +211,8 @@ QString PriceDataProvider::priceToStringSimple(int64_t cents) const void PriceDataProvider::fetch() { - assert(m_reply == nullptr); + if (m_reply) + return; if (m_yadioViaUSD && m_currencyConversions.isEmpty()) { m_reply = m_network.get(QNetworkRequest(QUrl(YadioURL))); logInfo() << "fetching yadio.io USD-conversions"; -- 2.54.0 From 4f2350190831d84d9e729dd85c81eff95aec9917 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 20:11:46 +0100 Subject: [PATCH 0798/1428] Fix some possible memory corruption issues. Use deleteLater for the QObject Use sender() in the slot instead of assuming that the class member still points to it. --- src/PaymentRequest.cpp | 57 ++++++++++++++++++++++++++---------------- src/PaymentRequest.h | 4 ++- src/WalletKeyView.cpp | 15 ++++++++--- src/WalletKeyView.h | 13 +++++++--- 4 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index d134f35..f2d2ff5 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -30,26 +30,34 @@ PaymentRequest::PaymentRequest(QObject *parent) : QObject(parent) { - connect (this, &PaymentRequest::paymentStateChanged, this, [=]() { - if (m_paymentState == PaymentSeen) { - // unconfirmed fully paid, but lets see in a couple of seconds... - QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() { - // A DSP can change the status, if its still unchanged then we - // can safely move to the 'Ok' state. - if (m_paymentState == PaymentSeen) - setPaymentState(PaymentSeenOk); - }); - } - }, Qt::QueuedConnection); +} + +PaymentRequest::~PaymentRequest() +{ + delete m_view; + m_view = nullptr; } void PaymentRequest::setPaymentState(PaymentState newState) { if (newState == m_paymentState) return; - m_paymentState = newState; emit paymentStateChanged(); + + if (m_paymentState == PaymentSeen) { +#if 1 + setPaymentState(PaymentSeenOk); +#else + // unconfirmed fully paid, but lets see in a couple of seconds... + QTimer::singleShot(FloweePay::instance()->dspTimeout(), this, [=]() { + // A DSP can change the status, if its still unchanged then we + // can safely move to the 'Ok' state. + if (m_paymentState == PaymentSeen) + setPaymentState(PaymentSeenOk); + }); +#endif + } } void PaymentRequest::updateFailReason() @@ -110,21 +118,26 @@ void PaymentRequest::setAccount(QObject *account) { if (account == m_account) return; - if (m_view && !m_view->transactions().isEmpty()) - throw std::runtime_error("Not allowed to change wallet on partially fulfilled request"); + if (m_view) { + if (!m_view->transactions().isEmpty()) { + // Not allowed to change wallet on partially fulfilled request !! + assert(false); // make debug build fail. + return; // release just ignores. + } + m_view->deleteLater(); + m_view = nullptr; + } if (m_account && m_privKeyId != -1 && m_paymentState == Unpaid) m_account->wallet()->unreserveAddress(m_privKeyId); - delete m_view; - m_view = nullptr; m_account = qobject_cast(account); m_privKeyId = -1; m_address = KeyId(); m_amountSeen = 0; if (m_account) { assert(QThread::currentThread() == thread()); - m_view = new WalletKeyView(m_account->wallet(), this); + m_view = new WalletKeyView(m_account->wallet()); connect (m_view, &WalletKeyView::walletEncrypted, this, [=]() { assert(QThread::currentThread() == thread()); updateFailReason(); @@ -137,7 +150,9 @@ void PaymentRequest::setAccount(QObject *account) connect (m_view, &WalletKeyView::transactionMatch, this, [=]() { assert(QThread::currentThread() == thread()); int64_t seen = 0; - for (const auto &tx : m_view->transactions()) { + auto view = qobject_cast(sender()); + assert(view); + for (const auto &tx : view->transactions()) { if (tx.state != WalletKeyView::UTXORejected) { seen += tx.amount; } @@ -145,7 +160,7 @@ void PaymentRequest::setAccount(QObject *account) // for paying transactions. if (!m_message.isEmpty()) { Wallet::OutputRef ref(tx.ref); - m_account->wallet()->setTransactionComment(ref.txIndex(), m_message); + view->wallet()->setTransactionComment(ref.txIndex(), m_message); } } @@ -279,9 +294,9 @@ void PaymentRequest::clear() emit amountChanged(); m_amountSeen = 0; setPaymentState(Unpaid); - delete m_view; + if (m_view) + m_view->deleteLater(); m_view = nullptr; setAccount(nullptr); - assert(m_view == nullptr); // ensure we didn't get a new one m_privKeyId = -1; } diff --git a/src/PaymentRequest.h b/src/PaymentRequest.h index 364c45a..6fc72e1 100644 --- a/src/PaymentRequest.h +++ b/src/PaymentRequest.h @@ -21,8 +21,9 @@ #include #include +#include "WalletKeyView.h" + class AccountInfo; -class WalletKeyView; /** * This is a user-created request for payment to a specific wallet/address. @@ -63,6 +64,7 @@ public: Q_ENUM(FailReason) PaymentRequest(QObject *parent = nullptr); + ~PaymentRequest(); QString message() const; void setMessage(const QString &message); diff --git a/src/WalletKeyView.cpp b/src/WalletKeyView.cpp index 9753b6e..77a6ce2 100644 --- a/src/WalletKeyView.cpp +++ b/src/WalletKeyView.cpp @@ -21,9 +21,8 @@ #include -WalletKeyView::WalletKeyView(Wallet *wallet, QObject *parent) - : QObject(parent), - m_wallet(wallet) +WalletKeyView::WalletKeyView(Wallet *wallet) + : m_wallet(wallet) { assert(wallet); m_walletIsImporting = wallet->walletIsImporting(); @@ -46,6 +45,11 @@ void WalletKeyView::setPrivKeyIndex(int privKeyIndex) m_privKeyIndex = privKeyIndex; } +bool WalletKeyView::walletIsImporting() const +{ + return m_walletIsImporting; +} + void WalletKeyView::appendedTransactions(const int firstNew, const int count) { if (m_walletIsImporting) @@ -93,6 +97,11 @@ void WalletKeyView::lastBlockSynchedChanged() } } +Wallet *WalletKeyView::wallet() const +{ + return m_wallet; +} + QList WalletKeyView::transactions() const { return m_transactions; diff --git a/src/WalletKeyView.h b/src/WalletKeyView.h index eba9554..9950b0f 100644 --- a/src/WalletKeyView.h +++ b/src/WalletKeyView.h @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#ifndef WALLET_KEY_VIEW_H +#define WALLET_KEY_VIEW_H #include class Wallet; @@ -26,7 +28,7 @@ class Wallet; * private key, which has to be owned by the wallet. * * The Wallet::reserveUnusedAddress() method returns a - * privagte key-index, you can set that with setPrivKeyIndex() + * private key-index, you can set that with setPrivKeyIndex() * to activate this class. * * While a wallet is importing (see walletIsImporting()) all @@ -38,11 +40,12 @@ class WalletKeyView : public QObject { Q_OBJECT public: - WalletKeyView(Wallet *wallet, QObject *parent); + explicit WalletKeyView(Wallet *wallet); // set which private key of the viewed wallet we are filtering on. void setPrivKeyIndex(int privKeyId); + /// !see importFinished() bool walletIsImporting() const; enum UTXOState { @@ -59,6 +62,8 @@ public: QList transactions() const; + Wallet *wallet() const; + signals: void transactionMatch(uint64_t ref, uint64_t amount, UTXOState state); void walletEncrypted(); @@ -71,7 +76,7 @@ private slots: void lastBlockSynchedChanged(); private: - const Wallet * const m_wallet; + Wallet * const m_wallet; // set at startup to true if the wallet is in the (once in its lifetime) importing // stage. If true, all deposits are historical! bool m_walletIsImporting; @@ -79,3 +84,5 @@ private: QList m_transactions; }; + +#endif -- 2.54.0 From b842c401d9c699455acedf121b605803862cff39 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 28 Jan 2024 20:12:29 +0100 Subject: [PATCH 0799/1428] Increase Android version --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 510515f..a3baa20 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="24" android:versionName="2024.01.3"> -- 2.54.0 From 87d5789bc75ff5e1c9b03ad8c042c53b2adad194 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 Jan 2024 17:21:57 +0100 Subject: [PATCH 0800/1428] Move the property to the right location --- src/ModuleSection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModuleSection.h b/src/ModuleSection.h index de351fb..e07044d 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -35,6 +35,7 @@ class ModuleSection : public QObject Q_PROPERTY(QString qml READ startQMLFile CONSTANT) Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: /// The placement in the main app of this section. enum SectionType { @@ -94,7 +95,6 @@ private: bool m_enabled = true; QStringList m_requiredModules; - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) }; #endif -- 2.54.0 From b24a939b93758e6e32996f753fc472af1ec99d1d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 Jan 2024 17:22:12 +0100 Subject: [PATCH 0801/1428] Be nice to devs. --- src/ModuleManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index cd32c2e..e40e2d1 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -163,6 +163,12 @@ void ModuleManager::load() void ModuleManager::save() const { + if (m_modules.isEmpty()) { + // be nice to the devs that use both mobile and non-mobile on their + // account, the non-mobile has no modules, as such we just don't + // (over)write the config file. + return; + } int saveFileSize = 100; for (const auto *m : m_modules) { saveFileSize += m->id().size() * 3; -- 2.54.0 From 3599385784646fbeacb9221ca549ddf0304c872c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 3 Feb 2024 23:36:44 +0100 Subject: [PATCH 0802/1428] minor spacing tweaks --- guis/mobile/AccountSelectorPopup.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 858315a..447cb79 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ import "../Flowee" as Flowee QQC2.Popup { id: root closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent - height: columnLayout.height + 10 + height: columnLayout.height + 16 focus: true // to allow Escape to close it property QtObject selectedAccount: portfolio.current @@ -158,6 +158,7 @@ QQC2.Popup { } } + Item { width: 10; height: 10 } // spacer Item { width: parent.width opacity: 0.8 // less bright || dark text @@ -170,7 +171,7 @@ QQC2.Popup { x: 6 y: 5 text: qsTr("Balance Total") + ":" - font.pixelSize: root.font.pixelSize * 0.8 + font.pixelSize: root.font.pixelSize * 0.9 } Flowee.BitcoinAmountLabel { id: totalAmount @@ -178,7 +179,7 @@ QQC2.Popup { anchors.right: parent.right anchors.rightMargin: 6 anchors.bottom: parent.bottom - font.pixelSize: root.font.pixelSize * 0.8 + font.pixelSize: root.font.pixelSize * 0.9 colorize: false } } -- 2.54.0 From 073ffe6e3b2424c2db8e3b8194252526cc64b3c3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 3 Feb 2024 23:52:27 +0100 Subject: [PATCH 0803/1428] New feature; show fusion icon on TransactionDetails When an input used in a transaction comes from a cash-fusion transaction, we now put the CF logo next to it in order to make this fact clear to the user and allow them to understand how tracable this specific payment was. --- guis/mobile/TransactionDetails.qml | 8 ++++++-- src/TransactionInfo.cpp | 10 ++++++++++ src/TransactionInfo.h | 7 ++++++- src/Wallet_support.cpp | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 7efe987..3b2a8b8 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -200,12 +200,16 @@ Page { return inAddress.height + 10; return inAddress.height + amount.height + 16; } + Flowee.CFIcon { + id: fusedIcon + visible: modelData.fromFused + } Flowee.AddressLabel { id: inAddress txInfo: modelData - width: Math.min(implicitWidth, parent.width) + x: fusedIcon.visible ? fusedIcon.width + 6 : 0 + width: Math.min(implicitWidth, parent.width - (fusedIcon.visible ? fusedIcon.width: 0)) } - Flowee.BitcoinAmountLabel { id: amount visible: modelData !== null diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 6010b95..7b06d45 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -186,6 +186,16 @@ void TransactionInputInfo::setCloakedAddress(const QString &newCloakedAddress) m_cloakedAddress = newCloakedAddress; } +void TransactionInputInfo::setFromFused(bool fused) +{ + m_fromFused = fused; +} + +bool TransactionInputInfo::fromFused() const +{ + return m_fromFused; +} + // ////////////////////////////////////////////////////// diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 0739359..a31df31 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ class TransactionInputInfo : public QObject Q_PROPERTY(QString address READ address CONSTANT) Q_PROPERTY(QString cloakedAddress READ cloakedAddress CONSTANT) Q_PROPERTY(qint64 value READ value CONSTANT) + Q_PROPERTY(bool fromFused READ fromFused CONSTANT FINAL) public: TransactionInputInfo(QObject *parent); @@ -42,10 +43,14 @@ public: const QString &cloakedAddress() const; void setCloakedAddress(const QString &newCloakedAddress); + void setFromFused(bool fused); + bool fromFused() const; + private: QString m_address; QString m_cloakedAddress; qint64 m_value = 0; + bool m_fromFused = false; }; class TransactionOutputInfo : public QObject diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 7f4c7b5..02deaf7 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -212,6 +212,7 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) else if (secret.fromHdWallet) { in->setCloakedAddress(tr("Main #%1").arg(secret.hdDerivationIndex)); } + in->setFromFused(w->second.isCashFusionTx); info->m_inputs[pair.first] = in; } #ifndef NDEBUG -- 2.54.0 From 539d52820e4155c98de5dab84cd71ec46c207fcf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Feb 2024 15:53:03 +0100 Subject: [PATCH 0804/1428] update build instructions --- README.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe0c8e2..5320b42 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,15 @@ Additionally you will want to have some development packages installed; ``` sh sudo apt install build-essential libboost-all-dev libssl-dev cmake \ - qt6-tools-dev-tools qt6-tools-dev qt6-declarative-dev libqrencode-dev \ - libqt6svg6-dev - + libzxing-dev qdbus-qt6 qt6-l10n-tools, libqt6shadertools6-dev \ + libqt6svg6-dev qt6-base-dev qt6-declarative-dev qt6-tools-dev ``` -After installing that succesfull, it is just a matter of calling: +If you use the older Ubuntu Jammy (2022-04), you need to also install + `libgl-dev` since that was missing fromt the Qt packages back then. + +After installing that succesfully, you can compile Flowee Pay by fetching +the sources and in the source tree run this sequence: ``` sh mkdir build @@ -58,6 +61,19 @@ After installing that succesfull, it is just a matter of calling: make install ``` +Running Flowee Pay requires Qt Quick, which on debian based Linux distro's +have been packaged to a painful number of tiny packages and as such you +will need these too: + +``` sh + sudo apt install \ + qml6-module-qtquick qml6-module-qtqml-workerscript \ + qml6-module-qtquick-controls qml6-module-qtquick-dialogs \ + qml6-module-qtquick-layouts qml6-module-qtquick-shapes \ + qml6-module-qtquick-templates qml6-module-qtquick-window \ +``` + + ### Manually compiling flowee-libs: We depend on the libraries shipped in 'theHub' git repo, also from Flowee. -- 2.54.0 From 2facea90d8c9a0a47b11b1cd5f736cc014e4e620 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Feb 2024 16:39:08 +0100 Subject: [PATCH 0805/1428] Make this compile with older ZXIng libs too --- src/QRCreator.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/QRCreator.cpp b/src/QRCreator.cpp index 1e5d9ba..2589b2c 100644 --- a/src/QRCreator.cpp +++ b/src/QRCreator.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2018-2023 Tom Zander + * Copyright (C) 2018-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,9 @@ #include #include +#include +#include + QRCreator::QRCreator(QRType type) : QQuickImageProvider(QQmlImageProviderBase::Image), m_type(type) @@ -33,7 +36,7 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ { Q_UNUSED(size); Q_UNUSED(requestedSize); - std::string data; // assumed utf8 by zxing + std::string data; // assumed utf8 if (m_type == URLEncoded) { QUrl url(id); // go via URL to encode spaces and special chars data = url.toEncoded(); @@ -42,7 +45,20 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ } auto writer = ZXing::MultiFormatWriter(ZXing::BarcodeFormat::QRCode).setMargin(16); - ZXing::BitMatrix matrix = writer.encode(data, 250, 250); + /* + * In newer versions of zxing there is a direct std::string version of the encode() + * call which is nice to avoid the extra coversion. + * For now we leave this extra code here as long as we are still able to compile and + * run on Ubuntu 2022.04 (jammy) which doesn't have this new call. + * + * Ironically, the codecvt below code is deprecated in C++17, so you get warnings now. + * Can't win this one, I guess... + * But the promise is that it will be part of C++ till the 2026 release, + * and I prefer it actually compiling on older zxing. So maybe lets just + * plan to remove the wstring conversion in a year or so (TZ: Feb 2024) + */ + std::wstring wdata = std::wstring_convert>().from_bytes(data); + ZXing::BitMatrix matrix = writer.encode(wdata, 250, 250); QImage result = QImage(matrix.height(), matrix.width(), QImage::Format_RGB32); constexpr uint black = 0xFF000000; -- 2.54.0 From 2391de723f8784dbaf28d767d69cfcfba10ea388 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 Feb 2024 21:30:34 +0100 Subject: [PATCH 0806/1428] Make Blur an optional component As we still support older versions of Qt, this moves to load the MultiEffect (blurring specifically) dynamically and avoids the entire project failing if the effects module is not found. The effects module was introduced in Qt6.5 --- guis/desktop.qrc | 1 + guis/desktop/BlurComponents.qml | 43 +++++++++++++++++++++++++++++++++ guis/desktop/main.qml | 39 +++++++++++------------------- 3 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 guis/desktop/BlurComponents.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc index c7ca36e..c1e0d28 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -33,5 +33,6 @@ desktop/WalletEncryptionStatus.qml desktop/AccountConfigMenu.qml desktop/AddressDbStats.qml + desktop/BlurComponents.qml diff --git a/guis/desktop/BlurComponents.qml b/guis/desktop/BlurComponents.qml new file mode 100644 index 0000000..8e51ab5 --- /dev/null +++ b/guis/desktop/BlurComponents.qml @@ -0,0 +1,43 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Effects + +Item { + property alias biggerBlur: biggerBlur + property alias simpleBlur: simpleBlur + + Component { + id: biggerBlur + MultiEffect { + blurEnabled: true + blur: 1 + blurMultiplier: 0.4 + blurMax: 40 + } + } + + Component { + id: simpleBlur + + MultiEffect { + blurEnabled: true + blur: 1 + } + } +} diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index cf3ae45..8a0fbbe 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -17,7 +17,6 @@ */ import QtQuick import QtQuick.Controls -import QtQuick.Effects import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors @@ -69,6 +68,12 @@ ApplicationWindow { anchors.fill: parent focus: true + Loader { + id: effects + source: "./BlurComponents.qml" + } + + Rectangle { id: header color: Pay.useDarkSkin ? "#00000000" : mainWindow.floweeBlue @@ -130,13 +135,8 @@ ApplicationWindow { color: "white" showFiat: false fontPixelSize: 28 - layer.enabled: Pay.hideBalance - layer.effect: MultiEffect { - blurEnabled: true - blur: 1 - blurMultiplier: 0.4 - blurMax: 40 - } + layer.enabled: effects.loaded && Pay.hideBalance + layer.effect: effects.item.biggerBlur } } Label { @@ -154,11 +154,8 @@ ApplicationWindow { } visible: balanceInHeader.visible opacity: 0.6 - layer.enabled: Pay.hideBalance - layer.effect: MultiEffect { - blurEnabled: true - blur: 1 - } + layer.enabled: effects.loaded && Pay.hideBalance + layer.effect: effects.item.simpleBlur } } @@ -488,13 +485,8 @@ ApplicationWindow { return leftColumn.width / 9 return 27; } - layer.enabled: Pay.hideBalance - layer.effect: MultiEffect { - blurEnabled: true - blur: 1 - blurMultiplier: 0.4 - blurMax: 40 - } + layer.enabled: effects.loaded && Pay.hideBalance + layer.effect: effects.item.biggerBlur } GridLayout { @@ -568,11 +560,8 @@ ApplicationWindow { return Fiat.formattedPrice(balance.value, Fiat.price); } opacity: 0.6 - layer.enabled: Pay.hideBalance - layer.effect: MultiEffect { - blurEnabled: true - blur: 1 - } + layer.enabled: effects.loaded && Pay.hideBalance + layer.effect: effects.item.simpleBlur } Item { width: 1; height: fiatValue.visible ? 10 : 0 } // spacer Item { -- 2.54.0 From 037aff628bed204a39f3953cfdf70db34c394746 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 9 Feb 2024 20:51:26 +0100 Subject: [PATCH 0807/1428] Move the paste/flash buttons on the scanner screen. The newer Android versions overlay a "Flowee Pay pasted from your clipboard" text. The location is dependent on model, but generally it is located at the bottom. So avoid the chance when the paste button is under that helpful OS text. --- guis/mobile/QRScannerOverlay.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 9e4e4ac..3376c57 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -111,8 +111,8 @@ FocusScope { Rectangle { id: pasteFrame x: 50 - anchors.bottom: parent.bottom - anchors.bottomMargin: 50 + anchors.top: topBar.bottom + anchors.topMargin: 6 visible: CameraController.supportsPaste && cbh.text !== "" radius: 6 width: pasteButton.width @@ -139,7 +139,7 @@ FocusScope { width: errorLabel.width + 10 height: errorLabel.height + 10 radius: 5 - anchors.top : pasteButton.bottom + anchors.top: pasteButton.bottom visible: false Flowee.Label { @@ -157,7 +157,7 @@ FocusScope { } Rectangle { id: flashFrame - anchors.top: pasteFrame.top + anchors.top: pasteFrame.bottom anchors.right: parent.right anchors.rightMargin: 50 radius: 6 @@ -175,6 +175,7 @@ FocusScope { } Rectangle { + id: topBar width: parent.width height: Math.max(closeIcon.height, instaLabel.height) + 20 color: mainWindow.floweeBlue -- 2.54.0 From 30c29fd3646058d65af77e76b2d74c44a0c7138a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 9 Feb 2024 21:16:21 +0100 Subject: [PATCH 0808/1428] Add asserts to make requirement clear. --- src/WalletHistoryModel.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 28430cc..6881e39 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -23,6 +23,7 @@ #include #include #include +#include /* * Attempt to add a transaction to this group. @@ -102,6 +103,7 @@ WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) int WalletHistoryModel::rowCount(const QModelIndex &parent) const { + assert(QThread::currentThread() == thread()); if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree return 0; @@ -110,6 +112,7 @@ int WalletHistoryModel::rowCount(const QModelIndex &parent) const QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const { + assert(QThread::currentThread() == thread()); if (!index.isValid()) return QVariant(); @@ -194,6 +197,7 @@ QVariant WalletHistoryModel::data(const QModelIndex &index, int role) const int WalletHistoryModel::groupIdForTxIndex(int txIndex) const { + assert(QThread::currentThread() == thread()); // Method is const because the cache is mutable. if (m_groupCache.txIndex != txIndex) { for (size_t i = 0; i < m_groups.size(); ++i) { @@ -210,6 +214,7 @@ int WalletHistoryModel::groupIdForTxIndex(int txIndex) const QString WalletHistoryModel::groupingPeriod(int groupId) const { + assert(QThread::currentThread() == thread()); if (groupId < 0 || groupId >= static_cast(m_groups.size())) return "bad groupId"; switch (m_groups.at(groupId).period) { @@ -237,6 +242,7 @@ QString WalletHistoryModel::groupingPeriod(int groupId) const QHash WalletHistoryModel::roleNames() const { + assert(QThread::currentThread() == thread()); QHash answer; answer[NewTransaction] = "isNew"; // Deprecated answer[TxId] = "txid"; @@ -256,6 +262,7 @@ QHash WalletHistoryModel::roleNames() const QString WalletHistoryModel::dateForItem(qreal offset) const { + assert(QThread::currentThread() == thread()); if (m_rowsProxy.isEmpty()) return QString(); if (std::isnan(offset) || offset < 0 || offset > 1.0) @@ -276,6 +283,7 @@ QString WalletHistoryModel::dateForItem(qreal offset) const void WalletHistoryModel::appendTransactions(int firstNew, int count) { + assert(QThread::currentThread() == thread()); QMutexLocker locker(&m_wallet->m_lock); const auto oldCount = m_rowsProxy.size(); for (auto i = firstNew; i < firstNew + count; ++i) { @@ -308,6 +316,7 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) void WalletHistoryModel::removeTransaction(int txIndex) { + assert(QThread::currentThread() == thread()); const int index = m_rowsProxy.indexOf(txIndex); if (index == -1) // probably never got inserted return; @@ -321,6 +330,7 @@ void WalletHistoryModel::removeTransaction(int txIndex) void WalletHistoryModel::transactionChanged(int txIndex) { + assert(QThread::currentThread() == thread()); const int index = m_rowsProxy.indexOf(txIndex); if (index == -1) // probably never got inserted return; @@ -336,6 +346,7 @@ void WalletHistoryModel::transactionChanged(int txIndex) void WalletHistoryModel::createMap() { + assert(QThread::currentThread() == thread()); m_recreateTriggered = false; m_groups.clear(); m_today = today(); @@ -369,6 +380,7 @@ void WalletHistoryModel::createMap() bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) const { + assert(QThread::currentThread() == thread()); if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) && wtx.isRejected()) @@ -380,6 +392,7 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) { + assert(QThread::currentThread() == thread()); if (m_groups.empty()) m_groups.push_back(TransactionGroup()); @@ -436,6 +449,7 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) int WalletHistoryModel::txIndexFromRow(int row) const { + assert(QThread::currentThread() == thread()); int newRow = m_rowsProxy.size() - row - 1; if (m_rowsSilentlyInserted > 0) newRow -= m_rowsSilentlyInserted; @@ -446,11 +460,13 @@ int WalletHistoryModel::txIndexFromRow(int row) const const QFlags &WalletHistoryModel::includeFlags() const { + assert(QThread::currentThread() == thread()); return m_includeFlags; } void WalletHistoryModel::setIncludeFlags(const QFlags &flags) { + assert(QThread::currentThread() == thread()); if (m_includeFlags == flags) return; m_includeFlags = flags; @@ -464,6 +480,7 @@ void WalletHistoryModel::setIncludeFlags(const QFlags &fla void WalletHistoryModel::freezeModel(bool on) { + assert(QThread::currentThread() == thread()); if ((on && m_rowsSilentlyInserted >= 0) || (!on && m_rowsSilentlyInserted < 0)) return; if (on) { @@ -484,11 +501,13 @@ void WalletHistoryModel::freezeModel(bool on) bool WalletHistoryModel::isModelFrozen() const { + assert(QThread::currentThread() == thread()); return m_rowsSilentlyInserted >= 0; } uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const { + assert(QThread::currentThread() == thread()); assert(blockHeight > 0); // wrap this for convenience and also ensure that we never return an insanely old // date (1970) just because we lack blockheader data. @@ -504,11 +523,13 @@ uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const QDate WalletHistoryModel::today() const { + assert(QThread::currentThread() == thread()); return QDate::currentDate(); } int WalletHistoryModel::lastSyncIndicator() const { + assert(QThread::currentThread() == thread()); return m_lastSyncIndicator; } @@ -521,6 +542,7 @@ void WalletHistoryModel::setLastSyncIndicator(int) void WalletHistoryModel::resetLastSyncIndicator() { + assert(QThread::currentThread() == thread()); const auto old = m_lastSyncIndicator; m_lastSyncIndicator = m_wallet->segment()->lastBlockSynched(); -- 2.54.0 From 61ff5172d757fb80033a87e57c7f884624f4666e Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 9 Feb 2024 21:42:00 +0100 Subject: [PATCH 0809/1428] Add asserts and ensure thread-safety. Add asserts making sure we're only called in the GUI thread. Add missing mutex locks. --- src/WalletSecretsModel.cpp | 19 +++++++++++++++++-- src/WalletSecretsModel.h | 2 ++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/WalletSecretsModel.cpp b/src/WalletSecretsModel.cpp index 613f1e9..27dd49d 100644 --- a/src/WalletSecretsModel.cpp +++ b/src/WalletSecretsModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include "Wallet.h" #include "FloweePay.h" +#include #include #include @@ -29,11 +30,12 @@ WalletSecretsModel::WalletSecretsModel(Wallet *wallet, QObject *parent) assert(m_wallet); update(); - connect (wallet, SIGNAL(appendedTransactions(int,int)), this, SLOT(transactionsAddedToWallet(int,int))); + connect (wallet, SIGNAL(appendedTransactions(int,int)), this, SLOT(transactionsAddedToWallet(int,int)), Qt::QueuedConnection); } int WalletSecretsModel::rowCount(const QModelIndex &parent) const { + assert(QThread::currentThread() == thread()); if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree return 0; @@ -42,6 +44,7 @@ int WalletSecretsModel::rowCount(const QModelIndex &parent) const QVariant WalletSecretsModel::data(const QModelIndex &index, int role) const { + assert(QThread::currentThread() == thread()); if (!index.isValid()) return QVariant(); @@ -82,6 +85,7 @@ QVariant WalletSecretsModel::data(const QModelIndex &index, int role) const QHash WalletSecretsModel::roleNames() const { + assert(QThread::currentThread() == thread()); QHash answer; answer[BitcoinAddress] = "address"; answer[PrivateKey] = "privatekey"; @@ -97,11 +101,13 @@ QHash WalletSecretsModel::roleNames() const bool WalletSecretsModel::showChangeChain() const { + assert(QThread::currentThread() == thread()); return m_showChangeChain; } void WalletSecretsModel::setShowChangeChain(bool on) { + assert(QThread::currentThread() == thread()); if (m_showChangeChain == on) return; m_showChangeChain = on; @@ -111,11 +117,13 @@ void WalletSecretsModel::setShowChangeChain(bool on) bool WalletSecretsModel::showUsedAddresses() const { + assert(QThread::currentThread() == thread()); return m_showUsedAddresses; } void WalletSecretsModel::setShowUsedAddresses(bool on) { + assert(QThread::currentThread() == thread()); if (m_showUsedAddresses == on) return; m_showUsedAddresses = on; @@ -125,11 +133,13 @@ void WalletSecretsModel::setShowUsedAddresses(bool on) const QString &WalletSecretsModel::searchTerm() const { + assert(QThread::currentThread() == thread()); return m_search; } void WalletSecretsModel::search(const QString &newSearch) { + assert(QThread::currentThread() == thread()); if (m_search == newSearch) return; m_search = newSearch; @@ -139,6 +149,7 @@ void WalletSecretsModel::search(const QString &newSearch) void WalletSecretsModel::update() { + assert(QThread::currentThread() == thread()); assert(m_wallet); QMutexLocker locker(&m_wallet->m_lock); if (!m_selectedPrivates.isEmpty()) { @@ -201,6 +212,7 @@ void WalletSecretsModel::update() const Wallet::KeyDetails &WalletSecretsModel::details(int privKeyId) const { + assert(QThread::currentThread() == thread()); QMutexLocker locker(&m_lock); auto iter = m_keyDetails.find(privKeyId); if (iter == m_keyDetails.end()) { @@ -213,7 +225,9 @@ const Wallet::KeyDetails &WalletSecretsModel::details(int privKeyId) const void WalletSecretsModel::transactionsAddedToWallet(int from, int count) { + assert(QThread::currentThread() == thread()); assert(m_wallet); + QMutexLocker locker(&m_wallet->m_lock); auto iter = m_wallet->m_walletTransactions.find(from); std::set changedPrivateKeys; while ( count-- > 0 && iter != m_wallet->m_walletTransactions.end()) { @@ -233,6 +247,7 @@ void WalletSecretsModel::transactionsAddedToWallet(int from, int count) } logDebug() << "New transactions touched these private keys:" << changedPrivateKeys; + QMutexLocker localLocker(&m_lock); for (auto privKeyId : changedPrivateKeys) { auto detailsIter = m_keyDetails.find(privKeyId); if (detailsIter != m_keyDetails.end()) { diff --git a/src/WalletSecretsModel.h b/src/WalletSecretsModel.h index 690de2e..1a50d4d 100644 --- a/src/WalletSecretsModel.h +++ b/src/WalletSecretsModel.h @@ -85,6 +85,8 @@ private: QString m_search; QVector m_selectedPrivates; + // locks m_keyDetails. + // Never lock this unless m_wallet->m_lock is already locked. mutable QMutex m_lock; mutable std::map m_keyDetails; }; -- 2.54.0 From a8c9fdca2906dc5cec64ee404bc6a082789bb020 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 9 Feb 2024 22:40:40 +0100 Subject: [PATCH 0810/1428] Add asserts and avoid duplicate work When a wallet is updating we may get a lot of new transactions added in a short amount of time, this avoids calling updateMap for each one in turn and instead triggers an update only if there isn't one in-flight. This takes advantage of the fact that the wallet does its update in a different thread than the UI-one. --- src/WalletCoinsModel.cpp | 24 ++++++++++++++++++++---- src/WalletCoinsModel.h | 5 +++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp index ccfd3df..2310f51 100644 --- a/src/WalletCoinsModel.cpp +++ b/src/WalletCoinsModel.cpp @@ -22,13 +22,17 @@ #include #include +#include #include WalletCoinsModel::WalletCoinsModel(Wallet *wallet, QObject *parent) - : QAbstractListModel(parent) + : QAbstractListModel(parent), + m_triggered(0) { setWallet(wallet); + + connect (this, SIGNAL(startRecalc()), this, SLOT(utxosChanged()), Qt::QueuedConnection); } void WalletCoinsModel::setWallet(Wallet *newWallet) @@ -40,7 +44,11 @@ void WalletCoinsModel::setWallet(Wallet *newWallet) if (m_wallet) disconnect (m_wallet, SIGNAL(utxosChanged()), this, SLOT(utxosChanged())); m_wallet = newWallet; - connect (m_wallet, SIGNAL(utxosChanged()), this, SLOT(utxosChanged())); + connect (m_wallet, &Wallet::utxosChanged, this, [=]() { + // when there are a log of changes, only schedule one update. + if (m_triggered.testAndSetAcquire(0, 1)) + emit startRecalc(); + }); beginRemoveRows(QModelIndex(), 0, m_rowsToOutputRefs.size() - 1); endRemoveRows(); @@ -51,6 +59,7 @@ void WalletCoinsModel::setWallet(Wallet *newWallet) int WalletCoinsModel::rowCount(const QModelIndex &parent) const { + assert(QThread::currentThread() == thread()); if (parent.isValid()) // only for the (invalid) root node we return a count, since this is a list not a tree return 0; @@ -59,6 +68,7 @@ int WalletCoinsModel::rowCount(const QModelIndex &parent) const QVariant WalletCoinsModel::data(const QModelIndex &index, int role) const { + assert(QThread::currentThread() == thread()); if (!index.isValid()) return QVariant(); @@ -170,6 +180,7 @@ QHash WalletCoinsModel::roleNames() const uint64_t WalletCoinsModel::outRefForRow(int row) const { + assert(QThread::currentThread() == thread()); auto i = m_rowsToOutputRefs.find(row); if (i == m_rowsToOutputRefs.end()) return 0; @@ -178,6 +189,7 @@ uint64_t WalletCoinsModel::outRefForRow(int row) const void WalletCoinsModel::setOutputLocked(int row, bool lock) { + assert(QThread::currentThread() == thread()); auto i = m_rowsToOutputRefs.find(row); if (i == m_rowsToOutputRefs.end()) return; @@ -198,11 +210,13 @@ void WalletCoinsModel::setOutputLocked(int row, bool lock) void WalletCoinsModel::setSelectionGetter(const std::function &callback) { + assert(QThread::currentThread() == thread()); m_selectionGetter = callback; } void WalletCoinsModel::updateRow(uint64_t outRef) { + assert(QThread::currentThread() == thread()); for (auto i = m_rowsToOutputRefs.cbegin(); i != m_rowsToOutputRefs.cend(); ++i) { if (i->second == outRef) { updateRow(i->first); @@ -213,6 +227,7 @@ void WalletCoinsModel::updateRow(uint64_t outRef) void WalletCoinsModel::updateRow(int row) { + assert(QThread::currentThread() == thread()); beginRemoveRows(QModelIndex(), row, row); endRemoveRows(); beginInsertRows(QModelIndex(), row, row); @@ -221,6 +236,7 @@ void WalletCoinsModel::updateRow(int row) void WalletCoinsModel::utxosChanged() { + assert(QThread::currentThread() == thread()); beginRemoveRows(QModelIndex(), 0, m_rowsToOutputRefs.size()); endRemoveRows(); createMap(); @@ -230,6 +246,8 @@ void WalletCoinsModel::utxosChanged() void WalletCoinsModel::createMap() { + assert(QThread::currentThread() == thread()); + m_triggered.storeRelaxed(0); QMutexLocker locker(&m_wallet->m_lock); // yes, its a recursive lock m_rowsToOutputRefs.clear(); @@ -258,5 +276,3 @@ void WalletCoinsModel::createMap() m_rowsToOutputRefs.insert(std::make_pair(index++, i->first)); } } - - diff --git a/src/WalletCoinsModel.h b/src/WalletCoinsModel.h index 2698cd7..49c602d 100644 --- a/src/WalletCoinsModel.h +++ b/src/WalletCoinsModel.h @@ -20,6 +20,7 @@ #include #include +#include class Wallet; @@ -60,6 +61,9 @@ public: /// mark row dirty for refresh in the UI void updateRow(int row); +signals: + void startRecalc(); + private slots: void utxosChanged(); @@ -70,6 +74,7 @@ private: std::map m_rowsToOutputRefs; std::function m_selectionGetter; + QAtomicInt m_triggered; }; #endif -- 2.54.0 From 3872b7a8ba0ebdccdf4e980f900d4ba3d75a2829 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 9 Feb 2024 23:49:58 +0100 Subject: [PATCH 0811/1428] Have the thumb be grabbable while its hiding. The 200ms hiding animation now triggers the thumb to not be grabbably only after it completes, not when it starts. --- guis/Flowee/ScrollThumb.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index 5217fb2..e8d7df5 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,7 +49,7 @@ QQC2.ScrollBar { onMovingChanged: if (moving) open = true width: 18 height: 30 - x: moving || open ? parent.width - width: parent.width + 2 + x: moving || open ? parent.width - width : parent.width + 2 y: { var pos = root.position var size = root.size @@ -104,7 +104,7 @@ QQC2.ScrollBar { width: thumbRect.width + 20 + root.width height: thumbRect.height + 50 anchors.right: parent.right - enabled: thumbRect.moving || thumbRect.open + enabled: thumbRect.moving || thumbRect.open || thumbRect.x < root.width y: { var pos = root.position var size = root.size @@ -116,6 +116,7 @@ QQC2.ScrollBar { property real startPos: 0 property bool engaged: false // seems that 'Mousearea.pressed' behaves different than I expect, this works better onPressed: (mouse)=> { + thumbRect.open = true; startY = root.flickable.mapFromItem(thumbInput, mouse.x, mouse.y).y startPos = root.position engaged = true @@ -123,6 +124,7 @@ QQC2.ScrollBar { onReleased: engaged = false preventStealing: true onPositionChanged: (mouse)=> { + thumbRect.open = true; // Most of the scroller properties are in the 0.0 - 1.0 range var absolutePos = root.flickable.mapFromItem(thumbInput, mouse.x, mouse.y); var diff = startY - absolutePos.y; -- 2.54.0 From a6af4c87711d34db07229557504111064816a37f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 00:02:51 +0100 Subject: [PATCH 0812/1428] Remap 'tried' to a more logical backing value. This changes 'tried' being about the amount of peers we actually handshaked with to the amount of peers we tried to connect to at all. --- guis/desktop/AddressDbStats.qml | 3 ++- modules/peers-view/StatsPage.qml | 3 ++- src/NetDataProvider.cpp | 14 ++++++++++++++ src/NetDataProvider.h | 7 +++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/guis/desktop/AddressDbStats.qml b/guis/desktop/AddressDbStats.qml index ff132a9..989b46a 100644 --- a/guis/desktop/AddressDbStats.qml +++ b/guis/desktop/AddressDbStats.qml @@ -54,7 +54,7 @@ QQC2.ApplicationWindow { Layout.alignment: Qt.AlignRight } QQC2.Label { - text: root.stats.everConnected; + text: root.stats.tried; } QQC2.Label { text: qsTr("Punished count") + ":" @@ -95,6 +95,7 @@ QQC2.ApplicationWindow { net.pardonBanned(); var newStats = net.createStats(root); root.stats.count = newStats.count; + root.stats.tried = newStats.tried; root.stats.everConnected = newStats.everConnected; root.stats.partialBanned = newStats.partialBanned; root.stats.banned = newStats.banned; diff --git a/modules/peers-view/StatsPage.qml b/modules/peers-view/StatsPage.qml index da4db5b..37bf156 100644 --- a/modules/peers-view/StatsPage.qml +++ b/modules/peers-view/StatsPage.qml @@ -37,7 +37,7 @@ Mobile.Page { text: qsTr("Total found") + ": " + root.stats.count; } Flowee.Label { - text: qsTr("Tried") + ": " + root.stats.everConnected; + text: qsTr("Tried") + ": " + root.stats.tried; } } Mobile.PageTitledBox { @@ -70,6 +70,7 @@ Mobile.Page { net.pardonBanned(); var newStats = net.createStats(root); root.stats.count = newStats.count; + root.stats.tried = newStats.tried; root.stats.everConnected = newStats.everConnected; root.stats.partialBanned = newStats.partialBanned; root.stats.banned = newStats.banned; diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 9ec043e..c02c5e0 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -31,6 +31,7 @@ BasicAddressStats::BasicAddressStats(const AddressDBStats &stats, QObject *paren m_partialBanned(stats.partialBanned), m_ipv6Addresses(stats.ipv6Addresses), m_everConnected(stats.everConnected), + m_tried(stats.tried), m_usesIPv4(stats.usesIPv4), m_usesIPv6(stats.usesIPv6) { @@ -114,6 +115,19 @@ void BasicAddressStats::setUsesIPv6(bool newUsesIPv6) emit usesIPv6Changed(); } +int BasicAddressStats::tried() const +{ + return m_tried; +} + +void BasicAddressStats::setTried(int newTried) +{ + if (m_tried == newTried) + return; + m_tried = newTried; + emit triedChanged(); +} + int BasicAddressStats::everConnected() const { return m_everConnected; diff --git a/src/NetDataProvider.h b/src/NetDataProvider.h index 4cb2942..cc8bb86 100644 --- a/src/NetDataProvider.h +++ b/src/NetDataProvider.h @@ -40,6 +40,7 @@ class BasicAddressStats : public QObject Q_PROPERTY(int partialBanned READ partialBanned WRITE setPartialBanned NOTIFY partialBannedChanged FINAL) Q_PROPERTY(int ipv6Addresses READ ipv6Addresses WRITE setIpv6Addresses NOTIFY ipv6AddressesChanged FINAL) Q_PROPERTY(int everConnected READ everConnected WRITE setEverConnected NOTIFY everConnectedChanged FINAL) + Q_PROPERTY(int tried READ tried WRITE setTried NOTIFY triedChanged FINAL) Q_PROPERTY(bool usesIPv4 READ usesIPv4 WRITE setUsesIPv4 NOTIFY usesIPv4Changed FINAL) Q_PROPERTY(bool usesIPv6 READ usesIPv6 WRITE setUsesIPv6 NOTIFY usesIPv6Changed FINAL) public: @@ -66,6 +67,9 @@ public: bool usesIPv6() const; void setUsesIPv6(bool newUsesIPv6); + int tried() const; + void setTried(int newTried); + signals: void countChanged(); void bannedChanged(); @@ -75,12 +79,15 @@ signals: void usesIPv4Changed(); void usesIPv6Changed(); + void triedChanged(); + private: int m_count; int m_banned; int m_partialBanned; int m_ipv6Addresses; int m_everConnected; + int m_tried; bool m_usesIPv4; bool m_usesIPv6; }; -- 2.54.0 From 523499cf7d762ad8acdcf8ab548a9a42627ab0fa Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 14:42:03 +0100 Subject: [PATCH 0813/1428] Fix the 'archive' button not showing up. We now fix archive being there when it makes sense, and also fix the focus being lost after toggling the archivi-ness of a wallet. Which means that hitting 'Ctrl-Q' will correctly close the app after. --- guis/desktop/AccountConfigMenu.qml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 46172bc..21a89b4 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,16 @@ ConfigItem { } property QtObject archiveAction: Action { text: root.account != null && root.account.isArchived ? qsTr("Unarchive") : qsTr("Archive Wallet") - onTriggered: root.account.isArchived = !root.account.isArchived + onTriggered: { + /* + * Toggling archive means this list and the list-item will be removed and a new one created at + * another place on screen. + * Focus is lost when the item that holds it suddenly dies, so let make sure it is moved + * somewhere useful first, allowing keyboard shortcuts to still work after. + */ + mainScreen.forceActiveFocus(); // when the parent item is removed, make sure _something_ has focus + root.account.isArchived = !root.account.isArchived + } } property QtObject primaryAction: Action { enabled: root.account != null && !root.account.isPrimaryAccount @@ -68,7 +77,7 @@ ConfigItem { items.push(closeWalletAction); if (onMainView && encrypted && !decrypted && tabbar.currentIndex != 0) items.push(openWalletAction); - var singleAccountSetup = portfolio.singleAccountSetup + var singleAccountSetup = portfolio.rawAccounts.length === 1 var isArchived = root.account.isArchived; if (!singleAccountSetup && !isArchived) items.push(primaryAction); -- 2.54.0 From c48f8500b5178796b13bfc40e2ac9cadd15b53bf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 19:30:28 +0100 Subject: [PATCH 0814/1428] Fix virtual transaction showing up in some cases In very rare cases on flusing the 'new since' feature we'd end up with a virtual transaction created at the bottom of the list. We now avoid this. --- src/WalletHistoryModel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 6881e39..0283f25 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -553,9 +553,10 @@ void WalletHistoryModel::resetLastSyncIndicator() break; --index; } - const int lastRow = m_rowsProxy.size() - index - 1; + const int lastRow = m_rowsProxy.size() - std::max(0, index) - 1; // refresh the rows that need the 'new' indicator removed. if (lastRow > 0) { + assert(lastRow < m_rowsProxy.size()); beginRemoveRows(QModelIndex(), 0, lastRow); endRemoveRows(); beginInsertRows(QModelIndex(), 0, lastRow); -- 2.54.0 From f0c9db2703411ad401e4e0b62ceb3a4078679c75 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 19:47:55 +0100 Subject: [PATCH 0815/1428] Improve debug logging slightly --- src/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index c9ef648..1935874 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1053,7 +1053,7 @@ void Wallet::rebuildBloom(RebuildBloomOption option) } } } - logDebug(LOG_WALLET) << "Rebuilding bloom filter. UTXO-size:" << secretsWithBalance.size(); + logDebug(LOG_WALLET) << "Rebuilding bloom filter." << m_segment->segmentId() << "UTXO-size:" << secretsWithBalance.size(); for (auto &i : m_walletSecrets) { const auto &secret = i.second; assert(secret.initialHeight < 10000000); // see similar check at start of this method -- 2.54.0 From 3a254cdb69cf54b1d152c8a1e7b6f75f9ff0763a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 20:24:59 +0100 Subject: [PATCH 0816/1428] Add mutex lockers where the m_wallet is used. --- src/WalletHistoryModel.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 0283f25..c9861d6 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -270,6 +270,7 @@ QString WalletHistoryModel::dateForItem(qreal offset) const const int row = std::round(offset * m_rowsProxy.size()); if (row >= m_rowsProxy.size()) return QString(); + QMutexLocker locker(&m_wallet->m_lock); auto item = m_wallet->m_walletTransactions.at(txIndexFromRow(row)); if (item.minedBlockHeight <= 0) return QString(); @@ -356,13 +357,13 @@ void WalletHistoryModel::createMap() m_rowsProxy.clear(); endRemoveRows(); } - m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); // we insert the key used in the m_wallet->m_walletTransaction map // in the order of how our rows work here. // This is oldest to newest, which is how our model is also structured. { QMutexLocker locker(&m_wallet->m_lock); + m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); for (const auto &iter : m_wallet->m_walletTransactions) { if (!filterTransaction(iter.second)) continue; @@ -396,6 +397,7 @@ void WalletHistoryModel::addTxIndexToGroups(int txIndex, int blockheight) if (m_groups.empty()) m_groups.push_back(TransactionGroup()); + QMutexLocker locker(&m_wallet->m_lock); uint32_t timestamp = 0; auto txIter = m_wallet->m_walletTransactions.find(txIndex); if (txIter != m_wallet->m_walletTransactions.end()) @@ -543,6 +545,7 @@ void WalletHistoryModel::setLastSyncIndicator(int) void WalletHistoryModel::resetLastSyncIndicator() { assert(QThread::currentThread() == thread()); + QMutexLocker locker(&m_wallet->m_lock); const auto old = m_lastSyncIndicator; m_lastSyncIndicator = m_wallet->segment()->lastBlockSynched(); -- 2.54.0 From dfa96a79a547a7d53c9a791a52034ce3f8727a3c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 10 Feb 2024 21:49:30 +0100 Subject: [PATCH 0817/1428] Handle transactions being deleted while scrolling. --- src/WalletHistoryModel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index c9861d6..982aa23 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -271,7 +271,12 @@ QString WalletHistoryModel::dateForItem(qreal offset) const if (row >= m_rowsProxy.size()) return QString(); QMutexLocker locker(&m_wallet->m_lock); - auto item = m_wallet->m_walletTransactions.at(txIndexFromRow(row)); + auto txIter = m_wallet->m_walletTransactions.find(txIndexFromRow(row)); + if (txIter == m_wallet->m_walletTransactions.end()) { + // it is possible for a transaction to be removed async... + return QString(); + } + const auto &item = txIter->second; if (item.minedBlockHeight <= 0) return QString(); auto timestamp = secsSinceEpochFor(item.minedBlockHeight); -- 2.54.0 From f06add936e60a1dbab00075b85e6fbad784f5480 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Feb 2024 17:52:54 +0100 Subject: [PATCH 0818/1428] Small UX fixes The main font on Android seems to not ship with 'semi-bold' on various devices. Which means that the title just was the same as the content and while I like subtlety, in this case if failed. Better to go with bold than with no difference at all... Additionally small spacing / sizing fixes. --- guis/mobile/PageTitledBox.qml | 2 +- guis/mobile/TransactionDetails.qml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index b7812e8..25c7f9f 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -46,7 +46,7 @@ Item { } Flowee.Label { id: boxTitle - font.weight: 600 + font.weight: 700 y: 4 } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 3b2a8b8..05be332 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -55,6 +55,7 @@ Page { anchors.right: copyIcon.left anchors.rightMargin: 10 wrapMode: Text.WrapAnywhere + font.pixelSize: root.font.pixelSize * 0.9 Rectangle { id: txidHighlight @@ -170,6 +171,7 @@ Page { } PageTitledBox { + spacing: 10 title: { if (root.infoObject == null) return ""; @@ -225,6 +227,7 @@ Page { } PageTitledBox { + spacing: 10 title: { if (root.infoObject == null) return ""; -- 2.54.0 From bc7891757d9ff5c980cd74abd3b268c599a36e0a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Feb 2024 18:34:32 +0100 Subject: [PATCH 0819/1428] Make the TransactionInfo on desktop better This follows the general idea and design from mobile, but adapted for the desktop form-factor. --- guis/desktop.qrc | 31 +- ...{WalletTransaction.qml => Transaction.qml} | 28 +- guis/desktop/TransactionDetails.qml | 61 ++++ guis/desktop/TransactionInfoSmall.qml | 214 ++++++++++++++ guis/desktop/WalletTransactionDetails.qml | 273 ------------------ guis/desktop/main.qml | 21 +- guis/mobile/TransactionDetails.qml | 76 +++++ guis/mobile/TransactionInfoSmall.qml | 6 +- 8 files changed, 400 insertions(+), 310 deletions(-) rename guis/desktop/{WalletTransaction.qml => Transaction.qml} (87%) create mode 100644 guis/desktop/TransactionDetails.qml create mode 100644 guis/desktop/TransactionInfoSmall.qml delete mode 100644 guis/desktop/WalletTransactionDetails.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc index c1e0d28..bd23f9e 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -16,23 +16,24 @@ desktop/ConfigItem.qml ControlColors.js desktop/main.qml - desktop/NetView.qml - desktop/ReceiveTransactionPane.qml - desktop/SendTransactionPane.qml - desktop/WalletTransactionDetails.qml - desktop/WalletTransaction.qml - desktop/AccountListItem.qml - desktop/AccountDetails.qml - desktop/SettingsPane.qml - desktop/NewAccountPane.qml - desktop/NewAccountCreateBasicAccount.qml - desktop/NewAccountImportAccount.qml - desktop/NewAccountCreateHDAccount.qml - desktop/WalletEncryption.qml - desktop/PaymentTweakingPanel.qml - desktop/WalletEncryptionStatus.qml desktop/AccountConfigMenu.qml + desktop/AccountDetails.qml + desktop/AccountListItem.qml desktop/AddressDbStats.qml desktop/BlurComponents.qml + desktop/NetView.qml + desktop/NewAccountCreateBasicAccount.qml + desktop/NewAccountCreateHDAccount.qml + desktop/NewAccountImportAccount.qml + desktop/NewAccountPane.qml + desktop/PaymentTweakingPanel.qml + desktop/ReceiveTransactionPane.qml + desktop/SendTransactionPane.qml + desktop/SettingsPane.qml + desktop/Transaction.qml + desktop/TransactionDetails.qml + desktop/TransactionInfoSmall.qml + desktop/WalletEncryption.qml + desktop/WalletEncryptionStatus.qml diff --git a/guis/desktop/WalletTransaction.qml b/guis/desktop/Transaction.qml similarity index 87% rename from guis/desktop/WalletTransaction.qml rename to guis/desktop/Transaction.qml index 4d5e3fe..c146b1c 100644 --- a/guis/desktop/WalletTransaction.qml +++ b/guis/desktop/Transaction.qml @@ -24,7 +24,7 @@ Rectangle { height: { var rc = mainLabel.height + 10 + date.height if (detailsPane.item != null) - rc += detailsPane.item.height + 10; + rc += detailsPane.item.height; return rc; } width: mainLabel.width + bitcoinAmountLabel.width + 30 @@ -78,14 +78,10 @@ Rectangle { function updateText() { if (txRoot.isRejected) text = qsTr("rejected") - var dat = model.date; - if (typeof dat === "undefined") - text = qsTr("unconfirmed") - else - text = Pay.formatDateTime(dat); + text = Pay.formatDateTime(model.date); } - opacity: txRoot.isRejected ? 1 : 0.5 - font.pointSize: mainLabel.font.pointSize * 0.8 + opacity: detailsPane.item === null ? 0.8 : 1; + font.pointSize: mainLabel.font.pointSize * 0.9 color: txRoot.isRejected ? (Pay.useDarkSkin ? "#ec2327" : "#b41214") : palette.windowText Component.onCompleted: updateText() @@ -134,7 +130,8 @@ Rectangle { Flowee.BitcoinAmountLabel { id: bitcoinAmountLabel - visible: Pay.activityShowsBch || !Pay.isMainChain + // visible: Pay.activityShowsBch || !Pay.isMainChain + fiatTimestamp: model.date value: { let inputs = model.fundsIn let outputs = model.fundsOut @@ -150,13 +147,9 @@ Rectangle { Flowee.Label { anchors.top: mainLabel.top anchors.right: parent.right - visible: bitcoinAmountLabel.visible === false + // visible: bitcoinAmountLabel.visible === false text: { - var timestamp = model.date; - if (timestamp === undefined) - var fiatPrice = Fiat.price; // todays price - else - fiatPrice = Fiat.historicalPrice(timestamp); + var fiatPrice = Fiat.historicalPrice(model.date); return Fiat.formattedPrice(bitcoinAmountLabel.value, fiatPrice) } color: { @@ -172,15 +165,14 @@ Rectangle { MouseArea { anchors.fill: parent - onClicked: detailsPane.source = (detailsPane.source == "") ? "./WalletTransactionDetails.qml" : "" + onClicked: detailsPane.source = (detailsPane.source == "") ? "./TransactionInfoSmall.qml" : "" } Loader { id: detailsPane anchors.bottom: parent.bottom anchors.bottomMargin: 6 - x: 4 // indent it - width: parent.width - 6 + width: parent.width onLoaded: item.infoObject = portfolio.current.txInfo(model.walletIndex, item) } diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml new file mode 100644 index 0000000..a6214b1 --- /dev/null +++ b/guis/desktop/TransactionDetails.qml @@ -0,0 +1,61 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee + +QQC2.ApplicationWindow { + id: root + visible: false + minimumWidth: 200 + minimumHeight: 200 + width: 400 + height: 500 + title: qsTr("Transaction Details") + modality: Qt.NonModal + flags: Qt.Dialog + + function openTab(walletIndex) { +console.log("xxx"); + tabs.push(portfolio.current.txInfo(walletIndex, root)) + } + property var tabs: [] + + Flowee.TabBar { + anchors.fill: parent + + Repeater { + model: root.tabs + delegate: Item { + property string title: "bla" + property string icon: "bla" + Rectangle { + width: 100 + height: 10 + color: "red" + } + } + } + } + + Flowee.Label { + y: 50 + text: tabs.length + } +} diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml new file mode 100644 index 0000000..a3bb618 --- /dev/null +++ b/guis/desktop/TransactionInfoSmall.qml @@ -0,0 +1,214 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee + +/* + * Mined: + * Sent: / Fees: + * Value now: + * Value then: + * TxId: + * More details + */ + +Item { + id: root + width: parent.width + height: column.height + 3 + + property QtObject infoObject: null + property int minedHeight: model.height // local cache + property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' + property bool isMoved: { + if (model.isCoinbase || model.isFused || model.fundsIn === 0) + return false; + var amount = model.fundsOut - model.fundsIn + return amount < 0 && amount > -2500 // then the diff is likely just fees. + } + property double amountBch: isMoved ? model.fundsIn + : (model.fundsOut - model.fundsIn) + + ColumnLayout { + id: column + width: parent.width + Flowee.Label { + id: rejectedLabel + property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' + text: { + if (isRejected) + return qsTr("Transaction is rejected") + if (typeof root.minedHeight < 1) + return qsTr("Processing") + return ""; + } + visible: text !== "" + color: isRejected ? mainWindow.errorRed : palette.windowText + } + + GridLayout { + width: parent.width + columns: 2 + + Flowee.Label { + visible: root.minedHeight > 0 + text: qsTr("Mined") + ":" + } + Flowee.Label { + Layout.fillWidth: true + visible: root.minedHeight > 0 + text: { + if (root.minedHeight <= 0) + return ""; + + var rc = Pay.formatBlockTime(root.minedHeight); + var confirmations = Pay.headerChainHeight - root.minedHeight + 1; + if (confirmations > 0 && confirmations < 20) + rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; + return rc; + } + } + + // value line + Flowee.Label { + id: paymentTypeLabel + visible: { + if (isMoved) + return false; + if (Pay.activityShowsBch) // avoid just duplicating data from the main delegate + return false; + return true; + } + text: { + if (model.isFused) + return qsTr("Fees") + ":"; + return mainLabel.text + ":" + } + } + Flowee.BitcoinAmountLabel { + visible: paymentTypeLabel.visible + colorizeValue: amountBch + (infoObject == null ? 0 : infoObject.fees) + value: Math.abs(colorizeValue) + fiatTimestamp: model.date + } + // txid line + Flowee.Label { + text: "TXID:" + } + Flowee.LabelWithClipboard { + menuText: qsTr("Copy transaction-ID") + text: model.txid + font.pixelSize: mainLabel.font.pixelSize * 0.9 + } + } + + Image { + sourceSize.width: 22 + sourceSize.height: 22 + smooth: true + visible: { + if (root.infoObject == null) + return false; + // visible if at least one output has a token. + var outputs = root.infoObject.knownOutputs; + for (let o of outputs) { + if (o !== null && o.hasCashToken) + return true; + } + return false; + } + source: visible ? "qrc:/CashTokens.svg" : "" + Flowee.Label { + x: 30 + text: qsTr("Holds a token") + anchors.verticalCenter: parent.verticalCenter + } + } + + GridLayout { + width: parent.width + columns: 2 + Flowee.Label { + visible: priceAtMining.visible + text: qsTr("Value now") + ":" + } + Flowee.Label { + visible: priceAtMining.visible + text: { + if (root.minedHeight <= 0) + return ""; + var fiatPriceNow = Fiat.price; + var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 + return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) + + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; + } + } + + // price at mining + // value in exchange gained + Flowee.Label { + id: priceAtMining + visible: { + if (root.minedHeight < 1) + return false; + if (model.isFused) + return false; + if (isMoved) + return false; + if (valueThenLabel.fiatPrice === 0) + return false; + if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later + return false; + return true; + } + text: qsTr("Value then") + ":" + } + Flowee.Label { + Layout.fillWidth: true + id: valueThenLabel + visible: priceAtMining.visible + // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false + property int fiatPrice: Fiat.historicalPriceAccurate(model.date) + text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) + } + } + } + Rectangle { + width: parent.width * 0.7 + height: 2 + color: palette.midlight + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + } + Rectangle { + color: "yellow" + opacity: 0.5 + anchors.right: parent.right + anchors.rightMargin: 20 + radius: 5 + width: 30 + height: 30 + MouseArea { + anchors.fill: parent + anchors.margins: -7 + onClicked: txDetailsWindow.openTab(model.walletIndex); + } + } +} diff --git a/guis/desktop/WalletTransactionDetails.qml b/guis/desktop/WalletTransactionDetails.qml deleted file mode 100644 index 07d5288..0000000 --- a/guis/desktop/WalletTransactionDetails.qml +++ /dev/null @@ -1,273 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import "../Flowee" as Flowee - -GridLayout { - id: root - - property QtObject infoObject: null - property var createDate: model.date - - columns: 2 - Flowee.LabelWithClipboard { - menuText: qsTr("Copy transaction-ID") - Layout.columnSpan: 2 - text: model.txid - font.pixelSize: 12 - } - Label { - text: qsTr("Status") + ":" - } - Flowee.LabelWithClipboard { - id: rightColumn - Layout.fillWidth: true - text: { - if (txRoot.isRejected) - return qsTr("rejected") - if (txRoot.minedHeight === -1) - return qsTr("unconfirmed") - var confirmations = Pay.headerChainHeight - txRoot.minedHeight + 1; - if (confirmations < 0) - return ""; - return qsTr("%1 confirmations (mined in block %2)", "", confirmations) - .arg(confirmations).arg(model.height); - } - clipboardText: model.height - menuText: qsTr("Copy block height") - } - - Label { - id: paymentTypeLabel - visible: { - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // this is our heuristic to mark it as 'moved' - return false; - return true; - } - text: mainLabel.text + ":" - } - Flowee.BitcoinAmountLabel { - visible: paymentTypeLabel.visible - value: model.fundsOut - model.fundsIn + infoObject.fees - fiatTimestamp: root.minedDate - } - Label { - id: feesLabel - visible: infoObject.createdByUs - text: qsTr("Fees") + ":" - } - Flowee.BitcoinAmountLabel { - visible: feesLabel.visible - value: infoObject.fees; - fiatTimestamp: root.minedDate - colorize: false - } - Label { - text: qsTr("Size") + ":" - } - Label { - text: qsTr("%1 bytes", "", infoObject.size).arg(infoObject.size) - } - Label { - text: qsTr("Inputs") + ":" - Layout.alignment: Qt.AlignTop - visible: infoObject.inputs.length > 0 - } - ColumnLayout { - Layout.fillWidth: true - Repeater { - model: infoObject.inputs - delegate: - Item { - width: rightColumn.width - height: modelData === null ? 6 : (5 + inAddress.height) - Flowee.ArrowPoint { - id: arrowPoint - anchors.bottom: parent.bottom - color: arrowLine.color - } - Rectangle { - id: arrowLine - color: modelData === null ? "grey" : "#c5c537" - anchors.left: arrowPoint.right - anchors.leftMargin: -2 // overlap the line and the arrow - anchors.bottom: arrowPoint.bottom - anchors.bottomMargin: 6 - anchors.right: parent.horizontalCenter - height: 1.6 - } - Label { - id: inIndex - text: index - visible: modelData !== null - anchors.left: arrowPoint.right - anchors.leftMargin: 6 - anchors.bottom: arrowLine.top - } - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: inAddress.visible - x: inAddress.x - 3 - y: inAddress.y -3 - height: inAddress.height + 6 - width: Math.min(inAddress.width, inAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { - id: inAddress - menuText: qsTr("Copy Address") - text: { - if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; - } - clipboardText: modelData === null ? "" : modelData.address - visible: modelData !== null - anchors.bottom: arrowLine.top - anchors.left: inIndex.right - anchors.leftMargin: 6 - anchors.right: amount.left - anchors.rightMargin: 10 - } - Flowee.BitcoinAmountLabel { - id: amount - visible: modelData !== null - value: modelData === null ? 0 : (-1 * modelData.value) - fiatTimestamp: root.minedDate - anchors.right: parent.right - anchors.bottom: arrowLine.top - } - } - } - } - Label { - text: qsTr("Outputs") + ":" - Layout.alignment: Qt.AlignTop - visible: infoObject.outputs.length > 0 - } - ColumnLayout { - Layout.fillWidth: true - Repeater { - model: infoObject.outputs - delegate: - Item { - width: rightColumn.width - height: { - if (modelData === null) - return 6; - - var h = 5 + outAddress.height; - if (modelData.hasCashToken) - h += 25; - return h; - } - - Image { - width: 20 - height: 20 - smooth: true - visible: modelData !== null && modelData.hasCashToken - source: visible ? "qrc:/CashTokens.svg" : "" - Label { - x: 30 - text: "A CashToken!" - anchors.verticalCenter: parent.verticalCenter - } - } - - Rectangle { - id: outArrowLine - /* - * There can be a nullptr, which means there is no info about this output. - * Then we have a bool 'forMe' which indicates the money goes to me or to - * someone else. Lets make the 'me' one the most visible one. - */ - color: modelData === null ? "grey" : (modelData.forMe ? "#c5c537" : "#67671d") - anchors.left: parent.horizontalCenter - anchors.bottom: outArrowPoint.bottom - anchors.bottomMargin: 6 - anchors.right: outArrowPoint.right - anchors.rightMargin: -1 // overlap the line and the arrow - height: 1.6 - } - Flowee.ArrowPoint { - id: outArrowPoint - anchors.bottom: parent.bottom - anchors.right: parent.right - color: outArrowLine.color - } - Label { - id: outIndex - visible: modelData !== null - text: index - anchors.left: parent.left - anchors.leftMargin: 6 - anchors.bottom: outArrowLine.top - } - Rectangle { - color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" - visible: modelData !== null && modelData.forMe - x: outAddress.x - 3 - y: outAddress.y -3 - height: outAddress.height + 6 - width: Math.min(outAddress.width, outAddress.contentWidth) + 6 - radius: height / 3 - opacity: 0.2 - } - Flowee.LabelWithClipboard { - id: outAddress - visible: modelData !== null - elide: Text.ElideMiddle - menuText: qsTr("Copy Address") - text: { - if (modelData === null) - return ""; - var cloaked = modelData.cloakedAddress - if (cloaked !== "") - return cloaked; - return modelData.address; - } - clipboardText: modelData === null ? "" : modelData.address - anchors.left: outIndex.right - anchors.leftMargin: 6 - anchors.rightMargin: 10 - anchors.right: outAmount.left - anchors.bottom: outArrowLine.top - } - Flowee.BitcoinAmountLabel { - id: outAmount - visible: modelData !== null - value: modelData === null ? 0 : modelData.value - fiatTimestamp: root.minedDate - colorize: modelData !== null && modelData.forMe - anchors.right: outArrowPoint.left - anchors.rightMargin: 2 - anchors.bottom: outArrowLine.top - anchors.bottomMargin: 6 - } - } - } - } -} diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 8a0fbbe..f3bf8d8 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -300,7 +300,7 @@ ApplicationWindow { id: activityView model: isLoading ? 0 : portfolio.current.transactions clip: true - delegate: WalletTransaction { width: activityView.width } + delegate: Transaction { width: activityView.width } anchors.top: activityHeader.bottom anchors.left: parent.left anchors.right: parent.right @@ -758,6 +758,25 @@ ApplicationWindow { } } } + Loader { + id: txDetailsWindow + function openTab(walletIndex) { + source = "./TransactionDetails.qml" + item.openTab(walletIndex); + } + onLoaded: { + ControlColors.applySkin(item); + txDetailsHandler.target = item; + item.show(); + } + Connections { + id: txDetailsHandler + function onVisibleChanged() { + if (!txDetailsWindow.item.visible) + txDetailsWindow.source = "" + } + } + } // new accounts pane, corresponding to the big green button Loader { id: newAccountPane diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 05be332..d933023 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -145,6 +145,81 @@ Page { } } + PageTitledBox { + id: fiatBox + property double amountBch: { + if (root.transaction === null) + return 0; + if (isMoved) + return root.transaction.fundsIn + return root.transaction.fundsOut - root.transaction.fundsIn; + } + // Is this transaction a 'move between addresses' tx. + // This is a heuristic and not available in the model, which is why its in the view. + property bool isMoved: { + if (root.transaction == null) + return false; + if (root.transaction.isCoinbase || root.transaction.isFused + || root.transaction.fundsIn === 0) + return false; + var amount = root.transaction.fundsOut - root.transaction.fundsIn + return amount < 0 && amount > -2500 // then the diff is likely just fees. + } + visible: { + if (root.transaction === null) + return false; + if (root.transaction.minedHeight < 1) + return false; + if (root.transaction.isFused) + return false; + if (isMoved) + return false; + if (valueThenLabel.fiatPrice === 0) + return false; + if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later + return false; + return true; + } + GridLayout { + columns: 2 + rowSpacing: 10 + width: parent.width + + Flowee.Label { + text: qsTr("Value now") + ":" + } + Flowee.Label { + text: { + if (root.transaction === null || root.transaction.minedHeight <= 0) + return ""; + var fiatPriceNow = Fiat.price; + var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 + return Fiat.formattedPrice(Math.abs(fiatBox.amountBch), fiatPriceNow) + + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; + } + } + + // price at mining + // value in exchange gained + Flowee.Label { + id: priceAtMining + text: qsTr("Value then") + ":" + } + Flowee.Label { + Layout.fillWidth: true + id: valueThenLabel + // when the backend does NOT get an 'accurate' (timewise) value, + // it returns zero. Which makes us set visibility to false + property int fiatPrice: { + if (root.transaction == null) + return 0; + Fiat.historicalPriceAccurate(root.transaction.date) + } + text: Fiat.formattedPrice(Math.abs(fiatBox.amountBch), fiatPrice) + } + } + } + PageTitledBox { Flowee.Label { text: root.infoObject == null ? "" : qsTr("Size: %1 bytes").arg(infoObject.size) @@ -166,6 +241,7 @@ Page { } Flowee.BitcoinAmountLabel { value: root.infoObject == null ? 0 : infoObject.fees + fiatTimestamp: model.date colorize: false } } diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 5d36d71..815f034 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -60,9 +60,9 @@ ColumnLayout { text: { if (root.minedHeight <= 0) return ""; - var rc = Pay.formatBlockTime(model.height); + var rc = Pay.formatBlockTime(root.minedHeight); var confirmations = Pay.headerChainHeight - root.minedHeight + 1; - if (confirmations > 0 && confirmations < 100) + if (confirmations > 0 && confirmations < 20) rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; return rc; } @@ -100,7 +100,7 @@ ColumnLayout { if (infoObject == null) return false; // visible if at least one output has a token. - var outputs = infoObject.outputs; + var outputs = infoObject.knownOutputs; for (let o of outputs) { if (o !== null && o.hasCashToken) return true; -- 2.54.0 From 2ccbb623779a92c5e3e98866035222b9c92a9cf9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 11 Feb 2024 21:05:38 +0100 Subject: [PATCH 0820/1428] Remove no longer needed versions General cleanup. Qt5 had the need to add versions to imports, this has (for very good reasons) been removed from Qt6. This removed the versions from the import lines. --- guis/Flowee/AddressInfoWidget.qml | 2 +- guis/Flowee/ArrowPoint.qml | 2 +- guis/Flowee/BitcoinAmountLabel.qml | 6 +++--- guis/Flowee/Button.qml | 4 ++-- guis/Flowee/CFIcon.qml | 4 ++-- guis/Flowee/CardTypeSelector.qml | 2 +- guis/Flowee/CheckBoxLabel.qml | 4 ++-- guis/Flowee/CloseIcon.qml | 2 +- guis/Flowee/ComboBox.qml | 4 ++-- guis/Flowee/Dialog.qml | 2 +- guis/Flowee/GroupBox.qml | 6 +++--- guis/Flowee/ListViewKeyHandler.qml | 2 +- guis/Flowee/MultilineTextField.qml | 6 +++--- guis/Flowee/PasswdDialog.qml | 2 +- guis/Flowee/ScrollThumb.qml | 4 ++-- guis/Flowee/TabBar.qml | 4 ++-- guis/Flowee/TextField.qml | 4 ++-- guis/Flowee/WarningLabel.qml | 4 ++-- 18 files changed, 32 insertions(+), 32 deletions(-) diff --git a/guis/Flowee/AddressInfoWidget.qml b/guis/Flowee/AddressInfoWidget.qml index a13e665..0cb05b0 100644 --- a/guis/Flowee/AddressInfoWidget.qml +++ b/guis/Flowee/AddressInfoWidget.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import Flowee.org.pay; +import Flowee.org.pay Item { id: addressInfo diff --git a/guis/Flowee/ArrowPoint.qml b/guis/Flowee/ArrowPoint.qml index 11d4450..855c8c3 100644 --- a/guis/Flowee/ArrowPoint.qml +++ b/guis/Flowee/ArrowPoint.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick Item { id: arrowPoint diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index b196351..92ddff4 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts /** * This class displays a Bitcoin value using the current settings diff --git a/guis/Flowee/Button.qml b/guis/Flowee/Button.qml index 3419c72..6156e3e 100644 --- a/guis/Flowee/Button.qml +++ b/guis/Flowee/Button.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 import "../ControlColors.js" as ControlColors // This is silly to be needed, but we want to introduce a visual difference diff --git a/guis/Flowee/CFIcon.qml b/guis/Flowee/CFIcon.qml index 3d2fd11..6314eab 100644 --- a/guis/Flowee/CFIcon.qml +++ b/guis/Flowee/CFIcon.qml @@ -1,5 +1,5 @@ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 Image { id: fusedIcon diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index b29e696..2524563 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick Item { id: root diff --git a/guis/Flowee/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml index ddad494..4e5e3eb 100644 --- a/guis/Flowee/CheckBoxLabel.qml +++ b/guis/Flowee/CheckBoxLabel.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 /** * This is a buddy that goes with a CheckBox component for when diff --git a/guis/Flowee/CloseIcon.qml b/guis/Flowee/CloseIcon.qml index 632260c..982f5c4 100644 --- a/guis/Flowee/CloseIcon.qml +++ b/guis/Flowee/CloseIcon.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick Item { id: root diff --git a/guis/Flowee/ComboBox.qml b/guis/Flowee/ComboBox.qml index 9e1ed8e..50fc569 100644 --- a/guis/Flowee/ComboBox.qml +++ b/guis/Flowee/ComboBox.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 QQC2.ComboBox { id: root diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index cba59d7..93649bb 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index 2377c49..3ca0dc8 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts QQC2.Control { id: root diff --git a/guis/Flowee/ListViewKeyHandler.qml b/guis/Flowee/ListViewKeyHandler.qml index 2078cae..6e6f9a1 100644 --- a/guis/Flowee/ListViewKeyHandler.qml +++ b/guis/Flowee/ListViewKeyHandler.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick /* * Typical usage is to add this in a place that is the 'root' of the focus. diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index 6241755..b664cde 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -14,9 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 -import QtQuick.Layouts 1.11 +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts /* * Annoyingly, there is a gap in the Qt default components. diff --git a/guis/Flowee/PasswdDialog.qml b/guis/Flowee/PasswdDialog.qml index ae21246..5798a26 100644 --- a/guis/Flowee/PasswdDialog.qml +++ b/guis/Flowee/PasswdDialog.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 +import QtQuick import "." as Flowee; Dialog { diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index e8d7df5..44e554e 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 QQC2.ScrollBar { id: root diff --git a/guis/Flowee/TabBar.qml b/guis/Flowee/TabBar.qml index dbb8e55..c0eb2f6 100644 --- a/guis/Flowee/TabBar.qml +++ b/guis/Flowee/TabBar.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 FocusScope { id: floweeTabBar diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 2b6b75e..4ca2115 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 /* * Sane defaults. diff --git a/guis/Flowee/WarningLabel.qml b/guis/Flowee/WarningLabel.qml index eec49dd..a2066c5 100644 --- a/guis/Flowee/WarningLabel.qml +++ b/guis/Flowee/WarningLabel.qml @@ -15,8 +15,8 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.11 -import QtQuick.Controls 2.11 as QQC2 +import QtQuick +import QtQuick.Controls as QQC2 Item { id: warningLabel -- 2.54.0 From c6e626ea018e4c9bf4989a1086b532a24b12fc69 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 15:43:04 +0100 Subject: [PATCH 0821/1428] Slight code cleanups --- guis/mobile/TransactionDetails.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index d933023..1b413cf 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -120,11 +120,11 @@ Page { var tx = root.transaction; if (tx == null) return ""; - if (root.transaction.height < 1) + let txHeight = tx.height; + if (txHeight < 1) return ""; - let txHeight = tx.height; - var answer = txHeight + "\n" + Pay.formatBlockTime(tx.height) + var answer = txHeight + "\n" + Pay.formatBlockTime(txHeight) let blockAge = Pay.chainHeight - txHeight + 1; answer += "\n"; answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); -- 2.54.0 From 8c0210b0fe265f509c42aab1c517827ed3b537dc Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 17:52:45 +0100 Subject: [PATCH 0822/1428] Add content to desktop TransactionDetails screen. --- guis/Flowee/AddressLabel.qml | 6 +- guis/Flowee/FiatTxInfo.qml | 94 +++++++++ guis/Flowee/TabBar.qml | 75 +++++--- guis/desktop/TransactionDetails.qml | 267 ++++++++++++++++++++++++-- guis/desktop/TransactionInfoSmall.qml | 75 ++------ guis/desktop/main.qml | 2 + guis/mobile/TransactionDetails.qml | 83 +------- guis/mobile/TransactionInfoSmall.qml | 50 +---- guis/widgets.qrc | 1 + src/TransactionInfo.cpp | 50 +++++ src/TransactionInfo.h | 44 +++-- src/Wallet.cpp | 19 +- src/Wallet.h | 6 + 13 files changed, 539 insertions(+), 233 deletions(-) create mode 100644 guis/Flowee/FiatTxInfo.qml diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml index 359be4b..5eccf4b 100644 --- a/guis/Flowee/AddressLabel.qml +++ b/guis/Flowee/AddressLabel.qml @@ -51,7 +51,11 @@ QQC2.Control { } return root.txInfo.address; } - font.pixelSize: root.font.pixelSize * 0.9 + font.pixelSize: { + if (text == root.txInfo.cloakedAddress) + return root.font.pixelSize; + return root.font.pixelSize * 0.9; + } MouseArea { anchors.fill: parent diff --git a/guis/Flowee/FiatTxInfo.qml b/guis/Flowee/FiatTxInfo.qml new file mode 100644 index 0000000..ed674de --- /dev/null +++ b/guis/Flowee/FiatTxInfo.qml @@ -0,0 +1,94 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee + +GridLayout { + id: root + columns: 2 + + required property QtObject txInfo + // Is this transaction a 'move between addresses' tx. + // This is a heuristic and not available in the model, which is why its in the view. + property bool isMoved: { + if (txInfo === null) + return false; + if (txInfo.isCoinbase || txInfo.isFused + || txInfo.fundsIn === 0) + return false; + var amount = txInfo.fundsOut - txInfo.fundsIn + return amount < 0 && amount > -2500 // then the diff is likely just fees. + } + property double amountBch: { + if (txInfo === null) + return 0; + if (isMoved) + return txInfo.fundsIn + return txInfo.fundsOut - txInfo.fundsIn; + } + + visible: { + if (txInfo === null) + return false; + if (txInfo.minedHeight < 1) + return false; + if (txInfo.isFused) + return false; + if (isMoved) + return false; + if (valueThenLabel.fiatPrice === 0) + return false; + if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later + return false; + return true; + } + + Flowee.Label { + text: qsTr("Value now") + ":" + } + Flowee.Label { + text: { + if (txInfo === null || txInfo.minedHeight <= 0) + return ""; + var fiatPriceNow = Fiat.price; + var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 + return Fiat.formattedPrice(Math.abs(root.amountBch), fiatPriceNow) + + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; + } + } + + // price at mining + // value in exchange gained + Flowee.Label { + id: priceAtMining + text: qsTr("Value then") + ":" + } + Flowee.Label { + Layout.fillWidth: true + id: valueThenLabel + // when the backend does NOT get an 'accurate' (timewise) value, + // it returns zero. Which makes us set visibility to false + property int fiatPrice: { + if (txInfo == null) + return 0; + Fiat.historicalPriceAccurate(txInfo.date) + } + text: Fiat.formattedPrice(Math.abs(root.amountBch), fiatPrice) + } +} diff --git a/guis/Flowee/TabBar.qml b/guis/Flowee/TabBar.qml index c0eb2f6..aa97f05 100644 --- a/guis/Flowee/TabBar.qml +++ b/guis/Flowee/TabBar.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,57 +30,71 @@ FocusScope { function setOpacities() { var visibleChild = null; - for (let i = 0; i < stack.children.length; ++i) { + for (let i = 0; i < header.tabs.length; ++i) { let on = i === currentIndex; - let child = stack.children[i]; + let child = header.tabs[i]; child.visible = on; if (on) visibleChild = child; } - // try to make sure the current tab gets keyboard focus properly. // We do some extra work in case the tab itself is a loader. - forceActiveFocus(); - visibleChild.focus = true - if (visibleChild instanceof Loader) { - var child = visibleChild.item; - if (child != null) - child.focus = true; + if (visibleChild) { + forceActiveFocus(); + visibleChild.focus = true + if (visibleChild instanceof Loader) { + var child = visibleChild.item; + if (child !== null) + child.focus = true; + } } } Row { id: header - + property var tabs: [] + onTabsChanged: floweeTabBar.setOpacities(); Repeater { - model: stack.children.length + model: header.tabs delegate: Item { height: payTabButtonText.height + 10 - width: floweeTabBar.width / stack.children.length; + width: floweeTabBar.width / header.tabs.length - Row { + Item { // text + icon, centered together. + width: { + var w = payTabButtonText.implicitWidth; + if (tabIcon.visible) + w += 6 + tabIcon.width; + if (w > parent.width) // limit text to available space + w = parent.width + return w; + } height: parent.height anchors.horizontalCenter: parent.horizontalCenter - spacing: 6 + Image { + id: tabIcon visible: source != "" - source: stack.children[index].icon + source: enabled ? header.tabs[index].icon : "" anchors.bottom: payTabButtonText.baseline anchors.bottomMargin: -2 - width: height height: payTabButtonText.height + width: height } Label { id: payTabButtonText - anchors.verticalCenter: parent.verticalCenter + y: 6 + x: tabIcon.visible ? tabIcon.width + 6 : 0 + width: parent.width - x; + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter font.bold: true color: { - var child = stack.children[index]; - if (!child.enabled) + if (!header.tabs[index].enabled) return "#888" return "white" } - text: stack.children[index].title + text: enabled ? header.tabs[index].title: "" } } @@ -95,8 +109,7 @@ FocusScope { color: { if (index === floweeTabBar.currentIndex) return mainWindow.floweeGreen - var child = stack.children[index]; - if (!child.enabled) + if (!header.tabs[index].enabled) return "#888" if (Pay.useDarkSkin) { if (hover) @@ -115,9 +128,8 @@ FocusScope { onEntered: highlight.hover = true onExited: highlight.hover = false onClicked: { - let child = stack.children[index]; // respect 'disabled' bool and don't change to the tab - if (child.enabled) + if (header.tabs[index].enabled) floweeTabBar.currentIndex = index } } @@ -127,6 +139,19 @@ FocusScope { Item { id: stack + onChildrenChanged: { + // copy the children, but skip Items that are not actually + // content. Like Repeaters + var copyOfChildren = []; + for (let i = 0; i < children.length; ++i) { + var item = children[i]; + if (item instanceof Repeater) + continue; + copyOfChildren.push(item); + } + header.tabs = copyOfChildren; + } + width: floweeTabBar.width anchors.top: header.bottom; anchors.bottom: floweeTabBar.bottom } diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index a6214b1..d46023f 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee +import Flowee.org.pay QQC2.ApplicationWindow { id: root @@ -29,33 +30,269 @@ QQC2.ApplicationWindow { height: 500 title: qsTr("Transaction Details") modality: Qt.NonModal - flags: Qt.Dialog + flags: Qt.Widget function openTab(walletIndex) { -console.log("xxx"); - tabs.push(portfolio.current.txInfo(walletIndex, root)) + transactions.push(portfolio.current.txInfo(walletIndex, root)) + tabList.model = transactions.length + // open the newly created tab + tabbar.currentIndex = transactions.length - 1 } - property var tabs: [] + property var transactions: [] Flowee.TabBar { + id: tabbar anchors.fill: parent Repeater { - model: root.tabs - delegate: Item { - property string title: "bla" - property string icon: "bla" + id: tabList + delegate: Flickable { + id: delegateRoot + anchors.fill: parent + contentHeight: content.height + 20 + clip: true + + property QtObject txInfo: root.transactions[index]; + // properties for the tabbar + property string title: txInfo.txid + property string icon: "" + Rectangle { - width: 100 - height: 10 + width: 20 + height: 20 color: "red" + anchors.right: parent.right + anchors.rightMargin: 20 + MouseArea { + anchors.fill: parent + onClicked: { + root.transactions.splice(index, 1); + if (root.transactions.length == 0) { + root.close(); + } + else { + tabbar.currentIndex = Math.max(0, index - 1) + tabList.model = transactions.length + } + } + } + } + + Column { + id: content + width: parent.width - 20 + x: 10 + y: 10 + spacing: 10 + + GridLayout { + width: parent.width + columns: 2 + + Flowee.Label { + text: qsTr("First Seen") + ":" + Layout.alignment: Qt.AlignRight + } + Flowee.Label { + Layout.fillWidth: true + text: Qt.formatDateTime(txInfo.date) + } + + Flowee.Label { + visible: minedDateLabel.visible + Layout.alignment: Qt.AlignTop | Qt.AlignRight + text: { + var h = txInfo.height; + if (h === -2) + var s = qsTr("Rejected") + if (h === -1) + s = qsTr("Unconfirmed") + else + s = qsTr("Mined at"); + return s + ":"; + } + } + Flowee.Label { + id: minedDateLabel + visible: text !== "" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.fillWidth: true + text: { + let txHeight = txInfo.minedHeight; + if (txHeight < 1) + return ""; + + var answer = txHeight + "\n" + Pay.formatBlockTime(txHeight) + let blockAge = Pay.chainHeight - txHeight + 1; + answer += "\n"; + answer += qsTr("%1 blocks ago", "", blockAge).arg(blockAge); + return answer; + } + } + + Flowee.Label { + Layout.alignment: Qt.AlignRight + text: qsTr("Comment") + ":" + } + Flowee.TextField { + id: editableLabel + Layout.fillWidth: true + text: txInfo.userComment + onTextChanged: txInfo.userComment = text + } + Flowee.FiatTxInfo { + txInfo: delegateRoot.txInfo + Layout.fillWidth: true + Layout.columnSpan: 2 + } + // We can't calculate the fees of just any transaction, + // only for the ones this account created. + Flowee.Label { + id: feesSection + text: qsTr("Fees paid") + ":" + visible: txInfo.createdByUs + Layout.alignment: Qt.AlignRight + } + Flowee.Label { + visible: feesSection.visible + text: qsTr("%1 Satoshi / 1000 bytes") + .arg((txInfo.fees * 1000 / txInfo.size).toFixed(0)) + } + Item { + visible: feesSection.visible + width: 1; height: 1 + } + Flowee.BitcoinAmountLabel { + visible: feesSection.visible + value: txInfo.fees + fiatTimestamp: txInfo.date + colorize: false + } + + Flowee.Label { + text: qsTr("Size") + ":" + Layout.alignment: Qt.AlignRight + } + Flowee.Label { + text: qsTr("%1 bytes").arg(txInfo.size) + } + Item { + width: 1; height: 1 + visible: txInfo.isCoinbase + } + Flowee.Label { + text: qsTr("Is Coinbase") + visible: txInfo.isCoinbase + } + + // txid line + Flowee.Label { + text: "TXID:" + Layout.alignment: Qt.AlignTop | Qt.AlignRight + } + Flowee.LabelWithClipboard { + menuText: qsTr("Copy transaction-ID") + text: txInfo.txid + font.pixelSize: root.font.pixelSize * 0.9 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.fillWidth: true + } + } + + Flowee.Label { + id: inputsList + text: { + if (txInfo == null) + return ""; + if (txInfo.isFused) + return qsTr("Fused from my addresses"); + if (txInfo.createdByUs) + return qsTr("Sent from my addresses"); + if (txInfo.isFused) + return qsTr("Sent from addresses"); + return ""; + } + visible: title !== "" + font.bold: true + } + + Repeater { + model: inputsList.visible ? txInfo.knownInputs : 0 + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width + height: { + if (modelData === null) + return 6; + if (inAddress.implicitWidth + 10 + amount.implicitWidth < content.width) + return inAddress.height + 10; + return inAddress.height + amount.height + 16; + } + Flowee.CFIcon { + id: fusedIcon + visible: modelData.fromFused + } + Flowee.AddressLabel { + id: inAddress + txInfo: modelData + x: fusedIcon.visible ? fusedIcon.width + 6 : 0 + width: Math.min(implicitWidth, parent.width - (fusedIcon.visible ? fusedIcon.width: 0)) + } + Flowee.BitcoinAmountLabel { + id: amount + visible: modelData !== null + value: modelData === null ? 0 : (-1 * modelData.value) + fiatTimestamp: txInfo.date + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + } + } + } + + Flowee.Label { + text: { + if (txInfo == null) + return ""; + if (txInfo.isFused) + return qsTr("Fused into my addresses"); + if (txInfo.createdByUs) + return qsTr("Received at addresses"); + return qsTr("Received at my addresses"); + } + font.bold: true + } + + Repeater { + model: txInfo.knownOutputs + delegate: Item { + Layout.alignment: Qt.AlignRight + width: content.width + height: { + if (outAddress.implicitWidth + 10 + outAmount.implicitWidth < width) + return outAmount.height + 10; + return outAddress.height + outAmount.height + 16; + } + Flowee.AddressLabel { + id: outAddress + txInfo: modelData + highlight: modelData.forMe + width: Math.min(implicitWidth, parent.width) + } + + Flowee.BitcoinAmountLabel { + id: outAmount + value: modelData.value + fiatTimestamp: txInfo.date + colorize: modelData.forMe + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + } + } + } } } } } - - Flowee.Label { - y: 50 - text: tabs.length - } } diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index a3bb618..38aea16 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -20,15 +20,6 @@ import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee -/* - * Mined: - * Sent: / Fees: - * Value now: - * Value then: - * TxId: - * More details - */ - Item { id: root width: parent.width @@ -116,6 +107,7 @@ Item { menuText: qsTr("Copy transaction-ID") text: model.txid font.pixelSize: mainLabel.font.pixelSize * 0.9 + Layout.fillWidth: true } } @@ -142,55 +134,12 @@ Item { } } - GridLayout { + Flowee.FiatTxInfo { + txInfo: infoObject width: parent.width - columns: 2 - Flowee.Label { - visible: priceAtMining.visible - text: qsTr("Value now") + ":" - } - Flowee.Label { - visible: priceAtMining.visible - text: { - if (root.minedHeight <= 0) - return ""; - var fiatPriceNow = Fiat.price; - var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) - + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; - } - } - - // price at mining - // value in exchange gained - Flowee.Label { - id: priceAtMining - visible: { - if (root.minedHeight < 1) - return false; - if (model.isFused) - return false; - if (isMoved) - return false; - if (valueThenLabel.fiatPrice === 0) - return false; - if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later - return false; - return true; - } - text: qsTr("Value then") + ":" - } - Flowee.Label { - Layout.fillWidth: true - id: valueThenLabel - visible: priceAtMining.visible - // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false - property int fiatPrice: Fiat.historicalPriceAccurate(model.date) - text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) - } } } - Rectangle { + Rectangle { width: parent.width * 0.7 height: 2 color: palette.midlight @@ -198,7 +147,21 @@ Item { anchors.bottom: parent.bottom } Rectangle { - color: "yellow" + color: "red" // open in explorer + opacity: 0.5 + anchors.right: parent.right + anchors.rightMargin: 60 + radius: 5 + width: 30 + height: 30 + MouseArea { + anchors.fill: parent + anchors.margins: -7 + onClicked: Pay.openInExplorer(model.txid); + } + } + Rectangle { + color: "yellow" // open details opacity: 0.5 anchors.right: parent.right anchors.rightMargin: 20 diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index f3bf8d8..2495446 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -762,6 +762,8 @@ ApplicationWindow { id: txDetailsWindow function openTab(walletIndex) { source = "./TransactionDetails.qml" + if (item.visible) + item.requestActivate(); item.openTab(walletIndex); } onLoaded: { diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 1b413cf..3db49fd 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -146,77 +146,9 @@ Page { } PageTitledBox { - id: fiatBox - property double amountBch: { - if (root.transaction === null) - return 0; - if (isMoved) - return root.transaction.fundsIn - return root.transaction.fundsOut - root.transaction.fundsIn; - } - // Is this transaction a 'move between addresses' tx. - // This is a heuristic and not available in the model, which is why its in the view. - property bool isMoved: { - if (root.transaction == null) - return false; - if (root.transaction.isCoinbase || root.transaction.isFused - || root.transaction.fundsIn === 0) - return false; - var amount = root.transaction.fundsOut - root.transaction.fundsIn - return amount < 0 && amount > -2500 // then the diff is likely just fees. - } - visible: { - if (root.transaction === null) - return false; - if (root.transaction.minedHeight < 1) - return false; - if (root.transaction.isFused) - return false; - if (isMoved) - return false; - if (valueThenLabel.fiatPrice === 0) - return false; - if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later - return false; - return true; - } - GridLayout { - columns: 2 - rowSpacing: 10 + Flowee.FiatTxInfo { + txInfo: root.transaction width: parent.width - - Flowee.Label { - text: qsTr("Value now") + ":" - } - Flowee.Label { - text: { - if (root.transaction === null || root.transaction.minedHeight <= 0) - return ""; - var fiatPriceNow = Fiat.price; - var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - return Fiat.formattedPrice(Math.abs(fiatBox.amountBch), fiatPriceNow) - + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; - } - } - - // price at mining - // value in exchange gained - Flowee.Label { - id: priceAtMining - text: qsTr("Value then") + ":" - } - Flowee.Label { - Layout.fillWidth: true - id: valueThenLabel - // when the backend does NOT get an 'accurate' (timewise) value, - // it returns zero. Which makes us set visibility to false - property int fiatPrice: { - if (root.transaction == null) - return 0; - Fiat.historicalPriceAccurate(root.transaction.date) - } - text: Fiat.formattedPrice(Math.abs(fiatBox.amountBch), fiatPrice) - } } } @@ -241,7 +173,7 @@ Page { } Flowee.BitcoinAmountLabel { value: root.infoObject == null ? 0 : infoObject.fees - fiatTimestamp: model.date + fiatTimestamp: root.transaction.date colorize: false } } @@ -249,7 +181,7 @@ Page { PageTitledBox { spacing: 10 title: { - if (root.infoObject == null) + if (infoObject == null) return ""; if (infoObject.isFused) return qsTr("Fused from my addresses"); @@ -272,8 +204,6 @@ Page { Layout.alignment: Qt.AlignRight width: content.width height: { - if (modelData === null) - return 0; if (inAddress.implicitWidth + 10 + amount.implicitWidth < content.width) return inAddress.height + 10; return inAddress.height + amount.height + 16; @@ -290,8 +220,7 @@ Page { } Flowee.BitcoinAmountLabel { id: amount - visible: modelData !== null - value: modelData === null ? 0 : (-1 * modelData.value) + value: -1 * modelData.value fiatTimestamp: root.transaction.date anchors.right: parent.right anchors.bottom: parent.bottom @@ -305,7 +234,7 @@ Page { PageTitledBox { spacing: 10 title: { - if (root.infoObject == null) + if (infoObject == null) return ""; if (infoObject.isFused) return qsTr("Fused into my addresses"); diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 815f034..4f3af0f 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -126,54 +126,10 @@ ColumnLayout { text: infoObject == null ? "" : infoObject.receiver font.pixelSize: paymentTypeLabel.font.pixelSize * 0.8 } - GridLayout { - columns: 2 - rowSpacing: 10 + + Flowee.FiatTxInfo { + txInfo: infoObject width: parent.width - - Flowee.Label { - visible: priceAtMining.visible - text: qsTr("Value now") + ":" - } - Flowee.Label { - visible: priceAtMining.visible - text: { - if (root.minedHeight <= 0) - return ""; - var fiatPriceNow = Fiat.price; - var gained = (fiatPriceNow - valueThenLabel.fiatPrice) / valueThenLabel.fiatPrice * 100 - return Fiat.formattedPrice(Math.abs(amountBch), fiatPriceNow) - + " (" + (gained >= 0 ? "↑" : "↓") + Math.abs(gained).toFixed(2) + "%)"; - } - } - - // price at mining - // value in exchange gained - Flowee.Label { - id: priceAtMining - visible: { - if (root.minedHeight < 1) - return false; - if (model.isFused) - return false; - if (isMoved) - return false; - if (valueThenLabel.fiatPrice === 0) - return false; - if (Math.abs(amountBch) < 10000) // hardcode 10k sats here, may need adjustment later - return false; - return true; - } - text: qsTr("Value then") + ":" - } - Flowee.Label { - Layout.fillWidth: true - id: valueThenLabel - visible: priceAtMining.visible - // when the backend does NOT get an 'accurate' (timewise) value, it returns zero. Which makes us set visibility to false - property int fiatPrice: Fiat.historicalPriceAccurate(model.date) - text: Fiat.formattedPrice(Math.abs(amountBch), fiatPrice) - } } TextButton { diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 63d1b43..b98d595 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -47,5 +47,6 @@ Flowee/HamburgerMenu.qml Flowee/QRWidget.qml Flowee/AddressInfoWidget.qml + Flowee/FiatTxInfo.qml diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 7b06d45..31abb74 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "FloweePay.h" #include "TransactionInfo.h" #include "Wallet.h" @@ -119,6 +120,55 @@ bool TransactionInfo::createdByUs() const return m_createdByUs; } +double TransactionInfo::fundsIn() const +{ + double answer = 0; + for (auto i : m_inputs) { + if (i) answer += i->value(); + } + return answer; +} + +double TransactionInfo::fundsOut() const +{ + double answer = 0; + for (auto o : m_outputs) { + if (o && o->forMe()) answer += o->value(); + } + return answer; +} + +QDateTime TransactionInfo::transactionDate() const +{ + if (m_wallet == nullptr) + return QDateTime::currentDateTimeUtc(); + auto time = m_wallet->transactionTime(m_walletIndex); + if (time == 0) { + int blockHeight = m_wallet->transactionMined(m_walletIndex); + try { + time = FloweePay::instance()->p2pNet()->blockchain().block(blockHeight).nTime; + } catch (const std::exception &e) { // a blockheight we don't have. + return QDateTime::currentDateTimeUtc(); + } + } + + return QDateTime::fromSecsSinceEpoch(time); +} + +int TransactionInfo::minedHeight() const +{ + if (m_wallet == nullptr) + return 0; + return m_wallet->transactionMined(m_walletIndex); +} + +QString TransactionInfo::txid() const +{ + if (m_wallet == nullptr) + return QString(); + return QString::fromStdString(m_wallet->txid(m_walletIndex).ToString()); +} + QString TransactionInfo::receiver() const { QString answer; diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index a31df31..96eaed3 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -21,6 +21,7 @@ #include #include #include +#include class Wallet; @@ -100,20 +101,26 @@ class TransactionInfo : public QObject * Return the amount of fees paid for this transaction. * This returns zero if we didn't create the transaction. */ - Q_PROPERTY(double fees READ fees CONSTANT) - Q_PROPERTY(QList inputs READ inputs CONSTANT) - Q_PROPERTY(QList outputs READ outputs CONSTANT) - Q_PROPERTY(QList knownInputs READ knownInputs CONSTANT) - Q_PROPERTY(QList knownOutputs READ knownOutputs CONSTANT) - Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY commentChanged) + Q_PROPERTY(double fees READ fees CONSTANT FINAL) + Q_PROPERTY(QList inputs READ inputs CONSTANT FINAL) + Q_PROPERTY(QList outputs READ outputs CONSTANT FINAL) + Q_PROPERTY(QList knownInputs READ knownInputs CONSTANT FINAL) + Q_PROPERTY(QList knownOutputs READ knownOutputs CONSTANT FINAL) + Q_PROPERTY(QString userComment READ userComment WRITE setUserComment NOTIFY commentChanged FINAL) /** * The recipient of the transaction. Typically just an address. */ - Q_PROPERTY(QString receiver READ receiver CONSTANT) - Q_PROPERTY(bool isCoinbase READ isCoinbase CONSTANT) - Q_PROPERTY(bool isFused READ isFused CONSTANT) - Q_PROPERTY(bool createdByUs READ createdByUs CONSTANT) - Q_PROPERTY(bool commentEditable READ commentEditable CONSTANT) + Q_PROPERTY(QString receiver READ receiver CONSTANT FINAL) + Q_PROPERTY(bool isCoinbase READ isCoinbase CONSTANT FINAL) + Q_PROPERTY(bool isFused READ isFused CONSTANT FINAL) + Q_PROPERTY(bool createdByUs READ createdByUs CONSTANT FINAL) + Q_PROPERTY(bool commentEditable READ commentEditable CONSTANT FINAL) + + Q_PROPERTY(QString txid READ txid CONSTANT FINAL) + Q_PROPERTY(int minedHeight READ minedHeight NOTIFY blockHeightChanged FINAL) + Q_PROPERTY(QDateTime date READ transactionDate CONSTANT FINAL) + Q_PROPERTY(double fundsIn READ fundsIn CONSTANT FINAL) + Q_PROPERTY(double fundsOut READ fundsOut CONSTANT FINAL) public: explicit TransactionInfo(QObject *parent = nullptr); @@ -130,8 +137,22 @@ public: bool isFused() const; bool createdByUs() const; + /// the combined amount of sats spent by us in the inputs + double fundsIn() const; + /// the combined amount of sats received by us in the outputs + double fundsOut() const; + + /// the time we first saw this transaction. + QDateTime transactionDate() const; + + /// the block-height this transaction got mined in. + /// it will be -1 for unconfirmed, or -2 for rejected. + int minedHeight() const; + /// The 'receiver' address of this transaction. QString receiver() const; + /// the transactionId, as string. + QString txid() const; /** * The API allows the user to set a 'user comment'. Which needs @@ -152,6 +173,7 @@ public: signals: void commentChanged(); + void blockHeightChanged(); private: Wallet *m_wallet = nullptr; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 1935874..35320e3 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -722,7 +722,6 @@ void Wallet::setTransactionComment(int txIndex, const QString &comment) { QMutexLocker locker(&m_lock); auto wtxIter = m_walletTransactions.find(txIndex); - assert(wtxIter != m_walletTransactions.end()); if (wtxIter == m_walletTransactions.end()) return; auto &wtx = wtxIter->second; @@ -1248,6 +1247,24 @@ qint64 Wallet::utxoOutputValue(OutputRef ref) const return iter->second; } +int64_t Wallet::transactionTime(int txIndex) const +{ + QMutexLocker locker(&m_lock); + auto iter = m_walletTransactions.find(txIndex); + if (m_walletTransactions.end() == iter) + throw std::runtime_error("Invalid tx-index"); + return iter->second.transactionTime; +} + +int Wallet::transactionMined(int txIndex) const +{ + QMutexLocker locker(&m_lock); + auto iter = m_walletTransactions.find(txIndex); + if (m_walletTransactions.end() == iter) + throw std::runtime_error("Invalid tx-index"); + return iter->second.minedBlockHeight; +} + Wallet::PrivKeyData Wallet::unlockKey(Wallet::OutputRef ref) const { QMutexLocker locker(&m_lock); diff --git a/src/Wallet.h b/src/Wallet.h index b77a8d9..100d315 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -163,6 +163,12 @@ public: /// Fetch UTXO value (in sats) qint64 utxoOutputValue(OutputRef ref) const; + /// return the time first seen for a transaction. + int64_t transactionTime(int txIndex) const; + + /// return the blockheight a transaction is mined at. + int transactionMined(int txIndex) const; + struct PrivKeyData { int privKeyId = 0; PrivateKey key; -- 2.54.0 From b8d650b9b3d0a1f28530b08b439b185cd48c7f26 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 19:19:01 +0100 Subject: [PATCH 0823/1428] Add UI scaling to desktop --- guis/desktop/SettingsPane.qml | 50 ++++++++++++++++++++++++++++++++--- guis/desktop/main.qml | 26 ++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index fd1373d..d9211ff 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ Pane { columns: 3 rowSpacing: 10 columnSpacing: 6 + width: parent.width Label { text: qsTr("Unit") + ":" @@ -53,8 +54,7 @@ Pane { Rectangle { color: "#00000000" radius: 6 - border.color: palette.button - border.width: 0.8 + Layout.fillWidth: true implicitHeight: units.height + 10 implicitWidth: units.width + 10 @@ -101,6 +101,7 @@ Pane { } Flowee.CheckBoxLabel { Layout.columnSpan: 2 + Layout.fillWidth: true buddy: showBchOnActivity text: qsTr("Show Bitcoin Cash value on Activity page") visible: Pay.isMainChain @@ -145,6 +146,48 @@ Pane { toolTipText: qsTr("Hides private wallets while enabled") } + Flowee.Label { + id: fontSizingLabel + Layout.alignment: Qt.AlignRight | Qt.AlignTop + text: qsTr("Font sizing") + ":" + } + Item { + Layout.fillWidth: true + Layout.columnSpan: 2 + height: 30 + fontSizingLabel.height * 0.75 + id: fontSizing + property double buttonWidth: width / 6 + Repeater { + model: 6 + delegate: Item { + width: fontSizing.buttonWidth + anchors.bottom: parent.bottom + height: 30 + x: width * index + property int target: index * 25 + 75 + + Rectangle { + width: parent.width - 5 + x: 2.5 + height: 5 + color: Pay.fontScaling === target ? palette.highlight : palette.button + } + + Flowee.Label { + font.pixelSize: 15 + text: "" + target + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + MouseArea { + anchors.fill: parent + anchors.topMargin: -30 + onClicked: Pay.fontScaling = target + } + } + } + } + Label { text: qsTr("Version") + ":" Layout.alignment: Qt.AlignRight @@ -162,6 +205,7 @@ Pane { Layout.columnSpan: 2 } Label { + Layout.alignment: Qt.AlignRight text: qsTr("Synchronization") + ":" } diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 2495446..3e3fa1a 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -38,6 +38,30 @@ ApplicationWindow { property bool isLoading: typeof portfolio === "undefined"; + Component.onCompleted: updateFontSize(mainWindow); + function updateFontSize(window) { + // 75% = > 14.25, 100% => 19, 200% => 28 + window.font.pixelSize = 17 + (11 * (Pay.fontScaling-100) / 100) + } + Connections { + target: Pay + function onFontScalingChanged() { + updateFontSize(mainWindow); + if (txDetailsWindow.loaded()) + updateFontSize(txDetailsWindow.item); + if (netView.loaded()) + updateFontSize(netView.item); + } + function onUseDarkSkinChanged() { + ControlColors.applySkin(mainWindow); + if (txDetailsWindow.loaded()) + ControlColors.applySkin(txDetailsWindow.item); + if (netView.loaded()) + ControlColors.applySkin(netView.item); + } + } + + onIsLoadingChanged: { if (!isLoading) { // delay loading to avoid errors due to not having a portfolio @@ -749,6 +773,7 @@ ApplicationWindow { onLoaded: { ControlColors.applySkin(item) netViewHandler.target = item + item.font = mainWindow.font } Connections { id: netViewHandler @@ -769,6 +794,7 @@ ApplicationWindow { onLoaded: { ControlColors.applySkin(item); txDetailsHandler.target = item; + item.font = mainWindow.font item.show(); } Connections { -- 2.54.0 From 1ec24150c1f8ff63a3b895e2c6c85c0a6aeaf8d7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 22:21:19 +0100 Subject: [PATCH 0824/1428] Add icons and colors to the placeholder buttons. --- guis/desktop.qrc | 2 ++ guis/desktop/TransactionDetails.qml | 38 +++++++++++++++----------- guis/desktop/TransactionInfoSmall.qml | 38 ++++++++++---------------- guis/desktop/images/infoIcon-light.svg | 5 ++++ guis/desktop/images/infoIcon.svg | 5 ++++ 5 files changed, 48 insertions(+), 40 deletions(-) create mode 100644 guis/desktop/images/infoIcon-light.svg create mode 100644 guis/desktop/images/infoIcon.svg diff --git a/guis/desktop.qrc b/guis/desktop.qrc index bd23f9e..55ca1c0 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -12,6 +12,8 @@ desktop/images/activityIcon.png desktop/images/activityIcon-light.png desktop/images/edit-delete.svg + desktop/images/infoIcon.svg + desktop/images/infoIcon-light.svg desktop/defaults.ini desktop/ConfigItem.qml ControlColors.js diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index d46023f..19322fa 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -40,6 +40,17 @@ QQC2.ApplicationWindow { } property var transactions: [] + Rectangle { + width: parent.width + height: tabbar.headerHeight + color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue + Rectangle { + anchors.fill: parent + opacity: 0.2 + color: Pay.useDarkSkin ? "black" : "white" + } + } + Flowee.TabBar { id: tabbar anchors.fill: parent @@ -57,23 +68,18 @@ QQC2.ApplicationWindow { property string title: txInfo.txid property string icon: "" - Rectangle { - width: 20 - height: 20 - color: "red" + Flowee.CloseIcon { anchors.right: parent.right - anchors.rightMargin: 20 - MouseArea { - anchors.fill: parent - onClicked: { - root.transactions.splice(index, 1); - if (root.transactions.length == 0) { - root.close(); - } - else { - tabbar.currentIndex = Math.max(0, index - 1) - tabList.model = transactions.length - } + y: 6 + anchors.rightMargin: 6 + onClicked: { + root.transactions.splice(index, 1); + if (root.transactions.length == 0) { + root.close(); + } + else { + tabbar.currentIndex = Math.max(0, index - 1) + tabList.model = transactions.length } } } diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index 38aea16..a9cd352 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -146,32 +146,22 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom } - Rectangle { - color: "red" // open in explorer - opacity: 0.5 - anchors.right: parent.right - anchors.rightMargin: 60 - radius: 5 - width: 30 - height: 30 - MouseArea { - anchors.fill: parent - anchors.margins: -7 - onClicked: Pay.openInExplorer(model.txid); - } + Flowee.ImageButton { + source: "qrc:/internet.svg" + smooth: true + iconSize: 25 + onClicked: Pay.openInExplorer(model.txid); + responseText: qsTr("Opening Website") + anchors.right: moreInfo.left + anchors.rightMargin: 10 } - Rectangle { - color: "yellow" // open details - opacity: 0.5 + Flowee.ImageButton { + id: moreInfo + smooth: true + source: "qrc:/infoIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + iconSize: 25 anchors.right: parent.right anchors.rightMargin: 20 - radius: 5 - width: 30 - height: 30 - MouseArea { - anchors.fill: parent - anchors.margins: -7 - onClicked: txDetailsWindow.openTab(model.walletIndex); - } + onClicked: txDetailsWindow.openTab(model.walletIndex); } } diff --git a/guis/desktop/images/infoIcon-light.svg b/guis/desktop/images/infoIcon-light.svg new file mode 100644 index 0000000..7569abd --- /dev/null +++ b/guis/desktop/images/infoIcon-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/guis/desktop/images/infoIcon.svg b/guis/desktop/images/infoIcon.svg new file mode 100644 index 0000000..eabbd77 --- /dev/null +++ b/guis/desktop/images/infoIcon.svg @@ -0,0 +1,5 @@ + + + + + -- 2.54.0 From 347a23bc72d35da9b06535c271b16355e3caf56f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 23:20:36 +0100 Subject: [PATCH 0825/1428] Make the PIN panel take more space This makes entering the PIN much easier simply because we use as much space on the screen as there is available. --- guis/mobile/NumericKeyboardWidget.qml | 7 ++++--- guis/mobile/UnlockWidget.qml | 14 +++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index bcca4af..61635c4 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,8 @@ import QtQuick.Controls as QQC2 Item { id: root - implicitHeight: flowLayout.implicitHeight + implicitHeight: 70 * 4 + height: 90 * 4 required property var dataInput; @@ -64,7 +65,7 @@ Item { model: 12 delegate: Item { width: root.width / 3 - height: 70 + height: root.height / 4 QQC2.Label { id: textLabel anchors.centerIn: parent diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index 40c8c6f..a6def5c 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -64,8 +64,8 @@ Item { Image { id: lockIcon source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - width: 80 - height: 80 + width: 60 + height: 60 anchors.horizontalCenter: parent.horizontalCenter smooth: true y: parent.height > 700 ? 40 : 10 @@ -106,6 +106,7 @@ Item { // Show the typed pin code, but as bullets as you type. Row { + id: pinPreview anchors.top: introText.bottom anchors.topMargin: root.height > 700 ? 20 : 6 spacing: 10 @@ -144,9 +145,12 @@ Item { id: keyboard x: switchButton.numericInput ? 0 : 0 - parent.width - y: parent.height - openButton.height - height - - (root.height > 700 ? 20 : 6) width: parent.width + anchors.top: pinPreview.top + anchors.topMargin: introText.height * 2 // work around the fact that it takes less space when empty + anchors.bottom: openButton.top + anchors.bottomMargin: 10 + dataInput: Item { property QtObject editor: Item { property string enteredString; -- 2.54.0 From d6bde257cc5ec95679046179004be7d350a5df4c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 12 Feb 2024 23:33:59 +0100 Subject: [PATCH 0826/1428] Fix components scaling We simply use the font size, which is our basic scaling system anyway. This avoids sizing issues for really large fonts when items start taking multiple lines. --- guis/Flowee/CheckBox.qml | 3 ++- guis/Flowee/RadioButton.qml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index c6b1f5f..92b13a6 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -31,8 +31,9 @@ T.CheckBox { spacing: 6 indicator: Item { - implicitHeight: titleLabel.implicitHeight / titleLabel.lineCount + implicitHeight: titleLabel.font.pixelSize implicitWidth: implicitHeight * 2.1 + y: 4.5 // make the inner circle visually baseline aligned Rectangle { anchors.fill: parent diff --git a/guis/Flowee/RadioButton.qml b/guis/Flowee/RadioButton.qml index 753ed3a..9645cee 100644 --- a/guis/Flowee/RadioButton.qml +++ b/guis/Flowee/RadioButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,16 +27,18 @@ T.RadioButton { spacing: 6 contentItem: Label { + id: textLabel text: control.text font: control.font opacity: enabled ? 1.0 : 0.3 color: control.down ? palette.midlight : palette.text verticalAlignment: Text.AlignVCenter leftPadding: control.indicator.width + control.spacing + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } indicator: Rectangle { - implicitWidth: control.implicitHeight * 0.6 // effectively based on font size + implicitWidth: textLabel.font.pixelSize implicitHeight: implicitWidth x: control.leftPadding y: parent.height / 2 - height / 2 -- 2.54.0 From 19b8c497cf06248c9965c4ada2ab5907c737ded0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Feb 2024 14:25:54 +0100 Subject: [PATCH 0827/1428] Improve text The help text now is more clear about how the payment may be started. --- guis/mobile/InstaPayConfigPage.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/InstaPayConfigPage.qml b/guis/mobile/InstaPayConfigPage.qml index 36dd3f5..978ef03 100644 --- a/guis/mobile/InstaPayConfigPage.qml +++ b/guis/mobile/InstaPayConfigPage.qml @@ -26,7 +26,8 @@ Page { Flowee.Label { id: introText - text: qsTr("Requests for payment can be approved automatically using Instant Pay.") + text: qsTr("Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit.") + anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top -- 2.54.0 From 6312a468af1e7c45b51b1857d184f693fbd90a48 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Feb 2024 14:26:29 +0100 Subject: [PATCH 0828/1428] Improve the entry of pin and numbers. --- guis/mobile.qrc | 2 + guis/mobile/AccountSelectorWidget.qml | 4 ++ guis/mobile/NumericKeyboardWidget.qml | 86 ++++++++++++++++++++--- guis/mobile/PayWithQR.qml | 27 +++++-- guis/mobile/UnlockWidget.qml | 16 +++-- guis/mobile/images/backspace-light.svg | 11 ++- guis/mobile/images/backspace.svg | 11 ++- guis/mobile/images/confirmIcon-light.svg | 6 ++ guis/mobile/images/confirmIcon.svg | 6 ++ modules/build-transaction/PayToOthers.qml | 1 - 10 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 guis/mobile/images/confirmIcon-light.svg create mode 100644 guis/mobile/images/confirmIcon.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 90bd2c2..15d45c1 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -21,6 +21,8 @@ mobile/images/qr-code-scan-light.svg mobile/images/backspace.svg mobile/images/backspace-light.svg + mobile/images/confirmIcon.svg + mobile/images/confirmIcon-light.svg mobile/images/settingsIcon-light.svg mobile/images/settingsIcon.svg mobile/images/edit-pen.svg diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 077f0b2..80186dd 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -19,6 +19,10 @@ import QtQuick import QtQuick.Controls as QQC2 import "../Flowee" as Flowee +/** + * This widget shows a single wallet and the balance. + * It allows the user to change the 'current' wallet as well via a popup. + */ Rectangle { id: root x: -10 diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 61635c4..adbebd0 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -21,9 +21,30 @@ import QtQuick.Controls as QQC2 Item { id: root implicitHeight: 70 * 4 - height: 90 * 4 + height: contentHeight required property var dataInput; + /** + * The keyboard can have a separator in the bottom left position, + * which is either a dot or a comma based on locale. + * Or if this property is false, we put the backspace there and + * reserve the bottom right button for finished() + */ + property bool hasSeparator: true + + /// emitted when the user used the 'Ok' button. @see hasSeparator + signal finished; + + // We export an implicitHeight that is rather small, + // the parent widget may consider giving this thing a LOT more + // space, though. Which equally looks bad. + // So here we have a maximum height we'll actually occupy for + // usability sake + property int contentHeight: { + // we aim to be have buttons no taller than 18mm + var realPixelSize = Screen.pixelDensity * 18; + return Math.min(height, realPixelSize * 4) + } Rectangle { // when the typed items are not allowd @@ -60,12 +81,38 @@ Item { Flow { id: flowLayout + width: parent.width + anchors.bottom: parent.bottom Repeater { model: 12 delegate: Item { width: root.width / 3 - height: root.height / 4 + height: root.contentHeight / 4 + Rectangle { + id: backgroundCircle + width: Math.min(parent.width, parent.height) - 6 + height: width + anchors.centerIn: parent + radius: pressed ? 20 : height + + property bool pressed: false + color: { + if (pressed || index === 11 || index === 9) + return palette.midlight + return palette.mid + } + opacity: enabled ? 1 : 0 + Behavior on opacity { NumberAnimation { } } + Behavior on color { ColorAnimation { } } + Behavior on radius { NumberAnimation { duration: 100 } } + + Timer { + interval: 200 + running: parent.pressed + onTriggered: parent.pressed = false + } + } QQC2.Label { id: textLabel anchors.centerIn: parent @@ -73,11 +120,11 @@ Item { text: { if (index < 9) return index + 1; - if (index === 9) + if (index === 9 && root.hasSeparator) return Qt.locale().decimalPoint if (index === 10) return "0" - return ""; // index === 11, the backspace. + return ""; } // make dim when not enabled. color: { @@ -87,11 +134,21 @@ Item { } } Image { - visible: index === 11 + visible: index === 11 || (!root.hasSeparator && index == 9) anchors.centerIn: parent - source: index === 11 ? ("qrc:/backspace" + (Pay.useDarkSkin ? "-light" : "") + ".svg") : "" - width: 27 - height: 17 + source: { + let base = ""; + if ((root.hasSeparator && index == 11) + || (!root.hasSeparator && index == 9)) + base = "qrc:/backspace"; + else if (!root.hasSeparator && index == 11) + base = "qrc:/confirmIcon"; + if (base === "") + return base; + return base + (Pay.useDarkSkin ? "-light" : "") + ".svg"; + } + width: 22 + height: 15 opacity: enabled ? 1 : 0.4 } @@ -99,12 +156,18 @@ Item { anchors.fill: parent function doSomething() { let editor = dataInput.editor; + let fail = false; if (index < 9) // these are digits - var fail = !editor.insertNumber("" + (index + 1)); - else if (index === 9) + fail = !editor.insertNumber("" + (index + 1)); + else if (index === 9 && root.hasSeparator) fail = !editor.addSeparator() else if (index === 10) fail = !editor.insertNumber("0"); + else if (index === 11 && !root.hasSeparator) { + fail = editor.enteredString === "" + if (!fail) + root.finished(); + } else fail = !editor.backspacePressed(); if (fail) { @@ -112,6 +175,9 @@ Item { errorFeedback.opacity = 0.7 dataInput.shake(); } + else { + backgroundCircle.pressed = true; + } } onClicked: doSomething(); onDoubleClicked: doSomething(); diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 8cea5a1..8772535 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -242,7 +242,7 @@ Page { return 0; if (commentLabel.y < 0) return priceInput.y + priceInput.height + 10; - return commentLabel.y + commentLabel.height + 10; + return commentLabel.y + commentLabel.height + 6; } } @@ -286,7 +286,7 @@ Page { return userComment.y + userComment.height + 10; return priceInput.y + priceInput.height + 10; } - return destinationAddressHeader.y + destinationAddressHeader.height + 10; + return destinationAddressHeader.y + destinationAddressHeader.height + 6; } } @@ -317,8 +317,25 @@ Page { AccountSelectorWidget { id: walletNameBackground - anchors.bottom: numericKeyboard.top - anchors.bottomMargin: 10 + y: { + var y = priceInput.height + if (commentLabel.visible && !root.allowEditAmount && commentLabel.y > 0) + y += commentLabel.height + userComment.height + 6 + 10 + else if (userComment.visible) + y += userComment.height + 10 + + if (destinationAddressHeader.visible && !root.allowEditAmount && destinationAddressHeader.y > 0) + y += destinationAddressHeader.height + destinationAddress.height + 6 + else if (destinationAddress.visible) + y += destinationAddress.height + 10 + y += 10; + + var altY = parent.height; + altY -= 10 + slideToApprove.height + 25 + altY -= numericKeyboard.contentHeight + height + 10 + + return Math.max(y, altY); + } balanceActions: { if (editPrice.checked) @@ -331,7 +348,9 @@ Page { id: numericKeyboard anchors.bottom: slideToApprove.top anchors.bottomMargin: 15 + anchors.top: walletNameBackground.bottom width: parent.width + height: implicitHeight enabled: !payment.details[0].maxSelected x: allowEditAmount ? 0 : 0 - width dataInput: priceInput diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index a6def5c..8504e53 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -145,12 +145,22 @@ Item { id: keyboard x: switchButton.numericInput ? 0 : 0 - parent.width + hasSeparator: false width: parent.width anchors.top: pinPreview.top anchors.topMargin: introText.height * 2 // work around the fact that it takes less space when empty anchors.bottom: openButton.top anchors.bottomMargin: 10 + onFinished: { + var pwd = keyboard.dataInput.editor.enteredString; + if (pwd !== "") { + root.password = pwd; + keyboard.dataInput.editor.reset(); + root.passwordEntered(); + } + } + dataInput: Item { property QtObject editor: Item { property string enteredString; @@ -220,6 +230,7 @@ Item { Flowee.Button { id: openButton + visible: !switchButton.numericInput anchors.right: parent.right y: { if (switchButton.numericInput) @@ -230,10 +241,7 @@ Item { text: qsTr("Open", "open wallet with PIN") onClicked: { - if (switchButton.numericInput) - var pwd = keyboard.dataInput.editor.enteredString; - else - pwd = pwdField.text; + var pwd = pwdField.text; if (pwd !== "") { root.password = pwd; keyboard.dataInput.editor.reset(); diff --git a/guis/mobile/images/backspace-light.svg b/guis/mobile/images/backspace-light.svg index 5faf02b..4dbd1cf 100644 --- a/guis/mobile/images/backspace-light.svg +++ b/guis/mobile/images/backspace-light.svg @@ -1,7 +1,6 @@ - - - - - - + + + + + diff --git a/guis/mobile/images/backspace.svg b/guis/mobile/images/backspace.svg index 39aee3d..8c989a9 100644 --- a/guis/mobile/images/backspace.svg +++ b/guis/mobile/images/backspace.svg @@ -1,7 +1,6 @@ - - - - - - + + + + + diff --git a/guis/mobile/images/confirmIcon-light.svg b/guis/mobile/images/confirmIcon-light.svg new file mode 100644 index 0000000..dc37a05 --- /dev/null +++ b/guis/mobile/images/confirmIcon-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/guis/mobile/images/confirmIcon.svg b/guis/mobile/images/confirmIcon.svg new file mode 100644 index 0000000..8a5ab39 --- /dev/null +++ b/guis/mobile/images/confirmIcon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 1f5a701..866aaf5 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -475,7 +475,6 @@ Page { ); } - Flickable { id: contentArea anchors.fill: parent -- 2.54.0 From e127574b7adfe3ef70636a0a1e0caccd8c21624b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Feb 2024 21:57:52 +0100 Subject: [PATCH 0829/1428] Improve keyboard usage of listViews The page-up/page-down now actually scroll a significant chunk of a page and not an annoyingly small distance. As we scroll, we make it a point to show the ScrollBar-thumb in order to give the user feedback on where in the list they are. --- guis/Flowee/ListViewKeyHandler.qml | 20 +++++++++++++++----- guis/Flowee/ScrollThumb.qml | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/guis/Flowee/ListViewKeyHandler.qml b/guis/Flowee/ListViewKeyHandler.qml index 6e6f9a1..d3bf65f 100644 --- a/guis/Flowee/ListViewKeyHandler.qml +++ b/guis/Flowee/ListViewKeyHandler.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,23 +33,33 @@ Item { id: root property ListView target: parent + onTargetChanged: if (target) target.flickDeceleration = 3000 + Keys.onPressed: (event)=> { if (root.target == null) return; if (event.key === Qt.Key_Down) { - root.target.flick(0, -500); + root.target.flick(0, -1000); event.accepted = true; } else if (event.key === Qt.Key_Up) { - root.target.flick(0, 500); + root.target.flick(0, 1000); event.accepted = true; } else if (event.key === Qt.Key_PageUp) { - root.target.flick(0, Math.sqrt(2 * 1000 * root.target.height)); + var cy = root.target.contentY + if (cy - root.target.height * 2 < 0) + root.target.flick(0, root.target.height * 10000); + else + root.target.contentY -= root.target.height * 0.75 event.accepted = true; } else if (event.key === Qt.Key_PageDown) { - root.target.flick(0, -Math.sqrt(2 * 1000 * root.target.height)); + var cy = root.target.contentY + if (cy + root.target.height * 2 > root.target.contentHeight) + root.target.flick(0, -root.target.height * 10000); + else + root.target.contentY += root.target.height * 0.75 event.accepted = true; } else if (event.key === Qt.Key_Home) { diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index 44e554e..c072b46 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -30,6 +30,22 @@ QQC2.ScrollBar { background: Item {} contentItem: Item {} + Connections { + target: root.flickable + property double prevY: 0 + function onFlickStarted() { + thumbRect.open = true + } + function onContentYChanged() { + var newY = root.flickable.contentY; + if (Math.abs(newY - prevY) > root.flickable.height / 3) { + // rapid movement, lets assist the user by showing the thumb. + thumbRect.open = true + } + prevY = newY; + } + } + Item { parent: root.flickable anchors.fill: parent -- 2.54.0 From 04c4c7ec03b0d6a02507043fbcdb1b505fc57d0c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Feb 2024 22:03:00 +0100 Subject: [PATCH 0830/1428] Avoid using a not existing account --- src/Payment.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index e5ef3c4..62689b0 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -569,10 +569,12 @@ void Payment::setCurrentAccount(AccountInfo *account) emit currentAccountChanged(); emit walletPinChanged(); - for (auto detail : m_paymentDetails) { - detail->setWallet(account->wallet()); + if (account) { + for (auto detail : m_paymentDetails) { + detail->setWallet(account->wallet()); + } + doAutoPrepare(); } - doAutoPrepare(); } int Payment::fiatPrice() const -- 2.54.0 From a2560d98a7f3b8e43197cd3e19362be8038911cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 13 Feb 2024 23:09:54 +0100 Subject: [PATCH 0831/1428] Separate 'singleAccount' concept between UIs The isSingleAccount property controls if the UI is simple or more complex. The single account setup doesn't show you that you have any concept on differnt accounts at all. No account chooser etc. On Desktop, however, we choose to always show the archived wallets anyway. Even though its not in-your-face. Which makes the behavior between those UIs slightly different. The addition of the limitedArchiveView property is meant to facilitate that. --- guis/desktop/AccountConfigMenu.qml | 2 +- guis/mobile/main.qml | 1 + src/PortfolioDataProvider.cpp | 64 ++++++++++++++++++++++++++---- src/PortfolioDataProvider.h | 26 +++++++++++- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 21a89b4..377fa47 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -77,7 +77,7 @@ ConfigItem { items.push(closeWalletAction); if (onMainView && encrypted && !decrypted && tabbar.currentIndex != 0) items.push(openWalletAction); - var singleAccountSetup = portfolio.rawAccounts.length === 1 + var singleAccountSetup = portfolio.singleAccountSetup var isArchived = root.account.isArchived; if (!singleAccountSetup && !isArchived) items.push(primaryAction); diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index bfeedeb..e8ab524 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -36,6 +36,7 @@ ApplicationWindow { property bool isLoading: typeof portfolio === "undefined"; onIsLoadingChanged: { + portfolio.limitedArchiveView = true; // only load our UI when the p2p layer is loaded and all // variables are available. if (!isLoading) { diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 3a18bf5..178b736 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -44,34 +44,35 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) addWalletAccount(w); } selectDefaultWallet(); + updateIsSingleAccount(); emit accountsChanged(); }); connect (app, SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); connect (app, &FloweePay::privateModeChanged, this, [=]() { selectDefaultWallet(); + updateIsSingleAccount(); emit accountsChanged(); }); - connect (this, &PortfolioDataProvider::accountsChanged, [=]() { - m_isSingleAccount = accounts().length() <= 1; - emit singleAccountChanged(); - }); - m_isSingleAccount = accounts().length() <= 1; + updateIsSingleAccount(); } QList PortfolioDataProvider::accounts() const { QList answer; const bool privateModeOn = FloweePay::instance()->privateMode(); + bool hasArchivedWallets = false; for (auto *account : m_accountInfos) { - if (account->isArchived()) - continue; if (!account->userOwnedWallet()) continue; if (privateModeOn && AccountConfig(account->id()).isPrivate()) continue; + if (account->isArchived()) { + hasArchivedWallets = true; + continue; + } answer.append(account); } - if (answer.length() == 1) { + if (answer.length() == 1 && !hasArchivedWallets && !m_limitedArchiveView) { // We have a 'mode' where having exactly one wallet will // hide the wallets-list to avoid confusing the user with multi // wallet features. @@ -99,7 +100,10 @@ QList PortfolioDataProvider::archivedAccounts() const QList answer; // we filter out the wallets that are NOT user-owned. Which is essentially the main initial // wallet created to allow people to deposit instantly. + const bool privateModeOn = FloweePay::instance()->privateMode(); for (auto *account : m_accountInfos) { + if (privateModeOn && AccountConfig(account->id()).isPrivate()) + continue; if (account->isArchived() && account->userOwnedWallet()) answer.append(account); } @@ -222,8 +226,10 @@ void PortfolioDataProvider::addWalletAccount(Wallet *wallet) connect (info, &AccountInfo::isPrivateChanged, this, [=]() { if (FloweePay::instance()->privateMode()) emit accountsChanged(); + updateIsSingleAccount(); }); emit accountsChanged(); + updateIsSingleAccount(); } void PortfolioDataProvider::walletChangedPriority() @@ -245,4 +251,46 @@ void PortfolioDataProvider::walletChangedPriority() } emit accountsChanged(); emit totalBalanceChanged(); // maybe one got (un) archived which changes the total balance + updateIsSingleAccount(); +} + +void PortfolioDataProvider::updateIsSingleAccount() +{ + int numFound = 0; + const bool privateModeOn = FloweePay::instance()->privateMode(); + for (int i = 0; i < m_accounts.size(); ++i) { + auto wallet = m_accounts.at(i); + if (!wallet->userOwnedWallet()) + continue; + + if (m_limitedArchiveView) { // reject archived wallets. + const auto prio = wallet->segment()->priority(); + if (prio == PrivacySegment::OnlyManual) + continue; + } + if (privateModeOn && AccountConfig(wallet->segment()->segmentId()).isPrivate()) + continue; + + // private mode + ++numFound; + } + bool newIsSingle = numFound <= 1; + if (m_isSingleAccount == newIsSingle) + return; + m_isSingleAccount = newIsSingle; + emit singleAccountChanged(); +} + +bool PortfolioDataProvider::limitedArchiveView() const +{ + return m_limitedArchiveView; +} + +void PortfolioDataProvider::setLimitedArchiveView(bool on) +{ + if (m_limitedArchiveView == on) + return; + m_limitedArchiveView = on; + emit limitedArchiveViewChanged(); + updateIsSingleAccount(); } diff --git a/src/PortfolioDataProvider.h b/src/PortfolioDataProvider.h index e2317a3..46deda4 100644 --- a/src/PortfolioDataProvider.h +++ b/src/PortfolioDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,7 +42,25 @@ class PortfolioDataProvider : public QObject Q_PROPERTY(QList rawAccounts READ rawAccounts NOTIFY accountsChanged) Q_PROPERTY(AccountInfo* current READ current WRITE setCurrent NOTIFY currentChanged) Q_PROPERTY(double totalBalance READ totalBalance NOTIFY totalBalanceChanged) + /** + * This returns true when the UI can avoid showing elements that allows the user to + * select between different wallets. + * + * Notice that when the limitedArchiveView is set to true, we are a single-account-setup + * simply by having exactly one non-archived wallet. + * In the case of the limitedArchiveView being set to false, implying the UI has full + * view of the archived wallets, the only way to have a single-account setup is by having + * exactly one wallet. + */ Q_PROPERTY(bool singleAccountSetup READ isSingleAccount NOTIFY singleAccountChanged) + /** + * If true, the UI hides archived wallets in the general screens. + * This should be set by the UI. + * + * A user interface that allows direct interaction with archived wallets will have a different idea of what + * makes a 'singleAccountSetup'. + */ + Q_PROPERTY(bool limitedArchiveView READ limitedArchiveView WRITE setLimitedArchiveView NOTIFY limitedArchiveViewChanged FINAL) public: explicit PortfolioDataProvider(QObject *parent = nullptr); @@ -60,6 +78,9 @@ public: bool isSingleAccount() const; + bool limitedArchiveView() const; + void setLimitedArchiveView(bool newLimitedArchiveView); + public slots: void addWalletAccount(Wallet *wallet); @@ -68,9 +89,11 @@ signals: void currentChanged(); void totalBalanceChanged(); void singleAccountChanged(); + void limitedArchiveViewChanged(); private slots: void walletChangedPriority(); + void updateIsSingleAccount(); private: Payment* createPaymentObject(const QString &address, double value) const; @@ -80,6 +103,7 @@ private: int m_currentAccount = -1; bool m_isSingleAccount = true; + bool m_limitedArchiveView = false; }; #endif -- 2.54.0 From 6e2eb8797a999a18a724b01cef13661e30e93e13 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 14 Feb 2024 00:21:13 +0100 Subject: [PATCH 0832/1428] Fix typo. --- guis/mobile/QRScannerOverlay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index 3376c57..b860ee5 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -157,7 +157,7 @@ FocusScope { } Rectangle { id: flashFrame - anchors.top: pasteFrame.bottom + anchors.top: pasteFrame.top anchors.right: parent.right anchors.rightMargin: 50 radius: 6 -- 2.54.0 From fc407ee7f2119476ef62b7862175cfebf78a6353 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 26 Feb 2024 23:09:56 +0100 Subject: [PATCH 0833/1428] Improve debug output features. In debug builds. This removes the 'debug' logging (again) in non-debug builds, but it also adds a new 'debugFile' argument where the user can point to the logs.conf (as used by all flowee server components) which allows setting of log levels per section, and more. --- src/main.cpp | 13 ++----------- src/main_utils.cpp | 34 +++++++++++++++++++++++++++------- src/main_utils_android.cpp | 16 ++++++++++++---- 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3cdd748..f1012e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,9 +27,6 @@ #include "QMLClipboardHelper.h" #include "MenuModel.h" #include "ModuleManager.h" -#ifdef NETWORK_LOGGER -# include "NetworkLogClient.h" -#endif #ifdef MOBILE #include "CameraController.h" #include "QRScanner.h" @@ -82,7 +79,7 @@ struct CommandLineParserData; CommandLineParserData* createCLD(QGuiApplication &app); void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld); std::unique_ptr handleStaticChain(CommandLineParserData *cld); -Log::Verbosity logVerbosity(CommandLineParserData *cld); +void initLogger(CommandLineParserData *cld); int main(int argc, char *argv[]) @@ -102,13 +99,7 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "ClipboardHelper"); auto cld = createCLD(qapp); - auto *logger = Log::Manager::instance(); - logger->clearChannels(); - logger->clearLogLevels(logVerbosity(cld)); - logger->addConsoleChannel(); -#ifdef NETWORK_LOGGER - logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); -#endif + initLogger(cld); // load the modules and its translations units first, which gives them the lowest priority ModuleManager modules; diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 8e166b1..245401e 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -32,6 +32,7 @@ struct CommandLineParserData explicit CommandLineParserData(QGuiApplication &qapp) : connect(QStringList() << "connect", "Connect to HOST", "HOST"), debug(QStringList() << "debug", "Use debug level logging"), + debugFile(QStringList() << "debugFile", "Point to logs.conf", "PATH"), verbose(QStringList() << "verbose" << "v", "Be more verbose"), quiet(QStringList() << "quiet" << "q", "Be quiet, only errors are shown"), testnet4(QStringList() << "testnet4", "Use testnet4"), @@ -40,7 +41,10 @@ struct CommandLineParserData { parser.setApplicationDescription("Flowee pay"); parser.addHelpOption(); +#ifndef NDEBUG parser.addOption(debug); + parser.addOption(debugFile); +#endif parser.addOption(verbose); parser.addOption(quiet); parser.addOption(connect); @@ -66,6 +70,7 @@ struct CommandLineParserData QCommandLineParser parser; QCommandLineOption connect; QCommandLineOption debug; + QCommandLineOption debugFile; QCommandLineOption verbose; QCommandLineOption quiet; QCommandLineOption testnet4; @@ -81,17 +86,32 @@ CommandLineParserData* createCLD(QGuiApplication &app) return new CommandLineParserData(app); } -Log::Verbosity logVerbosity(CommandLineParserData *cld) +void initLogger(CommandLineParserData *cld) { -#ifndef BCH_NO_DEBUG_OUTPUT + auto *logger = Log::Manager::instance(); + Log::Verbosity defaultVerbosity = Log::WarningLevel; +#ifndef NDEBUG + if (cld->parser.isSet(cld->debugFile)) { + auto path = cld->parser.value(cld->debugFile); + // lets load the debug levels from a logs.conf file, allowing fine-grainded + // settings for which logging is enabled or not. + logger->parseConfig(path.toStdString(), "pay.log"); + return; + } if (cld->parser.isSet(cld->debug)) - return Log::DebugLevel; + defaultVerbosity = Log::DebugLevel; + else #endif if (cld->parser.isSet(cld->verbose)) - return Log::InfoLevel; - if (cld->parser.isSet(cld->quiet)) - return Log::FatalLevel; - return Log::WarningLevel; + defaultVerbosity = Log::InfoLevel; + else if (cld->parser.isSet(cld->quiet)) + defaultVerbosity = Log::FatalLevel; + logger->clearChannels(); + logger->clearLogLevels(defaultVerbosity); + logger->addConsoleChannel(); +#ifdef NETWORK_LOGGER + logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); +#endif } std::unique_ptr handleStaticChain(CommandLineParserData *cld) diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index b4dc3e5..956bd33 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -26,6 +26,9 @@ #include #include +#ifdef NETWORK_LOGGER +# include "NetworkLogClient.h" +#endif struct CommandLineParserData { @@ -42,12 +45,17 @@ CommandLineParserData* createCLD(QGuiApplication &app) return dat; } -Log::Verbosity logVerbosity(CommandLineParserData*) +void initLogger(CommandLineParserData *cld) { + auto *logger = Log::Manager::instance(); + Log::Verbosity defaultVerbosity = Log::FatalLevel; #ifdef NETWORK_LOGGER - return Log::DebugLevel; -#else - return Log::FatalLevel; + defaultVerbosity = Log::DebugLevel; +#endif + logger->clearChannels(); + logger->clearLogLevels(defaultVerbosity); +#ifdef NETWORK_LOGGER + logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); #endif } -- 2.54.0 From 920e5d470c52b0d7266a84ae1e9f53f6d839cc68 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Feb 2024 00:10:32 +0100 Subject: [PATCH 0834/1428] Split bloom filter and private-key ownership. Now we view the creation of more private keys, and how many, as a separate concept as the bloom-filter 'gap' counts. This cleans up the concepts of bloom filters, we now use the design as documented on; https://codeberg.org/Flowee/pay/wiki/dev/bloom --- src/Wallet.cpp | 161 +++++++++++++++++++++-------------------- src/Wallet.h | 19 ++--- src/Wallet_support.cpp | 3 + 3 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 35320e3..077e82b 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -138,7 +138,7 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co prevTx.setTxIndex((i != m_txidCache.end()) ? i->second : 0); if (i != m_txidCache.end()) logDebug(LOG_WALLET) << " Input:" << inputIndex << "prevTx:" << prevTxhash - << Log::Hex << i->second << prevTx.encoded(); + << "ourRefs:" << Log::Hex << i->second << prevTx.encoded(); } } else if (iter.tag() == Tx::PrevTxIndex) { if (prevTx.isValid()) { @@ -217,59 +217,51 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co } // check if we need to create more private keys based on if this transaction -// used private keys close to the index we have created and keep track off. -bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx, bool &updateBloom) +// used private keys close to the last index we have created and kept track of. +bool Wallet::updateHDSignatures(const Wallet::WalletTransaction &wtx) { // mutex locked by caller if (m_hdData.get() == nullptr) return false; - static constexpr int ExtraAddresses = 100; - int needChangeAddresses = 0; - int needMainAddresses = 0; - - const int changeGap = filter_changeUnusedToInclude + (m_walletStoresCashFusions ? 50 : 0); - + int highestChangeKey = 0; + int highestMainKey = 0; for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { auto privKey = m_walletSecrets.find(i->second.walletSecretId); assert(privKey != m_walletSecrets.end()); // invalid wtx const WalletSecret &ws = privKey->second; + assert (ws.fromHdWallet); // since we have the m_hdData - if (ws.fromHdWallet) { - /* - * We previously sent a list of addresses to the peers in the bloom filter, - * we do this sequentually by HD-derivation index. We try to be conservative - * in what we share, which means that as those addresses are used we may need - * to send more addresses by rebuilding the bloom filter. - * - * This code looks at the 'gap', the amount of addresses between what is used - * and what we previously send in our bloom filter. If the gap is getting too small, - * we need to create a new filter in order to avoid missing transactions. - */ - if (ws.fromChangeChain) { - assert(m_hdData->lastChangeKey >= ws.hdDerivationIndex); - if (m_hdData->lastChangeKey - ws.hdDerivationIndex < changeGap) { - needChangeAddresses = ExtraAddresses - + (m_walletStoresCashFusions ? 75 : 0); - } - // notice that here we trigger on there only being 30 addresses between what we used and - // what was sent, while what we sent is (as set in filter_changeUnusedToInclude) much - // heigher. This is intentional so we update the filter only when we used a larger list - // of addresses instead of refreshing the filter after every single new key being used. - updateBloom |= m_hdData->lastIncludedChangeKey - ws.hdDerivationIndex < 30; // the gap - } else { - assert(m_hdData->lastMainKey >= ws.hdDerivationIndex); - updateBloom |= m_hdData->lastIncludedMainKey - ws.hdDerivationIndex < 50; // the gap - // additionally, we may need to actually generate more addresses - if (m_hdData->lastMainKey - ws.hdDerivationIndex < filter_hdUnusedToInclude + 50) - needMainAddresses = ExtraAddresses; - } + if (ws.fromChangeChain) { + assert(m_hdData->lastChangeKey >= ws.hdDerivationIndex); + highestChangeKey = std::max(highestChangeKey, ws.hdDerivationIndex); + } + else { + assert(m_hdData->lastMainKey >= ws.hdDerivationIndex); + highestMainKey = std::max(highestMainKey, ws.hdDerivationIndex); } } + /* if the used keys are relatively close to the gap, we may have missed + * some matches. We fix that by creating a bunch of new private keys and + * the caller will then re-parse the transaction and maybe find more + * matches. + * + * Notice that if we notice we are close, we're very generious in how many + * keys we generate in order to minimize the amount of times we hit this issue. + */ + + int needMainAddresses = 0; + int needChangeAddresses = 0; + int distance = m_hdData->lastChangeKey - highestChangeKey; + if (distance < 50) + needChangeAddresses = distance + 200; + distance = m_hdData->lastMainKey - highestMainKey; + if (distance < 50) + needMainAddresses = distance + 200; + if (needChangeAddresses + needMainAddresses > 0) { deriveHDKeys(needMainAddresses, needChangeAddresses); - updateBloom = false; // the deriveHDKeys just called rebuildBloom return true; } return false; @@ -314,8 +306,6 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) m_secretsChanged = true; m_utxoDirty = true; } - - rebuildBloom(); saveSecrets(); } @@ -347,7 +337,6 @@ void Wallet::newTransaction(const Tx &tx) int firstNewTransaction; P2PNet::Notification notification; notification.privacySegment = int(m_segment->segmentId()); - bool createdNewKeys = false; { QMutexLocker locker(&m_lock); if (m_walletIsImporting) { @@ -369,22 +358,19 @@ void Wallet::newTransaction(const Tx &tx) assert(wtx.isCoinbase == false); if (wtx.outputs.empty() && wtx.inputToWTX.empty()) { // no connection to our UTXOs - if (++m_bloomScore > 25) - rebuildBloom(ForceBuild); + ++m_bloomFilterMisses; return; } m_utxoDirty = true; setUserOwnedWallet(true); wtx.minedBlockHeight = Wallet::Unconfirmed; wtx.transactionTime = time(nullptr); - bool dummy = false; - while (updateHDSignatures(wtx, dummy)) { + while (updateHDSignatures(wtx)) { // if we added a bunch of new private keys, then rerun the matching // so we make sure we matched all we can auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); wtx.outputs = newWtx.outputs; wtx.userComment = newWtx.userComment; - createdNewKeys = true; } // Remember the signature type used for specific private keys @@ -419,15 +405,13 @@ void Wallet::newTransaction(const Tx &tx) emit utxosChanged(); emit appendedTransactions(firstNewTransaction, 1); FloweePay::instance()->sendTransactionNotification(notification); - if (createdNewKeys) - rebuildBloom(); + rebuildBloom(MaybeBuild); } void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) { auto transactions = WalletPriv::sortTransactions(blockTransactions); std::deque transactionsToSave; - bool needNewBloom = false; { QMutexLocker locker(&m_lock); /* We ask multiple peers for transactions, each hit gets a callback to newTransactions. @@ -459,8 +443,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: notification.blockHeight = blockHeight; if (wtx.outputs.empty() && wtx.inputToWTX.empty()) { // no connection to our UTXOs - if (++m_bloomScore > 25) - needNewBloom = true; + ++m_bloomFilterMisses; continue; } if (wtx.isCashFusionTx) // improve behavior of bloom filters @@ -476,7 +459,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: } m_utxoDirty = true; const bool wasUnconfirmed = wtx.minedBlockHeight == Wallet::Unconfirmed; - while (updateHDSignatures(wtx, needNewBloom)) { + while (updateHDSignatures(wtx)) { // if we added a bunch of new private keys, then rerun the matching // so we make sure we matched all we can auto newWtx = createWalletTransactionFromTx(tx, txid, signatureTypes, ¬ification); @@ -539,8 +522,6 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: ws->second.initialHeight = blockHeight; m_secretsChanged = true; } - - // TODO somehow make payment requests know about this. } @@ -618,8 +599,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: emit startDelayedSave(); } recalculateBalance(); - if (needNewBloom) - rebuildBloom(); + rebuildBloom(MaybeBuild); } void Wallet::saveTransaction(const Tx &tx) @@ -862,6 +842,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons m_walletIsImporting = true; } deriveHDKeys(200, 200, startHeight); + rebuildBloom(MaybeBuild); } int Wallet::lastTransactionTimestamp() const @@ -1004,6 +985,11 @@ int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCa return -1; } +// callback from the p2p lib, make sure that the bloom is Ok for new peers. +void Wallet::rebuildFilter() { + rebuildBloom(m_utxoDirty ? ForceBuild : MaybeBuild); +} + void Wallet::rebuildBloom(RebuildBloomOption option) { int unusedToInclude = filter_unusedToInclude; @@ -1027,12 +1013,7 @@ void Wallet::rebuildBloom(RebuildBloomOption option) return; } } - if (option != ForceBuild && !m_utxoDirty) - return; - m_utxoDirty = false; - auto lock = m_segment->clearFilter(); - // ^ notice that the 'lock' we get back will broadcast the new filter when it goes out of scope. std::set secretsUsed; for (const auto &i : m_walletTransactions) { const auto &wtx = i.second; @@ -1052,6 +1033,39 @@ void Wallet::rebuildBloom(RebuildBloomOption option) } } } + + if (m_bloomFilterMisses >= 100) + option = ForceBuild; + if (m_hdData.get() && option == MaybeBuild) { + int highestMainSecret = 0; // highest in use secret + int highestChangeSecret = 0; + for (int id : secretsUsed) { + auto iter = m_walletSecrets.find(id); + assert(m_walletSecrets.end() != iter); + const auto &secret = iter->second; + assert(secret.fromHdWallet); + if (secret.fromChangeChain) + highestChangeSecret = std::max(highestChangeSecret, secret.hdDerivationIndex); + else + highestMainSecret = std::max(highestMainSecret, secret.hdDerivationIndex); + } + + const int mainGap = m_hdData->lastIncludedMainKey - highestMainSecret; + const int changeGap = m_hdData->lastIncludedChangeKey - highestChangeSecret; + if (mainGap > hdUnusedToInclude / 3 && changeGap > changeUnusedToIncludeBase / 3) { + // we still have plenty of breathing space. Wait longer until we update. + logInfo(LOG_WALLET) << "RebuildBloom skipped. mainGap:" << mainGap << "changeGap:" << changeGap; + return; + } + } + else if (option == MaybeBuild && m_walletSecrets.size() == 1) { + logInfo(LOG_WALLET) << "RebuildBloom skipped. This is a single-key wallet"; + return; + } + + auto lock = m_segment->clearFilter(); + // ^ notice that the 'lock' we get back will broadcast the new filter when it goes out of scope. + logDebug(LOG_WALLET) << "Rebuilding bloom filter." << m_segment->segmentId() << "UTXO-size:" << secretsWithBalance.size(); for (auto &i : m_walletSecrets) { const auto &secret = i.second; @@ -1122,7 +1136,8 @@ void Wallet::rebuildBloom(RebuildBloomOption option) auto tx = m_walletTransactions.at(ref.txIndex()); m_segment->addToFilter(tx.txid, ref.outputIndex()); } - m_bloomScore = 0; + m_bloomFilterMisses = 0; + m_utxoDirty = false; } bool Wallet::anythingNew(int blockHeight, const std::deque &transactions) const @@ -1350,7 +1365,9 @@ int Wallet::reserveUnusedAddress(KeyId &keyId, PrivKeyType pkt) // no unused addresses, lets make some. if (m_hdData.get()) { deriveHDKeys(100, 0); - return reserveUnusedAddress(keyId); + auto rc = reserveUnusedAddress(keyId); + rebuildBloom(MaybeBuild); + return rc; } const bool walletUnavailable = m_encryptionLevel > NotEncrypted && !isDecrypted(); @@ -1504,20 +1521,10 @@ PrivacySegment * Wallet::segment() const void Wallet::createNewPrivateKey(uint32_t currentBlockheight) { QMutexLocker locker(&m_lock); - WalletSecret secret; - if (m_hdData.get()) { - secret.fromHdWallet = true; - secret.hdDerivationIndex = ++m_hdData->lastMainKey; + assert(m_hdData.get() == nullptr); - const auto count = m_hdData->derivationPath.size(); - assert(count >= 2); - m_hdData->derivationPath[count - 2] = 0; // Flowee Pay doesn't really care about the 'change' chain. - m_hdData->derivationPath[count - 1] = secret.hdDerivationIndex; - secret.privKey = m_hdData->masterKey.derive(m_hdData->derivationPath); - } - else { - secret.privKey.makeNewKey(); - } + WalletSecret secret; + secret.privKey.makeNewKey(); const PublicKey pubkey = secret.privKey.getPubKey(); secret.address = pubkey.getKeyId(); secret.initialHeight = currentBlockheight; diff --git a/src/Wallet.h b/src/Wallet.h index 100d315..f427c8b 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -112,9 +112,7 @@ public: void newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) override; // notify about unconfirmed Tx. void newTransaction(const Tx &tx) override; - void rebuildFilter() override { - rebuildBloom(); - } + void rebuildFilter() override; /// Let the wallet know that it is up-to-date to \a height void setLastSynchedBlockHeight(int height) override; void headerSyncComplete() override; @@ -125,7 +123,7 @@ public: PrivacySegment *segment() const; - /// Create a new private key. + /// Create a new private key, can not be called on a HD wallet. void createNewPrivateKey(uint32_t currentBlockheight); /// import an existing private key. bool addPrivateKey(const QString &privKey, uint32_t startBlockHeight); @@ -435,13 +433,12 @@ private: */ int findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const; - /// Fill the bloom filter with all the unspent transactions and addresses we handle. enum RebuildBloomOption { ForceBuild, - OnlyWhenDirty + MaybeBuild ///< Check the gap and rebuild a bloom filter if needed }; - - void rebuildBloom(RebuildBloomOption option = OnlyWhenDirty); + /// Fill the bloom filter with all the unspent transactions and addresses we handle. + void rebuildBloom(RebuildBloomOption option); // returns true if any of the transactions are unknown to the wallet bool anythingNew(int blockHeight, const std::deque &transactions) const; @@ -465,7 +462,6 @@ private: int m_nextWalletSecretId = 1; int m_nextWalletTransactionId = 1; int m_lastBlockHeightSeen = 0; - short m_bloomScore = 0; std::map m_walletSecrets; struct HierarchicallyDeterministicWalletData { @@ -569,7 +565,7 @@ private: // check if we need to create more private keys based on if this transaction // used private keys close to the index we have created and keep track off. // returns true if new private keys have been derived from the HD masterkey - bool updateHDSignatures(const WalletTransaction &wtx, bool &updateBloom); + bool updateHDSignatures(const WalletTransaction &wtx); /// use the hdData master key to create a number of private keys (WalletSecrets). void deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight = 0); @@ -624,7 +620,8 @@ private: bool parsePassword(const QString &password); bool m_saveStarted = false; bool m_haveEncryptionKey = false; - bool m_utxoDirty = false; // if dirty, rebuildBloom makes sense. + bool m_utxoDirty = false; // if dirty, a rebuildBloom may be useful. + int m_bloomFilterMisses = 0; // times a transaction not for us is found. uint32_t m_encryptionSeed = 0; uint16_t m_encryptionChecksum = 0; EncryptionLevel m_encryptionLevel = NotEncrypted; diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 02deaf7..b02a760 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -380,6 +380,9 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) // Notice that we don't do it in the above loop because a map reverse iterator doesn't // have a matching 'erase' method. + if (txIndex >= lastToRemove) + logDebug(LOG_WALLET) << "Removing transactions" << txIndex << "-" << lastToRemove << " blockHeight:" << blockHeight; + while (txIndex >= lastToRemove) { auto i = m_walletTransactions.find(txIndex--); if (i == m_walletTransactions.end()) { -- 2.54.0 From 15411010b0c06105665cfb569d77056d4eea747e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Feb 2024 10:49:27 +0100 Subject: [PATCH 0835/1428] Avoid rollback when not needed This now uses a shared-pointer and moves the creation to the point where we know the new data is actually a transaction for us, avoiding work when the bloom filter caught a transaction not actually for us. --- src/Wallet.cpp | 20 ++++++++++++++++---- src/Wallet.h | 2 +- src/Wallet_support.cpp | 8 ++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 077e82b..2875044 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -426,10 +426,11 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: * So if we get something inserted out-of-order we're going to rollback the ones we already * have AFTER this and re-apply them later. */ - const auto insertBeforeData = removeTransactionsAfter(blockHeight - 1); // remove them for this block too! + std::shared_ptr insertBeforeData; int firstNewTransaction = m_nextWalletTransactionId; - for (auto &tx: transactions) { + for (auto itx = transactions.begin(); itx != transactions.end(); ++itx) { + const auto &tx = *itx; const uint256 txid = tx.createHash(); WalletTransaction wtx; @@ -457,6 +458,15 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: continue; walletTransactionId = oldTx->second; } + if (insertBeforeData.get() == nullptr) { + // remove all transactions after this block, in order to replay them at the end + // of this insert. + // The first inserted transaction being new means we can include in the removal + // txs for this block, if not the first tx, then we can't. + const bool firstInBlock = itx == transactions.begin(); + insertBeforeData = removeTransactionsAfter(blockHeight + (firstInBlock ? -1 : 0)); + } + m_utxoDirty = true; const bool wasUnconfirmed = wtx.minedBlockHeight == Wallet::Unconfirmed; while (updateHDSignatures(wtx)) { @@ -580,8 +590,10 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: // the 'insert before' concept simply deletes and re-inserts, let // users know old transaction IDs no longer exist. - for (auto id : insertBeforeData.oldTransactionIds) { - emit transactionRemoved(id); + if (insertBeforeData.get()) { + for (auto id : insertBeforeData->oldTransactionIds) { + emit transactionRemoved(id); + } } if (!transactionsToSave.empty()) { emit utxosChanged(); diff --git a/src/Wallet.h b/src/Wallet.h index f427c8b..a572a4e 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -556,7 +556,7 @@ private: * So if we get something inserted out-of-order we're going to rollback the ones we already * have AFTER the header and re-apply them in the destructor of the InsertBeforeData struct. */ - InsertBeforeData removeTransactionsAfter(int blockHeight); + std::shared_ptr removeTransactionsAfter(int blockHeight); /// helper method to upgrade older wallets and find the sigType from transactions to populate the field in walletSecrets. /// Returns true if any changes were made. diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index b02a760..e5e1a26 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -356,9 +356,9 @@ Wallet::InsertBeforeData::~InsertBeforeData() parent->m_inInsertBeforeCallback = false; } -Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) +std::shared_ptr Wallet::removeTransactionsAfter(int blockHeight) { - InsertBeforeData ibd(this); + std::shared_ptr ibd = std::make_shared(this); if (m_inInsertBeforeCallback) return ibd; @@ -421,8 +421,8 @@ Wallet::InsertBeforeData Wallet::removeTransactionsAfter(int blockHeight) } } - ibd.oldTransactionIds.insert(i->first); - ibd.transactions.push_back(i->second); + ibd->oldTransactionIds.insert(i->first); + ibd->transactions.push_back(i->second); auto txidIter = m_txidCache.find(wtx.txid); assert(txidIter != m_txidCache.end()); m_txidCache.erase(txidIter); -- 2.54.0 From d1dc483b18455192d8fde1acdbbfd8a86c303be5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Feb 2024 10:56:11 +0100 Subject: [PATCH 0836/1428] A little counter balance for big bloom filters Last year I increased the bloom filters to make sure that bcom wallets could be imported with their weird behavior. The side-effect has been that wallets imported that have 3K addresses and up start to have massive amounts of false-positives on their bloom filter during import. So while the hacks done before were for wallet vendors intentionally keeping their list of private keys small (100 or so), we know we can thus turn off said weird workarounds when the wallet is in actual fact much larger. And thus save bandwidth. --- src/Wallet.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 2875044..9f8f406 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1046,7 +1046,8 @@ void Wallet::rebuildBloom(RebuildBloomOption option) } } - if (m_bloomFilterMisses >= 100) + if ((m_walletIsImporting && m_bloomFilterMisses >= 300) + || (!m_walletIsImporting && m_bloomFilterMisses >= 100)) option = ForceBuild; if (m_hdData.get() && option == MaybeBuild) { int highestMainSecret = 0; // highest in use secret @@ -1102,6 +1103,20 @@ void Wallet::rebuildBloom(RebuildBloomOption option) * and have no current balance on them (utxo does not refer to them) */ else if (secretsWithBalance.find(i.first) == secretsWithBalance.end()) { + /* + * Ok, the above is true and is good for privacy. We want to avoid uploading stuff + * we don't ever expect money on. + * BUT, some pretty big wallet out there are not so nice and are known to reuse + * a change suddenly. + * Sooooo! We still actually include those 'old' keys during an import and + * convince owners of such wallets to switch to our wallet by being great! + * + * Ok, but not if the wallet is getting big. That notorious company never + * let its wallets get big, which is why they reused addresses. (big is 800 here) + */ + if (m_walletIsImporting == false || m_walletSecrets.size() > 800) + continue; + /* * The amount of unused change addresses we include is meant to be a * 'gap'. Which means, after the last used addresss we should add that @@ -1110,17 +1125,6 @@ void Wallet::rebuildBloom(RebuildBloomOption option) * gaps between change addresses. */ changeUnusedToInclude = changeUnusedToIncludeBase; - - /* - * Ok, the above is true and is good for privacy. We want to avoid uploading stuff - * we don't ever expect money on. - * BUT, some pretty big wallet out there are not so nice and are known to reuse - * a change suddenly. - * Sooooo! We still actually include those 'old' keys during an import and - * convince owners of such wallets to switch to our wallet by being great! - */ - if (m_walletIsImporting == false) - continue; } m_hdData->lastIncludedChangeKey = std::max(m_hdData->lastIncludedChangeKey, secret.hdDerivationIndex); } -- 2.54.0 From d07a8f335502203a7cd766d468a1eedda717e9d6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 Feb 2024 19:42:39 +0100 Subject: [PATCH 0837/1428] new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index a3baa20..6397b9d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="25" android:versionName="2024.02.0"> diff --git a/src/main.cpp b/src/main.cpp index f1012e8..0b3d802 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.01.3"); + qapp.setApplicationVersion("2024.02.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 63e7f988f66c3e9e5d2bbe7b6aa86aeb1a695fb7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Feb 2024 11:45:16 +0100 Subject: [PATCH 0838/1428] Fixlets for fiat price changes pane. --- guis/Flowee/FiatTxInfo.qml | 4 +--- guis/mobile/TransactionDetails.qml | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/FiatTxInfo.qml b/guis/Flowee/FiatTxInfo.qml index ed674de..6f5ab3d 100644 --- a/guis/Flowee/FiatTxInfo.qml +++ b/guis/Flowee/FiatTxInfo.qml @@ -44,8 +44,6 @@ GridLayout { } visible: { - if (txInfo === null) - return false; if (txInfo.minedHeight < 1) return false; if (txInfo.isFused) @@ -87,7 +85,7 @@ GridLayout { property int fiatPrice: { if (txInfo == null) return 0; - Fiat.historicalPriceAccurate(txInfo.date) + return Fiat.historicalPriceAccurate(txInfo.date) } text: Fiat.formattedPrice(Math.abs(root.amountBch), fiatPrice) } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 3db49fd..3029857 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -146,7 +146,9 @@ Page { } PageTitledBox { + visible: fiatPrices.visible Flowee.FiatTxInfo { + id: fiatPrices txInfo: root.transaction width: parent.width } -- 2.54.0 From 2ebe29772d412430c6c6e873ff36883d15c81ef8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Feb 2024 11:51:15 +0100 Subject: [PATCH 0839/1428] Fix width indicator. --- guis/desktop/TransactionDetails.qml | 5 ++++- guis/mobile/TransactionDetails.qml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 19322fa..97e1180 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -230,7 +230,10 @@ QQC2.ApplicationWindow { height: { if (modelData === null) return 6; - if (inAddress.implicitWidth + 10 + amount.implicitWidth < content.width) + var neededWidth = inAddress.implicitWidth + 10 + amount.implicitWidth; + if (fusedIcon.visible) + neededWidth += 6 + fusedIcon.width + if (neededWidth < content.width) return inAddress.height + 10; return inAddress.height + amount.height + 16; } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 3029857..f68fd30 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -206,7 +206,10 @@ Page { Layout.alignment: Qt.AlignRight width: content.width height: { - if (inAddress.implicitWidth + 10 + amount.implicitWidth < content.width) + var neededWidth = inAddress.implicitWidth + 10 + amount.implicitWidth; + if (fusedIcon.visible) + neededWidth += 6 + fusedIcon.width + if (neededWidth < content.width) return inAddress.height + 10; return inAddress.height + amount.height + 16; } -- 2.54.0 From f119fd788cf804259c4f80595612c546fd8ffeb0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Feb 2024 12:10:09 +0100 Subject: [PATCH 0840/1428] Make the amount be according to user settings again Must have left it commented out after the refactor, this brings back the existing functionality to follow the user setting and only show fiat price. --- guis/desktop/Transaction.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index c146b1c..e28ca62 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -130,7 +130,7 @@ Rectangle { Flowee.BitcoinAmountLabel { id: bitcoinAmountLabel - // visible: Pay.activityShowsBch || !Pay.isMainChain + visible: Pay.activityShowsBch || !Pay.isMainChain fiatTimestamp: model.date value: { let inputs = model.fundsIn @@ -147,7 +147,7 @@ Rectangle { Flowee.Label { anchors.top: mainLabel.top anchors.right: parent.right - // visible: bitcoinAmountLabel.visible === false + visible: bitcoinAmountLabel.visible === false text: { var fiatPrice = Fiat.historicalPrice(model.date); return Fiat.formattedPrice(bitcoinAmountLabel.value, fiatPrice) -- 2.54.0 From 032f20d2ddbdfba8e89f82010e1cf803ea1a3389 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 5 Mar 2024 22:07:09 +0100 Subject: [PATCH 0841/1428] On Desktop, show tooltip with exact mined date. If the timestamp is something vague like "half an hour ago" then the tooltip will show the to-the-minute correct timestamp. --- guis/desktop/TransactionInfoSmall.qml | 20 +++++++++++++++++++- src/FloweePay.cpp | 12 +++++++++--- src/FloweePay.h | 8 ++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index a9cd352..7b102a2 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -63,7 +63,6 @@ Item { text: qsTr("Mined") + ":" } Flowee.Label { - Layout.fillWidth: true visible: root.minedHeight > 0 text: { if (root.minedHeight <= 0) @@ -75,6 +74,25 @@ Item { rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; return rc; } + + property string toolTipText: { + if (root.minedHeight <= 0) + return ""; + var loc = Qt.locale(); + var format = loc.dateTimeFormat(Locale.ShortFormat); + return loc.toString(Pay.blockTime(root.minedHeight), format); + } + + QQC2.ToolTip.delay: 800 + QQC2.ToolTip.visible: hoverArea.containsMouse + QQC2.ToolTip.text: toolTipText + MouseArea { + id: hoverArea + enabled: parent.text !== parent.toolTipText + anchors.fill: parent + hoverEnabled: true + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + } } // value line diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 094b127..ced0581 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -707,12 +707,18 @@ QString FloweePay::formatDateTime(QDateTime date) const return date.toString(format); } -QString FloweePay::formatBlockTime(int blockHeight) const +QDateTime FloweePay::blockTime(int blockHeight) const { + assert(blockHeight > 0); // wrap this for convenience and also ensure that we never return an insanely old // date (1970) just because we lack blockheader data. - return formatDateTime(QDateTime::fromSecsSinceEpoch(std::max(1250000000, - m_downloadManager->blockchain().block(blockHeight).nTime))); + return QDateTime::fromSecsSinceEpoch(std::max(1250000000, + m_downloadManager->blockchain().block(blockHeight).nTime)); +} + +QString FloweePay::formatBlockTime(int blockHeight) const +{ + return formatDateTime(blockTime(blockHeight)); } Wallet *FloweePay::createWallet(const QString &name) diff --git a/src/FloweePay.h b/src/FloweePay.h index a9cbc0b..514e6f8 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -150,8 +150,16 @@ public: static QString amountToString(qint64 price, UnitOfBitcoin unit); Q_INVOKABLE QString formatDate(QDateTime date) const; + /** + * Returns a user-friendly formatting of the given date-time. + * This incudes us returning words (translated) like "today" or "an hour ago". + */ Q_INVOKABLE QString formatDateTime(QDateTime datetime) const; + /** + * Returns the formatDateTime() formatted date of the blocks timestamp. + */ Q_INVOKABLE QString formatBlockTime(int blockHeight) const; + Q_INVOKABLE QDateTime blockTime(int blockHeight) const; /// create a new HD wallet with an optional name. Q_INVOKABLE NewWalletConfig* createNewWallet(const QString &derivationPath, const QString &password = QString(), const QString &walletName = QString()); -- 2.54.0 From 53ac4a2592cc612020e01fc130226419a5ac9470 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Mar 2024 14:04:00 +0100 Subject: [PATCH 0842/1428] [desktop] Disable showing of clipboard icons until hover --- guis/Flowee/AddressLabel.qml | 8 +++++--- guis/desktop/TransactionDetails.qml | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml index 5eccf4b..76d1166 100644 --- a/guis/Flowee/AddressLabel.qml +++ b/guis/Flowee/AddressLabel.qml @@ -22,12 +22,14 @@ QQC2.Control { id: root required property QtObject txInfo; property alias highlight: highlight.visible + property alias showCopyIcon: copyIcon.visible visible: txInfo !== null width: implicitWidth height: implicitHeight implicitHeight: Math.max(theLabel.implicitHeight, copyIcon.height) - implicitWidth: theLabel.implicitWidth + 10 + copyIcon.width + implicitWidth: theLabel.implicitWidth + + (copyIcon.visible ? (copyIcon.width + 10) : 0) Label { background: Rectangle { @@ -36,7 +38,7 @@ QQC2.Control { radius: height / 3 opacity: 0.2 } - width: parent.width - copyIcon.width - 10 + width: parent.width - (copyIcon.visible ? (copyIcon.width + 10) : 0) height: parent.height id: theLabel @@ -52,7 +54,7 @@ QQC2.Control { return root.txInfo.address; } font.pixelSize: { - if (text == root.txInfo.cloakedAddress) + if (text === root.txInfo.cloakedAddress) return root.font.pixelSize; return root.font.pixelSize * 0.9; } diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 97e1180..e63ca60 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -246,6 +246,7 @@ QQC2.ApplicationWindow { txInfo: modelData x: fusedIcon.visible ? fusedIcon.width + 6 : 0 width: Math.min(implicitWidth, parent.width - (fusedIcon.visible ? fusedIcon.width: 0)) + showCopyIcon: false; } Flowee.BitcoinAmountLabel { id: amount @@ -256,6 +257,14 @@ QQC2.ApplicationWindow { anchors.bottom: parent.bottom anchors.bottomMargin: 10 } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: inAddress.showCopyIcon = true; + onExited: inAddress.showCopyIcon = false; + propagateComposedEvents: true; + } } } @@ -287,6 +296,7 @@ QQC2.ApplicationWindow { txInfo: modelData highlight: modelData.forMe width: Math.min(implicitWidth, parent.width) + showCopyIcon: false; } Flowee.BitcoinAmountLabel { @@ -298,6 +308,14 @@ QQC2.ApplicationWindow { anchors.bottom: parent.bottom anchors.bottomMargin: 10 } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: outAddress.showCopyIcon = true; + onExited: outAddress.showCopyIcon = false; + propagateComposedEvents: true; + } } } } -- 2.54.0 From d614ebe83646a949f9110566282764525db2ad19 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 6 Mar 2024 14:04:41 +0100 Subject: [PATCH 0843/1428] Fix warning. --- guis/mobile/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index e8ab524..7704e94 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -36,10 +36,10 @@ ApplicationWindow { property bool isLoading: typeof portfolio === "undefined"; onIsLoadingChanged: { - portfolio.limitedArchiveView = true; // only load our UI when the p2p layer is loaded and all // variables are available. if (!isLoading) { + portfolio.limitedArchiveView = true; thePile.replace("./MainView.qml"); if (!portfolio.current.isUserOwned) thePile.push("./StartupScreen.qml"); -- 2.54.0 From e34c4492fc9ac815e38943f245538ddcbfb4dc2a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 7 Mar 2024 10:35:09 +0100 Subject: [PATCH 0844/1428] Cleanup --- guis/Flowee/CFIcon.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/Flowee/CFIcon.qml b/guis/Flowee/CFIcon.qml index 6314eab..db0db41 100644 --- a/guis/Flowee/CFIcon.qml +++ b/guis/Flowee/CFIcon.qml @@ -3,7 +3,6 @@ import QtQuick.Controls as QQC2 Image { id: fusedIcon - visible: fusedCount > 0 source: "qrc:/cf.svg" width: 24 height: 24 -- 2.54.0 From 7b5ed942984ed7256ee5ad15181bbb272c66ad7e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 7 Mar 2024 10:35:45 +0100 Subject: [PATCH 0845/1428] Avoid null pointer warning --- guis/Flowee/FiatTxInfo.qml | 2 ++ guis/mobile/TransactionDetails.qml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/FiatTxInfo.qml b/guis/Flowee/FiatTxInfo.qml index 6f5ab3d..62012a6 100644 --- a/guis/Flowee/FiatTxInfo.qml +++ b/guis/Flowee/FiatTxInfo.qml @@ -44,6 +44,8 @@ GridLayout { } visible: { + if (txInfo === null) + return true; if (txInfo.minedHeight < 1) return false; if (txInfo.isFused) diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index f68fd30..20c5238 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -175,7 +175,7 @@ Page { } Flowee.BitcoinAmountLabel { value: root.infoObject == null ? 0 : infoObject.fees - fiatTimestamp: root.transaction.date + fiatTimestamp: root.transaction === null ? undefined : root.transaction.date colorize: false } } -- 2.54.0 From bde68cc7bc3dfcf1256de16cede0a4e3c6352a76 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 7 Mar 2024 10:41:41 +0100 Subject: [PATCH 0846/1428] Reach balance; add CT icon For inputs we added fusion icons, for outputs we now have CashToken icons that show up when applicable. --- guis/desktop/TransactionDetails.qml | 15 +++++++++++++-- guis/mobile/TransactionDetails.qml | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index e63ca60..be78818 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -287,15 +287,26 @@ QQC2.ApplicationWindow { Layout.alignment: Qt.AlignRight width: content.width height: { - if (outAddress.implicitWidth + 10 + outAmount.implicitWidth < width) + var neededWidth = outAddress.implicitWidth + 10 + outAmount.implicitWidth; + if (ctIcon.visible) + neededWidth += 6 + ctIcon.width + if (neededWidth < width) return outAmount.height + 10; return outAddress.height + outAmount.height + 16; } + Image { + id: ctIcon + visible: modelData.hasCashToken + source: "qrc:/CashTokens.svg"; + width: 24 + height: 24 + } Flowee.AddressLabel { id: outAddress txInfo: modelData highlight: modelData.forMe - width: Math.min(implicitWidth, parent.width) + x: ctIcon.visible ? ctIcon.width + 6 : 0 + width: Math.min(implicitWidth, parent.width - (ctIcon.visible ? ctIcon.width: 0)) showCopyIcon: false; } diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 20c5238..d7510b6 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -254,15 +254,27 @@ Page { Layout.alignment: Qt.AlignRight width: content.width height: { - if (outAddress.implicitWidth + 10 + outAmount.implicitWidth < width) + var neededWidth = outAddress.implicitWidth + 10 + outAmount.implicitWidth; + if (ctIcon.visible) + neededWidth += 6 + ctIcon.width + if (neededWidth < width) return outAmount.height + 10; return outAddress.height + outAmount.height + 16; + + } + Image { + id: ctIcon + visible: modelData.hasCashToken + source: "qrc:/CashTokens.svg"; + width: 24 + height: 24 } Flowee.AddressLabel { id: outAddress txInfo: modelData + x: ctIcon.visible ? ctIcon.width + 6 : 0 highlight: modelData.forMe - width: Math.min(implicitWidth, parent.width) + width: Math.min(implicitWidth, parent.width - (ctIcon.visible ? ctIcon.width: 0)) } Flowee.BitcoinAmountLabel { -- 2.54.0 From dbed7c7a697d7f4e233dde576edef91f219de0d4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 13 Mar 2024 11:47:38 +0100 Subject: [PATCH 0847/1428] Reject transactions of v >= 10 This is purely a future-proofing measure, don't accept transactions that are likely to be handled differently than todays transactions. Since the May 2023 upgrade versions other than 1 or two are not allowed on-chain, but before that some version 4 transactions were mined (and some negative numbered ones longer ago). Talks about a version 10 transaction has started, so make sure this software, if it is still running at someone's machine, will already reject higher version transactions because we can't know if they are compatible or not. --- src/Wallet.cpp | 7 +++++++ testing/wallet/TestWallet.cpp | 34 ++++++++++++++++++++++++++++++++++ testing/wallet/TestWallet.h | 1 + 3 files changed, 42 insertions(+) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 9f8f406..768143e 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -202,6 +202,13 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co } } } + else if (iter.tag() == Tx::TxVersion) { + if (iter.intData() >= 10) { + logCritical(LOG_WALLET) << "Transaction of too high version found, is this installation old?"; + // as version is the first field, this insta-rejects the transaction. + return wtx; + } + } } if (wtx.isCashFusionTx) { qint64 sats = 0; diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index 8956d90..a75e28e 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -773,6 +773,40 @@ void TestWallet::rejectTx() } } +void TestWallet::txVersion() +{ + /* + * At the time of writing this test, the network only allows a couple of versions of transactions. + * In the future a new upgrade may happen that supports a new transaction version, but then today's + * software is not going to be able to parse it correctly. As such we should reject the transaction + * until such a time that the code has been changed to support this specific future version. + */ + auto wallet = createWallet(); + TransactionBuilder builder; + uint256 prevTxId = uint256S("0x12830924807308721309128309128"); + builder.appendInput(prevTxId, 0); + builder.appendOutput(1000000); + builder.pushOutputPay2Address(wallet->nextUnusedAddress()); + builder.setTransactionVersion(10); + auto pool = std::make_shared(); + Tx tx = builder.createTransaction(pool); + QCOMPARE(wallet->balanceConfirmed(), 0); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + wallet->newTransaction(tx); + QCOMPARE(wallet->balanceConfirmed(), 0); + QCOMPARE(wallet->balanceUnconfirmed(), 0); + QCOMPARE(wallet->balanceImmature(), 0); + + // valid version should get accepted. + builder.setTransactionVersion(2); + tx = builder.createTransaction(pool); + wallet->newTransaction(tx); + QCOMPARE(wallet->balanceConfirmed(), 0); + QCOMPARE(wallet->balanceUnconfirmed(), 1000000); + QCOMPARE(wallet->balanceImmature(), 0); +} + void TestWallet::testEncryption1() { { diff --git a/testing/wallet/TestWallet.h b/testing/wallet/TestWallet.h index 6f29b8a..64b69c3 100644 --- a/testing/wallet/TestWallet.h +++ b/testing/wallet/TestWallet.h @@ -51,6 +51,7 @@ private slots: void unconfirmed(); void hierarchicallyDeterministic(); void rejectTx(); + void txVersion(); void testEncryption1(); void testEncryption2(); -- 2.54.0 From 5e89ba5d41915285bf8e8899464e833b7546e07f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 13 Mar 2024 11:59:09 +0100 Subject: [PATCH 0848/1428] match comment to reality --- testing/wallet/TestWallet.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index a75e28e..6a085f2 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -213,8 +213,8 @@ void TestWallet::addingTransactions() void TestWallet::addingTransactions2() { /* - * addTransaction() is for adding unconfirmed transactions. - * addTransactions() for confirmed transactions. + * newTransaction() is for adding unconfirmed transactions. + * newTransactions() for confirmed transactions. * * The wallet may receive transactions out of order, for various reasons a first * scan may miss transactions (private keys (HD) not generated yet, for instance). -- 2.54.0 From 8f26c086a5ecc6d3bea696d7a1f82bfc9519e921 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 23 Apr 2024 09:59:15 +0200 Subject: [PATCH 0849/1428] Fix UX bug where clickable areas overlapped. This makes more clear what happens when you click on the extend of the zoom/scale row. --- guis/desktop/SettingsPane.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index d9211ff..ce15641 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -181,8 +181,9 @@ Pane { } MouseArea { anchors.fill: parent - anchors.topMargin: -30 + anchors.topMargin: -18 onClicked: Pay.fontScaling = target + cursorShape: Qt.PointingHandCursor } } } -- 2.54.0 From ab75ae136f06f014b379120e2a325bd57908932c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 23 Apr 2024 12:55:51 +0200 Subject: [PATCH 0850/1428] Start new class ImportHandler. ImportHandler can connect to an Electrum Server and check different derivation paths etc for a given seed. --- src/CMakeLists.txt | 1 + src/ImportHandler.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++ src/ImportHandler.h | 55 ++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 src/ImportHandler.cpp create mode 100644 src/ImportHandler.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfe729e..b317f48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ set (PAY_SOURCES PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp + ImportHandler.cpp PaymentProtocol.cpp PortfolioDataProvider.cpp diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp new file mode 100644 index 0000000..3ff86a8 --- /dev/null +++ b/src/ImportHandler.cpp @@ -0,0 +1,173 @@ +#include "ImportHandler.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr const char *USERAGENT = "net/useragent"; + +ImportHandler::ImportHandler(QObject *parent) + : QObject{parent}, + m_electronServer(new QSslSocket(this)) +{ + connect (m_electronServer, SIGNAL(connected()), this, SLOT(connectionEstablished())); + connect (m_electronServer, SIGNAL(disconnected()), this, SLOT(disconnected())); + connect (m_electronServer, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect (m_electronServer, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); +} + +void ImportHandler::startTest() +{ + logFatal() << "starting"; + m_nextToCheck = MainDerivation; + + m_electronServer->connectToHostEncrypted("cashnode.bch.ninja", 50002); + // m_electronServer->connectToHostEncrypted("bch.imaginary.cash", 50002); + // m_electronServer->connectToHostEncrypted("bch.loping.net", 50002); +} + +void ImportHandler::connectionEstablished() +{ + // first version we need is 1.5.2 because that is the one that introduced the + // get_first_use method. + // The second version is the highest we've tested to work. + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"server.version\"," + "\"params\": [\"%1\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); + + QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); + QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); + call = call.arg(useragent); + + m_electronServer->write(call.toUtf8()); + m_electronServer->write("\n"); +} + +void ImportHandler::disconnected() +{ + logFatal(); +} + +void ImportHandler::socketError(QAbstractSocket::SocketError error) +{ + logFatal() << error; +} + +void ImportHandler::socketDataAvailable() +{ + auto data = m_electronServer->readAll(); + logDebug() << QString::fromUtf8(data); + QJsonDocument doc; + doc = QJsonDocument::fromJson(data); + if (!doc.isObject()) { + m_electronServer->close(); + return; + } + auto o = doc.object(); + switch(o.value("id").toInt()) { + case 1: handleVersion(o); break; + case 2: handleFirstUse(o); break; + default: + // fail + logFatal() << "fail"; + return; + } +} + +void ImportHandler::handleVersion(const QJsonObject &rootObject) +{ + auto result = rootObject.value("result"); + if (!result.isArray()) { + logFatal() << "No viable protocol-version found for this server"; + // TODO mark current server as bad? + m_electronServer->close(); + return; + } + + assert(m_nextToCheck != Done); + checkNext(); +} + +void ImportHandler::handleFirstUse(const QJsonObject &rootObject) +{ + auto result_ = rootObject.value("result"); + if (!result_.isNull()) { + auto result = result_.toObject(); + auto foundOne = m_currentlyChecking; + foundOne.firstBlockHeight = result.value("block_height").toInt(-1); + if (foundOne.firstBlockHeight > 0) + m_found.append(foundOne); + } + + if (m_nextToCheck == Done) { + emit finished(m_found); + m_electronServer->close(); + return; + } + checkNext(); +} + +void ImportHandler::checkNext() +{ + HDMasterKey::MnemonicType type; + auto derivation = HDMasterKey::deriveFromString("m/44'/0'/0'/0/0"); + assert(derivation.size() == 5); + switch (m_nextToCheck) { + case MainDerivation: + type = HDMasterKey::BIP39Mnemonic; + m_nextToCheck = Derivation145; + break; + case Derivation145: + type = HDMasterKey::BIP39Mnemonic; + derivation[1] = 145; + m_nextToCheck = MainDerivationElectrumMnemonic; + break; + case MainDerivationElectrumMnemonic: + type = HDMasterKey::ElectrumMnemonic; + m_nextToCheck = Derivation145ElectrumMnemonic; + break; + case Derivation145ElectrumMnemonic: + type = HDMasterKey::ElectrumMnemonic; + derivation[1] = 145; + m_nextToCheck = Done; + break; + default: + assert(false); + return; + } + + HDMasterKey master = HDMasterKey::fromMnemonic(m_seedToCheck.toStdString(), type); + auto privKey = master.derive(derivation); + auto pkh = privKey.getPubKey().getKeyId(); + CashAddress::Content cashAddress; + cashAddress.type = CashAddress::PUBKEY_TYPE; + cashAddress.hash = std::vector(pkh.begin(), pkh.end()); + auto scriptHash = CashAddress::createHashedOutputScript(cashAddress); + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"blockchain.scripthash.get_first_use\"," + "\"params\": [\"%1\"], \"id\": 2}"); + call = call.arg(QString::fromStdString(scriptHash.toHex_reversed())); + m_currentlyChecking.derivation = derivation; + m_currentlyChecking.electrum = type == HDMasterKey::ElectrumMnemonic; + logDebug() << "json to send:" << call; + m_electronServer->write(call.toUtf8()); + m_electronServer->write("\n"); +} + +QString ImportHandler::seedToCheck() const +{ + return m_seedToCheck; +} + +void ImportHandler::setSeedToCheck(const QString &newSeedToCheck) +{ + m_seedToCheck = newSeedToCheck; +} diff --git a/src/ImportHandler.h b/src/ImportHandler.h new file mode 100644 index 0000000..20d3404 --- /dev/null +++ b/src/ImportHandler.h @@ -0,0 +1,55 @@ +#ifndef IMPORTHANDLER_H +#define IMPORTHANDLER_H + +#include + +class QSslSocket; + +class ImportHandler : public QObject +{ + Q_OBJECT +public: + explicit ImportHandler(QObject *parent = nullptr); + + void startTest(); + + QString seedToCheck() const; + void setSeedToCheck(const QString &newSeedToCheck); + + struct ImportData { + std::vector derivation; + bool electrum = false; + int firstBlockHeight = 0; + }; + +signals: + void finished(const QList &results); + +private slots: + void connectionEstablished(); + void disconnected(); + void socketError(QAbstractSocket::SocketError error); + void socketDataAvailable(); + +private: + void handleVersion(const QJsonObject &rootObject); + void handleFirstUse(const QJsonObject &rootObject); + void checkNext(); + +private: + QSslSocket *m_electronServer; + QString m_seedToCheck; + enum NextToCheck { + MainDerivation, + Derivation145, + MainDerivationElectrumMnemonic, + Derivation145ElectrumMnemonic, + Done + }; + NextToCheck m_nextToCheck = MainDerivation; + + ImportData m_currentlyChecking; + QList m_found; +}; + +#endif -- 2.54.0 From 930bedfeaa3ac31a1e3660c401748e850008d062 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 23 Apr 2024 12:58:01 +0200 Subject: [PATCH 0851/1428] Add copyright headers to new class --- src/ImportHandler.cpp | 17 +++++++++++++++++ src/ImportHandler.h | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 3ff86a8..5e5f9c4 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "ImportHandler.h" #include diff --git a/src/ImportHandler.h b/src/ImportHandler.h index 20d3404..9c425a7 100644 --- a/src/ImportHandler.h +++ b/src/ImportHandler.h @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef IMPORTHANDLER_H #define IMPORTHANDLER_H -- 2.54.0 From 7a5a9d8b412686a8e8e65efb2c8c25a7d2effdbf Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 24 Apr 2024 18:48:22 +0200 Subject: [PATCH 0852/1428] Expand ImportHandler to support privkeys etc too. --- src/ImportHandler.cpp | 122 +++++++++++++++++++++++++----------------- src/ImportHandler.h | 23 ++++++-- 2 files changed, 93 insertions(+), 52 deletions(-) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 5e5f9c4..f69f428 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include constexpr const char *USERAGENT = "net/useragent"; @@ -41,10 +43,16 @@ ImportHandler::ImportHandler(QObject *parent) connect (m_electronServer, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); } -void ImportHandler::startTest() +void ImportHandler::startCheck(WalletType type, const QString &text, const QString &password) { - logFatal() << "starting"; m_nextToCheck = MainDerivation; + m_type = type; + assert(m_type >= FromSeed && m_type <= FromXPub); + m_input = text; + assert(!m_input.isEmpty()); + m_password = password; + m_found.clear(); + m_currentlyChecking = ImportData(); m_electronServer->connectToHostEncrypted("cashnode.bch.ninja", 50002); // m_electronServer->connectToHostEncrypted("bch.imaginary.cash", 50002); @@ -81,7 +89,6 @@ void ImportHandler::socketError(QAbstractSocket::SocketError error) void ImportHandler::socketDataAvailable() { auto data = m_electronServer->readAll(); - logDebug() << QString::fromUtf8(data); QJsonDocument doc; doc = QJsonDocument::fromJson(data); if (!doc.isObject()) { @@ -94,7 +101,6 @@ void ImportHandler::socketDataAvailable() case 2: handleFirstUse(o); break; default: // fail - logFatal() << "fail"; return; } } @@ -134,57 +140,77 @@ void ImportHandler::handleFirstUse(const QJsonObject &rootObject) void ImportHandler::checkNext() { - HDMasterKey::MnemonicType type; - auto derivation = HDMasterKey::deriveFromString("m/44'/0'/0'/0/0"); - assert(derivation.size() == 5); - switch (m_nextToCheck) { - case MainDerivation: - type = HDMasterKey::BIP39Mnemonic; - m_nextToCheck = Derivation145; - break; - case Derivation145: - type = HDMasterKey::BIP39Mnemonic; - derivation[1] = 145; - m_nextToCheck = MainDerivationElectrumMnemonic; - break; - case MainDerivationElectrumMnemonic: - type = HDMasterKey::ElectrumMnemonic; - m_nextToCheck = Derivation145ElectrumMnemonic; - break; - case Derivation145ElectrumMnemonic: - type = HDMasterKey::ElectrumMnemonic; - derivation[1] = 145; - m_nextToCheck = Done; - break; - default: - assert(false); - return; + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"blockchain.scripthash.get_first_use\"," + "\"params\": [\"%1\"], \"id\": 2}"); + + const P2PNet::Chain chain = FloweePay::instance()->chain(); + PublicKey pubKey; + if (m_type == FromPrivateKey) { + CBase58Data legacy; + if (legacy.SetString(m_input.toStdString()) + && ((chain == P2PNet::MainChain && legacy.isMainnetPrivKey()) + || (chain == P2PNet::Testnet4Chain && legacy.isTestnetPrivKey()))) { + PrivateKey privKey; + privKey.set(legacy.data().begin(), legacy.data().end(), legacy.data().size() == 32); + pubKey = privKey.getPubKey(); + } + } + else { + HDMasterKey::MnemonicType type; + auto derivation = HDMasterKey::deriveFromString("m/44'/0'/0'/0/0"); + assert(derivation.size() == 5); + switch (m_nextToCheck) { + case MainDerivation: + type = HDMasterKey::BIP39Mnemonic; + m_nextToCheck = Derivation145; + break; + case Derivation145: + type = HDMasterKey::BIP39Mnemonic; + derivation[1] = 145; + m_nextToCheck = m_type == FromSeed ? MainDerivationElectrumMnemonic : Done; + break; + case MainDerivationElectrumMnemonic: + type = HDMasterKey::ElectrumMnemonic; + m_nextToCheck = Derivation145ElectrumMnemonic; + break; + case Derivation145ElectrumMnemonic: + type = HDMasterKey::ElectrumMnemonic; + derivation[1] = 145; + m_nextToCheck = Done; + break; + default: + assert(false); + return; + } + m_currentlyChecking.derivation = derivation; + m_currentlyChecking.electrum = type == HDMasterKey::ElectrumMnemonic; + + if (m_type == FromXPub) { + HDMasterPubkey xpub = HDMasterPubkey::fromXPub(m_input.toStdString()); + pubKey = xpub.derive(derivation); + if (xpub.level() > 2) + m_nextToCheck = Done; + } + else if (m_type == FromSeed) { + HDMasterKey master = HDMasterKey::fromMnemonic(m_input.toStdString(), type); + auto privKey = master.derive(derivation); + pubKey = privKey.getPubKey(); + } } - HDMasterKey master = HDMasterKey::fromMnemonic(m_seedToCheck.toStdString(), type); - auto privKey = master.derive(derivation); - auto pkh = privKey.getPubKey().getKeyId(); + if (!pubKey.isValid()) { + // TODO report error + emit finished(m_found); + m_electronServer->close(); + return; + } + auto pkh = pubKey.getKeyId(); CashAddress::Content cashAddress; cashAddress.type = CashAddress::PUBKEY_TYPE; cashAddress.hash = std::vector(pkh.begin(), pkh.end()); auto scriptHash = CashAddress::createHashedOutputScript(cashAddress); - QString call("{\"jsonrpc\":\"2.0\"," - "\"method\":\"blockchain.scripthash.get_first_use\"," - "\"params\": [\"%1\"], \"id\": 2}"); call = call.arg(QString::fromStdString(scriptHash.toHex_reversed())); - m_currentlyChecking.derivation = derivation; - m_currentlyChecking.electrum = type == HDMasterKey::ElectrumMnemonic; - logDebug() << "json to send:" << call; m_electronServer->write(call.toUtf8()); m_electronServer->write("\n"); } - -QString ImportHandler::seedToCheck() const -{ - return m_seedToCheck; -} - -void ImportHandler::setSeedToCheck(const QString &newSeedToCheck) -{ - m_seedToCheck = newSeedToCheck; -} diff --git a/src/ImportHandler.h b/src/ImportHandler.h index 9c425a7..2361f1c 100644 --- a/src/ImportHandler.h +++ b/src/ImportHandler.h @@ -28,10 +28,19 @@ class ImportHandler : public QObject public: explicit ImportHandler(QObject *parent = nullptr); - void startTest(); + enum WalletType { + FromSeed, + FromPrivateKey, + FromXPub + // FromXPriv, TODO + }; - QString seedToCheck() const; - void setSeedToCheck(const QString &newSeedToCheck); + /** + * Find the import details for a verified to be correct type/text. + * + * This method returns instantly and will emit finished() when checking has completed. + */ + void startCheck(WalletType type, const QString &text, const QString &password = QString()); struct ImportData { std::vector derivation; @@ -55,7 +64,13 @@ private: private: QSslSocket *m_electronServer; - QString m_seedToCheck; + QString m_input; + QString m_password; // seeds may have a password + + // what is the Input supposed to re-present? + WalletType m_type; + + // for seeds, we need to check multiple options, we iterate through these. enum NextToCheck { MainDerivation, Derivation145, -- 2.54.0 From 6b31d468924e09a48b745d656a77599d0bae98ee Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 27 Apr 2024 20:34:06 +0200 Subject: [PATCH 0853/1428] Add IndexerServics to find electrum indexers This does not hardcode any indexers, as that would be fragile as well as painting a bulls-eye on the backs of the server operators for ddos targets. Instead this reuses the same concept from Satoshi's Bitcoin. We have DNS feeds (currently only one) maintained by a crawler. This finds us a handful of Electrum servers. We connect to one of those to find more servers, notice that this is not SSL encrypted because we only have an IP address at that time while SSL wants a hostname. This quick connection over plainnet is there just to fetch the server list known to the server, we can then open an SSL connection to any of those. --- src/CMakeLists.txt | 3 +- src/ImportHandler.cpp | 12 +- src/IndexerServices.cpp | 372 ++++++++++++++++++++++++++++++++++++++++ src/IndexerServices.h | 71 ++++++++ src/IndexerServices_p.h | 51 ++++++ 5 files changed, 503 insertions(+), 6 deletions(-) create mode 100644 src/IndexerServices.cpp create mode 100644 src/IndexerServices.h create mode 100644 src/IndexerServices_p.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b317f48..088d41f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,8 @@ set (PAY_SOURCES AddressInfo.cpp BitcoinValue.cpp FloweePay.cpp + ImportHandler.cpp + IndexerServices.cpp MenuModel.cpp NetDataProvider.cpp NewWalletConfig.cpp @@ -30,7 +32,6 @@ set (PAY_SOURCES PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp - ImportHandler.cpp PaymentProtocol.cpp PortfolioDataProvider.cpp diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index f69f428..139982f 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -54,7 +54,8 @@ void ImportHandler::startCheck(WalletType type, const QString &text, const QStri m_found.clear(); m_currentlyChecking = ImportData(); - m_electronServer->connectToHostEncrypted("cashnode.bch.ninja", 50002); + // m_electronServer->connectToHostEncrypted("cashnode.bch.ninja", 50002); + m_electronServer->connectToHostEncrypted("173.249.11.35", 50002); // m_electronServer->connectToHostEncrypted("bch.imaginary.cash", 50002); // m_electronServer->connectToHostEncrypted("bch.loping.net", 50002); } @@ -66,11 +67,11 @@ void ImportHandler::connectionEstablished() // The second version is the highest we've tested to work. QString call("{\"jsonrpc\":\"2.0\"," "\"method\":\"server.version\"," - "\"params\": [\"%1\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); + "\"params\": [\"Electron Cash 4.2.12\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); - QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); - QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); - call = call.arg(useragent); +// QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); +// QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); +// call = call.arg(useragent); m_electronServer->write(call.toUtf8()); m_electronServer->write("\n"); @@ -89,6 +90,7 @@ void ImportHandler::socketError(QAbstractSocket::SocketError error) void ImportHandler::socketDataAvailable() { auto data = m_electronServer->readAll(); + logFatal() << QString::fromLatin1(data); QJsonDocument doc; doc = QJsonDocument::fromJson(data); if (!doc.isObject()) { diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp new file mode 100644 index 0000000..3844018 --- /dev/null +++ b/src/IndexerServices.cpp @@ -0,0 +1,372 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "IndexerServices.h" +#include "IndexerServices_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { +// for the save-file +enum ServicesTags { + Separator, + Hostname, + IPAddress, + PortNum, + SecurePortNum, + ProtocolVersion, + Punishment +}; + + +constexpr const char * FILENAME = "/electrum.dat"; +} + + +IndexerServices::IndexerServices(const QString &basedir, boost::asio::io_service &ioService, QObject *parent) + : QObject{parent}, + m_resolver(ioService), + m_basedir(basedir) +{ + connect (this, SIGNAL(startMaybeFindServices()), this, SLOT(maybeFindServices()), Qt::QueuedConnection); + + load(); +} + +void IndexerServices::populate() +{ + tcp::resolver::query query("ec-seed.flowee.cash", "http"); + m_resolver.async_resolve(query, std::bind(&IndexerServices::onSeedLookupComplete, + this, std::placeholders::_1, std::placeholders::_2)); +} + +void IndexerServices::load() +{ + m_knownServices.clear(); + const QString filebase = m_basedir + FILENAME; + QFile in(filebase); + if (!in.open(QIODevice::ReadOnly)) + return; + + const auto dataSize = in.size(); + auto pool = Streaming::pool(dataSize); + in.read(pool->begin(), dataSize); + Streaming::MessageParser parser(pool->commit(dataSize)); + Indexer indexItem; + while (parser.next() == Streaming::FoundTag) { + switch (parser.tag()) { + case Separator: + m_knownServices.push_back(indexItem); + indexItem = Indexer(); + break; + case Hostname: + indexItem.hostname = QString::fromStdString(parser.stringData()); + break; + case IPAddress: + indexItem.ip = QString::fromStdString(parser.stringData()); + break; + case PortNum: + indexItem.port = parser.intData(); + break; + case SecurePortNum: + indexItem.securePort = parser.intData(); + break; + case ProtocolVersion: + indexItem.protocolVersion = parser.longData(); + break; + case Punishment: + indexItem.punishment = parser.intData(); + break; + } + } +} + +void IndexerServices::save() +{ + auto buffer = std::make_shared(m_knownServices.size() * 1000); + Streaming::MessageBuilder builder(buffer); + for (const auto &service : m_knownServices) { + builder.add(Hostname, service.hostname.toStdString()); + builder.add(IPAddress, service.ip.toStdString()); + builder.add(PortNum, service.port); + builder.add(SecurePortNum, service.securePort); + builder.add(ProtocolVersion, (uint64_t) service.protocolVersion); + builder.add(Punishment, service.punishment); + builder.add(Separator, false); + } + auto data = builder.buffer(); + + const QString filebase = m_basedir + FILENAME; + QFile origFile(filebase); + if (origFile.open(QIODevice::ReadOnly)) { + CRIPEMD160 fileHasher; + auto origContent = origFile.readAll(); + fileHasher.write(origContent.data(), origContent.size()); + char fileHash[CRIPEMD160::OUTPUT_SIZE]; + fileHasher.finalize(fileHash); + + CRIPEMD160 memHasher; + memHasher.write(data.begin(), data.size()); + char memHash[CRIPEMD160::OUTPUT_SIZE]; + memHasher.finalize(memHash); + if (memcmp(fileHash, memHash, CRIPEMD160::OUTPUT_SIZE) == 0) { + // no changes, so don't write. + return; + } + } + + try { + std::string filebaseStr(filebase.toStdString()); + std::ofstream out(filebaseStr + "~"); + out.write(data.begin(), data.size()); + out.flush(); + out.close(); + std::filesystem::rename(filebaseStr + "~", filebaseStr); + } catch (const std::exception &e) { + logFatal() << "Failed to create electrum.dat file. Permissions issue?" << e; + } +} + +void IndexerServices::onSeedLookupComplete(const boost::system::error_code &error, tcp::resolver::iterator iterator) +{ + if (error) + return; + + QMutexLocker locker(&m_mutex); + tcp::resolver::iterator end; + while (iterator != end) { + const QString ip = QString::fromStdString(iterator->endpoint().address().to_string()); + ++iterator; + bool found = false; + + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->ip == ip) { + found = true; + iter->punishment = std::max(0, iter->punishment - 100); + break; + } + } + + if (!found) { + Indexer indexer; + indexer.ip = ip; + indexer.hostname = ip; + indexer.port = 50001; + m_knownServices.push_back(indexer); + } + } + + emit startMaybeFindServices(); // make sure that one is called in the main thread. +} + +void IndexerServices::maybeFindServices() +{ + assert(thread() == QThread::currentThread()); + if (m_fetcher) + return; + int goodFound = 0; + Indexer known; + QMutexLocker locker(&m_mutex); + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->punishment < 250) { + if (iter->securePort > 0) + ++goodFound; + if (iter->port != -1 && (iter->protocolVersion == 0 || iter->protocolVersion >= 0x010502)) { + // To randomly select an item from the list, replace + // with later one in 25% of the cases. + if (known.port <= 0 || (std::rand() % 4) == 0) + known = *iter; + } + } + } + if (goodFound > 10 || known.port <= 0) + return; + + m_fetcher = new FetchIndexerServicePeers(known.ip, this); + connect (m_fetcher, &FetchIndexerServicePeers::finished, this, [=]() { + QMutexLocker locker(&m_mutex); + assert(m_fetcher); + if (m_fetcher->failScore()) { // the remote didn't follow the protocol properly, down prioritize. + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->ip == known.ip) { + iter->punishment += m_fetcher->failScore(); + break; + } + } + } + + for (const auto &server : m_fetcher->servicesFound()) { + bool found = false; + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->hostname == server.hostname || iter->ip == server.ip) { + found = true; + iter->hostname = server.hostname; + iter->ip = server.ip; + iter->port = server.port; + iter->securePort = server.securePort; + iter->protocolVersion = server.protocolVersion; + break; + } + } + if (!found) + m_knownServices.push_back(server); + } + m_fetcher->deleteLater(); + m_fetcher = nullptr; + + save(); + + // run it again, to check we have enough service providers. + QTimer::singleShot(1000, this, SLOT(maybeFindServices())); + }); +} + + +// --------------------------------------------------------- + +FetchIndexerServicePeers::FetchIndexerServicePeers(const QString &server, QObject *parent) + : QObject(parent), + m_remote(new QTcpSocket(this)) +{ + m_remote->connectToHost(server, 50001); + + connect (m_remote, SIGNAL(connected()), this, SLOT(connectionEstablished())); + connect (m_remote, SIGNAL(disconnected()), this, SLOT(disconnected())); + connect (m_remote, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect (m_remote, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); +} + +std::deque FetchIndexerServicePeers::servicesFound() const +{ + return m_result; +} + +void FetchIndexerServicePeers::connectionEstablished() +{ + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"server.peers.subscribe\"," + "\"params\":[], \"id\": 314}"); + + m_remote->write(call.toUtf8()); + m_remote->write("\n"); +} + +void FetchIndexerServicePeers::disconnected() +{ + logFatal(); +} + +void FetchIndexerServicePeers::socketError(QAbstractSocket::SocketError error) +{ + logFatal() << error; +} + +void FetchIndexerServicePeers::socketDataAvailable() +{ + auto data = m_remote->readAll(); + QJsonDocument doc; + doc = QJsonDocument::fromJson(data); + if (!doc.isObject()) { + m_remote->close(); + m_failScore = 500; // invalid json + emit finished(); + return; + } + auto o = doc.object(); + if (o.value("id").toInt() == 314) { + std::deque newServices; + auto result_ = o.value("result"); + if (!result_.isArray()) { + m_failScore = 500; // invalid json + emit finished(); + return; + } + auto result = result_.toArray(); + for (int serverIndex = 0; serverIndex < result.count(); ++serverIndex) { + auto server_ = result.at(serverIndex); + if (server_.isArray()) { + auto server = server_.toArray(); + if (server.count() != 3) + continue; + if (!server.at(2).isArray()) + continue; + + IndexerServices::Indexer indexer; + indexer.ip = server.at(0).toString(); + indexer.hostname = server.at(1).toString(); + if (indexer.hostname.size() > 750) // too long + continue; + auto params = server.at(2).toArray(); + for (int i = 0; i < params.count(); ++i) { + QString dat = params.at(i).toString(); + if (dat.startsWith('v')) { + auto versionNumbers = dat.mid(1).split('.'); + while (versionNumbers.size() < 3) versionNumbers.append("0"); + if (versionNumbers.size() < 5) { + for (const auto &ver : versionNumbers) { + uint32_t intValue = ver.toInt(); + indexer.protocolVersion = indexer.protocolVersion << 8; + if (intValue < 256) + indexer.protocolVersion += intValue; + } + } + } + else if (dat.startsWith('t')) { + if (dat.length() > 1) + indexer.port = dat.mid(1).toInt(); + else + indexer.port = 50001; + } + else if (dat.startsWith('s')) { + if (dat.length() > 1) + indexer.securePort = dat.mid(1).toInt(); + else + indexer.securePort = 50002; + } + } + try { + auto ip = boost::asio::ip::address::from_string(indexer.ip.toStdString()); + Q_UNUSED(ip); + // the above throws if not a real IP (but an onion for instance) so we filter our + // input based on that simple metric. + newServices.push_back(indexer); + } catch (...) {} + } + } + m_result = newServices; + + emit finished(); + } +} + +int FetchIndexerServicePeers::failScore() const +{ + return m_failScore; +} diff --git a/src/IndexerServices.h b/src/IndexerServices.h new file mode 100644 index 0000000..c026a09 --- /dev/null +++ b/src/IndexerServices.h @@ -0,0 +1,71 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef INDEXERSERVICES_H +#define INDEXERSERVICES_H + +#include +#include +#include + +#include + +#include +using boost::asio::ip::tcp; + +class FetchIndexerServicePeers; + +class IndexerServices : public QObject +{ + Q_OBJECT +public: + explicit IndexerServices(const QString &basedir, boost::asio::io_service &ioService, QObject *parent = nullptr); + + void populate(); + + struct Indexer { + QString hostname; + QString ip; + int port = -1; + int securePort = -1; + uint32_t protocolVersion = 0; + + int punishment = 0; + }; + + void save(); + +signals: + void startMaybeFindServices(); + +private slots: + void maybeFindServices(); + +private: + void load(); + + void onSeedLookupComplete(const boost::system::error_code &error, tcp::resolver::iterator iterator); + + std::deque m_knownServices; + tcp::resolver m_resolver; + mutable QMutex m_mutex; + QString m_basedir; + + FetchIndexerServicePeers *m_fetcher = nullptr; +}; + +#endif diff --git a/src/IndexerServices_p.h b/src/IndexerServices_p.h new file mode 100644 index 0000000..4167dc8 --- /dev/null +++ b/src/IndexerServices_p.h @@ -0,0 +1,51 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef INDEXERSERVICES_P_H +#define INDEXERSERVICES_P_H + + +#include "IndexerServices.h" +#include +#include + +class FetchIndexerServicePeers : public QObject +{ + Q_OBJECT +public: + FetchIndexerServicePeers(const QString &server, QObject *parent = nullptr); + + std::deque servicesFound() const; + + int failScore() const; + +signals: + void finished(); + +private slots: + void connectionEstablished(); + void disconnected(); + void socketError(QAbstractSocket::SocketError error); + void socketDataAvailable(); + +private: + QTcpSocket *m_remote; + std::deque m_result; + int m_failScore = 0; +}; + +#endif -- 2.54.0 From e7c937cae17092cb7df08f3e2837532f1b71c460 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 27 Apr 2024 21:03:28 +0200 Subject: [PATCH 0854/1428] Integrate into the app singleton --- src/FloweePay.cpp | 11 ++++++++++- src/FloweePay.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index ced0581..d3b9362 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -20,6 +20,7 @@ #include "NewWalletConfig.h" #include "AddressInfo.h" #include "PriceDataProvider.h" +#include "IndexerServices.h" #include #include @@ -139,7 +140,8 @@ QString joinWords(const QList &words, bool lowercaseFirstWord) FloweePay::FloweePay() : m_chain(s_chain), - m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)) + m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), + m_indexerServices(new IndexerServices(m_basedir, ioService(), this)) { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); @@ -169,6 +171,7 @@ FloweePay::FloweePay() saveAll(); p2pNet()->saveData(); saveData(); + m_indexerServices->save(); } } else if (state == Qt::ApplicationActive) { @@ -316,6 +319,7 @@ void FloweePay::shutdown() { p2pNet()->shutdown(); saveData(); + m_indexerServices->save(); auto *dl = m_downloadManager.get(); if (dl) // p2pNet follows lazy initialization. @@ -909,6 +913,11 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +IndexerServices *FloweePay::indexerServices() const +{ + return m_indexerServices; +} + bool FloweePay::skinFollowsPlatform() const { return m_skinFollowsPlatform; diff --git a/src/FloweePay.h b/src/FloweePay.h index 514e6f8..0eaf641 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -39,6 +39,7 @@ class NewWalletConfig; class KeyId; class PriceDataProvider; class CameraController; +class IndexerServices; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); @@ -335,6 +336,8 @@ public: QString paymentProtocolRequest() const; void setPaymentProtocolRequest(const QString &newPaymentProtocolRequest); + IndexerServices *indexerServices() const; + signals: void loadComplete(); /// \internal @@ -394,6 +397,7 @@ private: std::unique_ptr m_prices; NotificationManager m_notifications; CameraController* m_cameraController; + IndexerServices *m_indexerServices; // the Electrum indexer index. QList m_wallets; QHash m_accountConfigs; // key is wallet-segment-id int m_dspTimeout = 5000; -- 2.54.0 From 0ac472f0af977cf0eb5c29fb314228203ef02f98 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 Apr 2024 11:20:41 +0200 Subject: [PATCH 0855/1428] Allow override of the 'back' button --- guis/mobile/Page.qml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 31836ac..d82cb3b 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,6 +46,14 @@ QQC2.Control { property var menuItems: [ ] signal headerButtonClicked + /** + * Handler called when the 'back' button in the page header is called. + * Replace this with your own function if the inheriting page needs to + * do something diffrent. + * Notice that you likely to call `thePile.pop()` from your handler. + */ + property var backHandler: function handler() { thePile.pop(); } + function takeFocus() { focusScope.forceActiveFocus(); } @@ -80,7 +88,7 @@ QQC2.Control { MouseArea { anchors.fill: parent anchors.margins: -15 - onClicked: thePile.pop(); + onClicked: root.backHandler(); } } -- 2.54.0 From d3baf2495ac1677a780756f846c2957769959601 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 Apr 2024 11:56:00 +0200 Subject: [PATCH 0856/1428] Add CliboardHelper support for privatekeys + seeds. --- src/QMLClipboardHelper.cpp | 7 ++++++- src/QMLClipboardHelper.h | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index 0b6205b..2092845 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -100,6 +100,11 @@ void QMLClipboardHelper::parseClipboard() else if (m_filter.testAnyFlag(LegacyAddresses) && (type == WalletEnums::LegacyPKH || type == WalletEnums::LegacySH)) itsAHit = true; + else if (m_filter.testAnyFlag(PrivateKey) && type == WalletEnums::PrivateKey) + itsAHit = true; + else if (m_filter.testAnyFlag(MnemonicSeed) + && (type == WalletEnums::CorrectMnemonic || type == WalletEnums::ElectrumMnemonic)) + itsAHit = true; if (itsAHit) setClipboardText(text); diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index 43c1139..efdb392 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,6 +51,8 @@ public: Addresses = 1 , LegacyAddresses = 2, AddressUrl = 4, + PrivateKey = 8, + MnemonicSeed = 16, }; Q_ENUM(Type) Q_DECLARE_FLAGS(Types, Type) -- 2.54.0 From 4b47da747f4703dae3bff20dac504856d3ff3a57 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 Apr 2024 12:13:34 +0200 Subject: [PATCH 0857/1428] Add QR for the xpub --- guis/mobile/AccountPageListItem.qml | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 984b423..85fb73d 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -89,7 +89,7 @@ QQC2.Control { // non-layoutable items. QQC2.Popup { - id: seedPopup + id: qrPopup width: 270 height: 270 x: (root.width - width) / 2 @@ -138,7 +138,7 @@ QQC2.Control { anchors.fill: parent onClicked: { seedQr.qrText = root.account.mnemonic - seedPopup.open(); + qrPopup.open(); } } } @@ -169,10 +169,29 @@ QQC2.Control { PageTitledBox { title: qsTr("xpub") - Flowee.LabelWithClipboard { - text: root.account.xpub + Item { + implicitHeight: xpubLabel.implicitHeight width: parent.width - wrapMode: Text.WrapAnywhere + Flowee.LabelWithClipboard { + id: xpubLabel + text: root.account.xpub + width: parent.width - 36 + wrapMode: Text.WrapAnywhere + } + + Image { + width: 20 + height: 20 + anchors.right: parent.right + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + MouseArea { + anchors.fill: parent + onClicked: { + seedQr.qrText = root.account.xpub + qrPopup.open(); + } + } + } } } -- 2.54.0 From 25a2fce9f2c3d2ae56a2081e4d5b475037a59d2a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 Apr 2024 12:14:35 +0200 Subject: [PATCH 0858/1428] Add support for xpub strings. This allows the xpub type strings to be identified and used. Nobody uses it at this time, though. --- src/FloweePay.cpp | 9 +++++++++ src/FloweePay.h | 2 +- src/QMLClipboardHelper.cpp | 2 ++ src/QMLClipboardHelper.h | 1 + src/WalletEnums.h | 1 + 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d3b9362..6802777 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1274,6 +1274,7 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const const QString string_ = joinWords(words, false); const std::string s = string_.toStdString(); + CBase58Data legacy; if (legacy.SetString(s)) { if ((m_chain == P2PNet::MainChain && legacy.isMainnetPkh()) @@ -1295,6 +1296,14 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const return WalletEnums::CashSH; } + if (string_.startsWith("xpub")) { + try { + auto rc = HDMasterPubkey::fromXPub(s); + if (rc.isValid()) + return WalletEnums::XPub; + } catch (...) { } // fromXPub throws on faulty input. + } + return WalletEnums::Unknown; } diff --git a/src/FloweePay.h b/src/FloweePay.h index 0eaf641..ebb1f7a 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -203,7 +203,7 @@ public: Q_INVOKABLE bool checkDerivation(const QString &path) const; - /// take a bitcoin-address and identify the type. + /// take a bitcoin-related string and identify the type. Q_INVOKABLE WalletEnums::StringType identifyString(const QString &string) const; /// return a string version of the \a unit name. tBCH for instance. diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index 2092845..e94e2a1 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -105,6 +105,8 @@ void QMLClipboardHelper::parseClipboard() else if (m_filter.testAnyFlag(MnemonicSeed) && (type == WalletEnums::CorrectMnemonic || type == WalletEnums::ElectrumMnemonic)) itsAHit = true; + else if (m_filter.testAnyFlag(XPub) && type == WalletEnums::XPub) + itsAHit = true; if (itsAHit) setClipboardText(text); diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index efdb392..48f92a1 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -53,6 +53,7 @@ public: AddressUrl = 4, PrivateKey = 8, MnemonicSeed = 16, + XPub = 32, }; Q_ENUM(Type) Q_DECLARE_FLAGS(Types, Type) diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 449d9b9..af024b6 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -38,6 +38,7 @@ public: CorrectMnemonic, MissingLexicon, ElectrumMnemonic, + XPub }; Q_ENUM(StringType) -- 2.54.0 From 39b23f3725aa028b34a7950b96276307909a65f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 29 Apr 2024 17:36:24 +0200 Subject: [PATCH 0859/1428] Enhance the TestPasteDecorator This moves the component out to its own file while fixing some UX issues. * It is now always visible for (UI) discovery purposes. * When we paste when there is no matching text we show negative feedback. --- guis/mobile.qrc | 1 + guis/mobile/TextPasteDecorator.qml | 109 ++++++++++++++++++++++ modules/build-transaction/PayToOthers.qml | 71 +------------- 3 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 guis/mobile/TextPasteDecorator.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 15d45c1..b2c1264 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -89,5 +89,6 @@ mobile/UnlockWalletPanel.qml mobile/UnlockApplication.qml mobile/LockApplication.qml + mobile/TextPasteDecorator.qml diff --git a/guis/mobile/TextPasteDecorator.qml b/guis/mobile/TextPasteDecorator.qml new file mode 100644 index 0000000..5f99039 --- /dev/null +++ b/guis/mobile/TextPasteDecorator.qml @@ -0,0 +1,109 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import Flowee.org.pay + +/* + * This is a small visible item that places itself below a 'buddy' TextField or MultilineTextField + * in order to have a 'paste' button to paste one of the designated types of information from the + * clipboard. + * When the paste fails (no valid info found on clipboard), we give visual feedback. + */ +Item { + id: root + y: buddy.height - height / 3 + anchors.horizontalCenter: buddy.horizontalCenter + width: 10 + height: labelText.height + 10 + + required property Item buddy; + property alias clipboardTypes: cbh.filter + + ClipboardHelper { id: cbh } + + Rectangle { + id: errorFeedback + width: root.parent.width - 40 + height: root.parent.width / 3 + anchors.centerIn: parent + color: mainWindow.errorRedBg + radius: 25 + opacity: 0 + + Timer { + interval: 100 + running: errorFeedback.opacity > 0 + repeat: true + onTriggered: { + anim.duration = 400 + errorFeedback.opacity = 0 + } + } + + Behavior on opacity { + NumberAnimation { + id: anim + duration: 50 + easing.type: Easing.InOutQuad + } + } + } + + Rectangle { + x: parent.width / 2 - width / 2 + width: labelText.height + labelText.width + 20 + 5 + 20 + height: labelText.height + 10 + radius: 6 + color: palette.window + border.color: palette.midlight + border.width: 1 + visible: root.buddy.totalText === "" + + Image { + x: 20 + width: labelText.height + height: width + source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + anchors.verticalCenter: parent.verticalCenter + } + + Flowee.Label { + id: labelText + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 20 + text: qsTr("Paste") + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (cbh.text !== "") { + root.buddy.text = cbh.text + } else { + anim.duration = 40 + errorFeedback.opacity = 0.7 + shaker.start(); + } + } + } + + Flowee.ObjectShaker { id: shaker } + } +} diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 866aaf5..c3946a2 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -286,72 +286,6 @@ Page { return palette.windowText } } - Rectangle { - id: pasteButton - property bool appWasHidden: false - anchors.verticalCenter: destination.bottom - anchors.horizontalCenter: destination.horizontalCenter - width: labelText.height + labelText.width + 20 + 5 + 20 - height: labelText.height + 10 - radius: 6 - color: palette.window - border.color: palette.midlight - border.width: 1 - visible: destination.totalText === "" && (cbh.text !== "" || appWasHidden) - Connections { - /* - * A usecase of pasting is that the user opens this screen, goes to another app and - * copies something they want to copy and then comes back here to paste it. - * This causes the 'appWasHidden' to be set to true and we then 'enable' the clipboardHelper - * in order to read the clipboard-data. - * - * Notice that this is needed because the clipboard doesn't notice something new being - * present after we just got unfrozen. - */ - target: Application - function onActiveChanged() { - if (Application.active !== Qt.ApplicationActive) { - pasteButton.appWasHidden = true; - cbh.enabled = false; - } - } - } - - ClipboardHelper { - id: cbh - filter: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses - } - - Image { - x: 20 - width: labelText.height - height: width - source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - anchors.verticalCenter: parent.verticalCenter - } - - Flowee.Label { - id: labelText - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 20 - text: qsTr("Paste") - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (pasteButton.appWasHidden) { - pasteButton.appWasHidden = false; - cbh.enabled = true; // fills text, if there is something to fill. - } - if (cbh.text !== "") { - destination.text = cbh.text - priceInput.takeFocus(); - } - } - } - } Flowee.LabelWithClipboard { id: nativeLabel @@ -442,6 +376,11 @@ Page { } } } + TextPasteDecorator { + buddy: destination + clipboardTypes: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses + y: destination.y + destination.height - height / 3 + } } } /* -- 2.54.0 From 49b10a6842c1d5f8aba049b2471ac30f303ac4f1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 30 Apr 2024 21:42:00 +0200 Subject: [PATCH 0860/1428] Add xpriv import and detection --- src/FloweePay.cpp | 8 ++++++++ src/QMLClipboardHelper.cpp | 2 ++ src/QMLClipboardHelper.h | 1 + src/WalletEnums.h | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 6802777..1d843c6 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -1303,6 +1304,13 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const return WalletEnums::XPub; } catch (...) { } // fromXPub throws on faulty input. } + else if (string_.startsWith("xprv")) { + try { + auto rc = HDMasterKey::fromXPriv(s); + if (rc.isValid()) + return WalletEnums::XPriv; + } catch (...) { } // fromXPriv throws on faulty input. + } return WalletEnums::Unknown; } diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index e94e2a1..e0b0318 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -107,6 +107,8 @@ void QMLClipboardHelper::parseClipboard() itsAHit = true; else if (m_filter.testAnyFlag(XPub) && type == WalletEnums::XPub) itsAHit = true; + else if (m_filter.testAnyFlag(XPriv) && type == WalletEnums::XPriv) + itsAHit = true; if (itsAHit) setClipboardText(text); diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index 48f92a1..baaf833 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -54,6 +54,7 @@ public: PrivateKey = 8, MnemonicSeed = 16, XPub = 32, + XPriv = 64, }; Q_ENUM(Type) Q_DECLARE_FLAGS(Types, Type) diff --git a/src/WalletEnums.h b/src/WalletEnums.h index af024b6..1e97d7e 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -38,7 +38,8 @@ public: CorrectMnemonic, MissingLexicon, ElectrumMnemonic, - XPub + XPub, + XPriv }; Q_ENUM(StringType) -- 2.54.0 From 9ec9632999b5c52bc9c865a00e097c38662891d8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 1 May 2024 16:51:38 +0200 Subject: [PATCH 0861/1428] Add helper method FloweePay::addressForPrivKey Also use the helper method in Flowee_utils: static PrivateKey PrivateKey::fromBase58(std::string); --- src/FloweePay.cpp | 8 ++++++++ src/FloweePay.h | 5 +++++ src/ImportHandler.cpp | 12 +++--------- src/Wallet.cpp | 10 +++------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1d843c6..3f557d7 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1182,6 +1182,14 @@ void FloweePay::setAppPassword(const QString &password) hasher.finalize(m_appProtectionHash.begin()); } +QString FloweePay::addressForPrivKey(const QString &privateKey) const +{ + auto privKey = PrivateKey::fromBase58(privateKey.toStdString()); + if (!privKey.isValid()) + return QString(); + return renderAddress(privKey.getPubKey().getKeyId()); +} + NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, const QDateTime &date, bool electrumFormat) { const int height = p2pNet()->blockchain().blockHeightAtTime(date.toSecsSinceEpoch()); diff --git a/src/FloweePay.h b/src/FloweePay.h index ebb1f7a..8663776 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -181,6 +181,11 @@ public: Q_INVOKABLE bool checkAppPassword(const QString &password); Q_INVOKABLE void setAppPassword(const QString &password); + /** + * Helper method to convert a private address to a BitcoinCash address. + */ + Q_INVOKABLE QString addressForPrivKey(const QString &privateKey) const; + /** * Import a mnemonics based (BIP39) wallet. * Warning; will throw if the mnemonic is invalid diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 139982f..6c1298f 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -146,17 +146,11 @@ void ImportHandler::checkNext() "\"method\":\"blockchain.scripthash.get_first_use\"," "\"params\": [\"%1\"], \"id\": 2}"); - const P2PNet::Chain chain = FloweePay::instance()->chain(); PublicKey pubKey; if (m_type == FromPrivateKey) { - CBase58Data legacy; - if (legacy.SetString(m_input.toStdString()) - && ((chain == P2PNet::MainChain && legacy.isMainnetPrivKey()) - || (chain == P2PNet::Testnet4Chain && legacy.isTestnetPrivKey()))) { - PrivateKey privKey; - privKey.set(legacy.data().begin(), legacy.data().end(), legacy.data().size() == 32); - pubKey = privKey.getPubKey(); - } + auto privKey = PrivateKey::fromBase58(m_input.toStdString()); + if (privKey.isValid()) + pubKey = privKey.getPubKey(); } else { HDMasterKey::MnemonicType type; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 768143e..fad4b6c 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1564,14 +1564,10 @@ void Wallet::createNewPrivateKey(uint32_t currentBlockheight) bool Wallet::addPrivateKey(const QString &privKey, uint32_t startBlockHeight) { QMutexLocker locker(&m_lock); - CBase58Data encodedData; - auto bytes = privKey.toLatin1(); - encodedData.SetString(bytes.constData()); - if (encodedData.isMainnetPrivKey() || encodedData.isTestnetPrivKey()) { - WalletSecret secret; - secret.privKey.set(encodedData.data().begin(), encodedData.data().begin() + 32, - encodedData.data().size() > 32 && encodedData.data()[32] == 1); + WalletSecret secret; + secret.privKey = PrivateKey::fromBase58(privKey.toStdString()); + if (secret.privKey.isValid()) { // TODO loop over secrets and avoid adding one privkey twice. const PublicKey pubkey = secret.privKey.getPubKey(); -- 2.54.0 From bb7c0821192e01b46864d792692e8d965d928952 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 May 2024 15:28:39 +0200 Subject: [PATCH 0862/1428] UX improvements for ComboBox Fix ComboBox general colors to fit in the theme. This also adds enabled usage, so the enabled comboboxes fall in line with the textfield color settings, same for disabled ones. We also added a focus indicator. --- guis/Flowee/ComboBox.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/guis/Flowee/ComboBox.qml b/guis/Flowee/ComboBox.qml index 50fc569..8945343 100644 --- a/guis/Flowee/ComboBox.qml +++ b/guis/Flowee/ComboBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,10 +40,12 @@ QQC2.ComboBox { } } background: Rectangle { - color: palette.dark + color: enabled ? palette.base : palette.light implicitWidth: 120 implicitHeight: 40 - radius: 2 + radius: enabled ? 2 : 6 + border.width: enabled ? 1.3 : 0 + border.color: root.activeFocus ? palette.highlight : palette.button } contentItem: Text { @@ -52,7 +54,7 @@ QQC2.ComboBox { text: root.displayText font: root.font - color: root.palette.light + color: root.palette.buttonText verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } @@ -72,7 +74,7 @@ QQC2.ComboBox { context.lineTo(width, 0); context.lineTo(width / 2, height); context.closePath(); - context.fillStyle = root.palette.light + context.fillStyle = enabled ? root.palette.text : root.palette.dark context.fill(); } } -- 2.54.0 From a0455ab4141ad8191126b5336ec9e599e2026cee Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 May 2024 22:50:13 +0200 Subject: [PATCH 0863/1428] UX fixes for TextField Fix colors on disabled fields. We now render the Placeholder as italic and in a more distinct color to the main text. --- guis/Flowee/TextField.qml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 4ca2115..759fa9a 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,8 +25,16 @@ QQC2.TextField { id: root selectByMouse: true // In Qt6.3 this is just always black, so adjust to color - placeholderTextColor: Pay.useDarkSkin ? "#cecece" : "#505050" - color: palette.windowText + placeholderTextColor: Pay.useDarkSkin ? "#858585" : "#9a9a9a" + color: enabled ? palette.windowText : + (Pay.useDarkSkin ? Qt.darker(palette.windowText, 1.6) : Qt.lighter(palette.windowText, 1.6) ) + + onTotalTextChanged: updateItalic(); + Component.onCompleted: updateItalic(); + // when the placeholder is the only thing showing, set the font to be italic + function updateItalic() { + font.italic = totalText === ""; + } property string totalText: { var t = text; @@ -56,7 +64,7 @@ QQC2.TextField { border.color: { if (root.enabled) return root.activeFocus ? palette.highlight : palette.button - return "transparant"; + return "#00000000"; } border.width: 0.8 } -- 2.54.0 From 46d73996d7eeffa6e1e839e54f6c146f6f4d2aec Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 11:51:16 +0200 Subject: [PATCH 0864/1428] Make the ImportHandler & IndexerServices work together We now get an actual endpoint from the IndexerServices which is handed to the ImportHandler for remote services. This also includes a bunch of bugfixes and basic 'make it work' stuff. --- src/ImportHandler.cpp | 57 ++++++++++++++++++++++++++++++----------- src/ImportHandler.h | 17 +++++++++--- src/IndexerServices.cpp | 37 ++++++++++++++++++++++++++ src/IndexerServices.h | 8 +++++- 4 files changed, 100 insertions(+), 19 deletions(-) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 6c1298f..765d130 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -45,6 +45,10 @@ ImportHandler::ImportHandler(QObject *parent) void ImportHandler::startCheck(WalletType type, const QString &text, const QString &password) { + assert(!m_serviceAddress.hostname.empty()); + if (m_serviceAddress.hostname.empty()) + throw std::runtime_error("Invalid use, startCheck called without a service"); + m_error = false; m_nextToCheck = MainDerivation; m_type = type; assert(m_type >= FromSeed && m_type <= FromXPub); @@ -54,10 +58,20 @@ void ImportHandler::startCheck(WalletType type, const QString &text, const QStri m_found.clear(); m_currentlyChecking = ImportData(); - // m_electronServer->connectToHostEncrypted("cashnode.bch.ninja", 50002); - m_electronServer->connectToHostEncrypted("173.249.11.35", 50002); - // m_electronServer->connectToHostEncrypted("bch.imaginary.cash", 50002); - // m_electronServer->connectToHostEncrypted("bch.loping.net", 50002); + logInfo(10005) << "ImportHandler connecting to" << m_serviceAddress; + m_electronServer->connectToHostEncrypted( + QString::fromStdString(m_serviceAddress.hostname), m_serviceAddress.announcePort); +} + +void ImportHandler::setService(const EndPoint &ep) +{ + m_serviceAddress = ep; + assert(!m_serviceAddress.hostname.empty()); +} + +EndPoint ImportHandler::service() const +{ + return m_serviceAddress; } void ImportHandler::connectionEstablished() @@ -79,18 +93,20 @@ void ImportHandler::connectionEstablished() void ImportHandler::disconnected() { - logFatal(); + logDebug(10005); } void ImportHandler::socketError(QAbstractSocket::SocketError error) { - logFatal() << error; + logCritical(10005) << error; + m_error = true; + emit finished(); } void ImportHandler::socketDataAvailable() { auto data = m_electronServer->readAll(); - logFatal() << QString::fromLatin1(data); + logDebug(10005) << QString::fromLatin1(data); QJsonDocument doc; doc = QJsonDocument::fromJson(data); if (!doc.isObject()) { @@ -102,7 +118,6 @@ void ImportHandler::socketDataAvailable() case 1: handleVersion(o); break; case 2: handleFirstUse(o); break; default: - // fail return; } } @@ -111,9 +126,10 @@ void ImportHandler::handleVersion(const QJsonObject &rootObject) { auto result = rootObject.value("result"); if (!result.isArray()) { - logFatal() << "No viable protocol-version found for this server"; - // TODO mark current server as bad? + logFatal(10005) << "No viable protocol-version found for this server"; + m_error = true; m_electronServer->close(); + emit finished(); return; } @@ -133,7 +149,7 @@ void ImportHandler::handleFirstUse(const QJsonObject &rootObject) } if (m_nextToCheck == Done) { - emit finished(m_found); + emit finished(); m_electronServer->close(); return; } @@ -151,6 +167,7 @@ void ImportHandler::checkNext() auto privKey = PrivateKey::fromBase58(m_input.toStdString()); if (privKey.isValid()) pubKey = privKey.getPubKey(); + m_nextToCheck = Done; } else { HDMasterKey::MnemonicType type; @@ -163,7 +180,7 @@ void ImportHandler::checkNext() break; case Derivation145: type = HDMasterKey::BIP39Mnemonic; - derivation[1] = 145; + derivation[1] = 145 + HDMasterKey::Hardened; m_nextToCheck = m_type == FromSeed ? MainDerivationElectrumMnemonic : Done; break; case MainDerivationElectrumMnemonic: @@ -172,7 +189,7 @@ void ImportHandler::checkNext() break; case Derivation145ElectrumMnemonic: type = HDMasterKey::ElectrumMnemonic; - derivation[1] = 145; + derivation[1] = 145 + HDMasterKey::Hardened; m_nextToCheck = Done; break; default: @@ -196,8 +213,8 @@ void ImportHandler::checkNext() } if (!pubKey.isValid()) { - // TODO report error - emit finished(m_found); + logFatal(10005) << "Invalid input, can't derive pubkey"; + emit finished(); m_electronServer->close(); return; } @@ -210,3 +227,13 @@ void ImportHandler::checkNext() m_electronServer->write(call.toUtf8()); m_electronServer->write("\n"); } + +bool ImportHandler::errored() const +{ + return m_error; +} + +QList ImportHandler::found() const +{ + return m_found; +} diff --git a/src/ImportHandler.h b/src/ImportHandler.h index 2361f1c..2d5ba1b 100644 --- a/src/ImportHandler.h +++ b/src/ImportHandler.h @@ -19,6 +19,7 @@ #define IMPORTHANDLER_H #include +#include class QSslSocket; @@ -31,8 +32,8 @@ public: enum WalletType { FromSeed, FromPrivateKey, - FromXPub - // FromXPriv, TODO + FromXPub, + FromXPriv }; /** @@ -42,14 +43,22 @@ public: */ void startCheck(WalletType type, const QString &text, const QString &password = QString()); + void setService(const EndPoint &ep); + EndPoint service() const; + struct ImportData { std::vector derivation; bool electrum = false; int firstBlockHeight = 0; }; + QList found() const; + + /// returns true if an error occurred since startCheck() + bool errored() const; + signals: - void finished(const QList &results); + void finished(); private slots: void connectionEstablished(); @@ -82,6 +91,8 @@ private: ImportData m_currentlyChecking; QList m_found; + EndPoint m_serviceAddress; + bool m_error = false; }; #endif diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index 3844018..76f289a 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -18,6 +18,7 @@ #include "IndexerServices.h" #include "IndexerServices_p.h" +#include #include #include #include @@ -154,6 +155,42 @@ void IndexerServices::save() } } +EndPoint IndexerServices::service() const +{ + EndPoint ep; + QMutexLocker locker(&m_mutex); + std::vector eligible; + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->punishment < 250 && iter->securePort > 0 + && iter->hostname != iter->ip + && (iter->protocolVersion == 0 || iter->protocolVersion >= 0x010502)) { + eligible.push_back(*iter); + } + } + if (!eligible.empty()) { + int index = GetRand(eligible.size()); + assert((int) eligible.size() > index); + const auto &result = eligible.at(index); + ep.hostname = result.hostname.toStdString(); + ep.announcePort = result.securePort; + ep.peerPort = result.securePort; + } + return ep; +} + +void IndexerServices::punish(const EndPoint &ep, int score) +{ + QMutexLocker locker(&m_mutex); + std::vector eligible; + QString hostname = QString::fromStdString(ep.hostname); + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->hostname == hostname && ep.announcePort == iter->securePort) { + iter->punishment += score; + save(); + } + } +} + void IndexerServices::onSeedLookupComplete(const boost::system::error_code &error, tcp::resolver::iterator iterator) { if (error) diff --git a/src/IndexerServices.h b/src/IndexerServices.h index c026a09..57503ee 100644 --- a/src/IndexerServices.h +++ b/src/IndexerServices.h @@ -22,9 +22,10 @@ #include #include +#include +#include #include -#include using boost::asio::ip::tcp; class FetchIndexerServicePeers; @@ -49,6 +50,11 @@ public: void save(); + /// return a single, (semi) random service provider. + EndPoint service() const; + + void punish(const EndPoint &ep, int score); + signals: void startMaybeFindServices(); -- 2.54.0 From cccdce7ba99bc5a088477af5b9dada6d6e42e247 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 12:11:45 +0200 Subject: [PATCH 0865/1428] Make 'BigButton' a common widget --- guis/Flowee/BigButton.qml | 64 +++++++++++++++++++++++++++++++++++++++ guis/desktop/main.qml | 23 +++----------- 2 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 guis/Flowee/BigButton.qml diff --git a/guis/Flowee/BigButton.qml b/guis/Flowee/BigButton.qml new file mode 100644 index 0000000..d53d1f2 --- /dev/null +++ b/guis/Flowee/BigButton.qml @@ -0,0 +1,64 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 + +QQC2.Control { + id: control + height: buttonLabel.height + 32 + width: 200 + focus: true + activeFocusOnTab: true + + property alias text: buttonLabel.text + + // the color of the main button is Flowee - Green. + property bool isMainButton: false + signal clicked; + + Rectangle { + id: background + radius: 7 + color: isMainButton ? mainWindow.floweeGreen : palette.button + anchors.fill: parent + + border.width: control.activeFocus ? 1.3 : 0 + border.color: palette.highlight + + Behavior on color { ColorAnimation { } } + } + Text { + id: buttonLabel + anchors.centerIn: background + width: Math.min(parent.width - 10, implicitWidth) + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + horizontalAlignment: Qt.AlignHCenter + opacity: enabled ? 1 : 0.4 + font.pixelSize: control.font.pixelSize + color: control.isMainButton ? "black" : palette.text + } + MouseArea { + anchors.fill: parent + onClicked: control.clicked(); + } + + Keys.onPressed: (event)=> { + if (event.modifiers === 0 && event.key === Qt.Key_Space) + control.clicked(); + } +} diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 3e3fa1a..051a870 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -674,26 +674,13 @@ ApplicationWindow { height: 20 } - Rectangle { // button 'add bitcoin cash wallet' - color: mainWindow.floweeGreen - radius: 7 + Flowee.BigButton { // button 'add bitcoin cash wallet' + text: qsTr("Add Bitcoin Cash wallet") + onClicked: newAccountPane.source = "./NewAccountPane.qml" + isMainButton: true width: leftColumn.width - height: buttonLabel.height + 28 - Text { - id: buttonLabel - anchors.centerIn: parent - width: Math.min(parent.width - 10, implicitWidth) - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: qsTr("Add Bitcoin Cash wallet") - horizontalAlignment: Qt.AlignHCenter - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: newAccountPane.source = "./NewAccountPane.qml" - } } + Item { // spacer width: 1 height: 20 -- 2.54.0 From da8802c6d15a8a5fa1c5aefe40b28cbd870d23f9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 11:51:30 +0200 Subject: [PATCH 0866/1428] Re-do the 'import wallet' screen (mobile) This completely re-designed the 'import wallet' screens on mobile. We use the various new features available now, most importantly the ability to detect derivation and start-height from an electrum indexing server. --- guis/Flowee/MultilineTextField.qml | 7 +- guis/Flowee/ObjectShaker.qml | 4 +- guis/mobile/ImportWalletPage.qml | 588 +++++++++++++++++++++-------- guis/widgets.qrc | 1 + src/CMakeLists.txt | 1 + src/FloweePay.cpp | 4 +- src/QMLImportHelper.cpp | 231 ++++++++++++ src/QMLImportHelper.h | 95 +++++ src/main.cpp | 2 + 9 files changed, 774 insertions(+), 159 deletions(-) create mode 100644 src/QMLImportHelper.cpp create mode 100644 src/QMLImportHelper.h diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index b664cde..b3254b1 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -38,8 +38,9 @@ QQC2.Control { * a little harder as we simply want to be able to get the total in order to validate * it or update the backend as the user is typing in order to not lose "uncommitted" text. */ - var pre = textEdit.preeditText; - if (pre !== "") { + + if (textEdit.inputMethodComposing) { + var pre = textEdit.preeditText; let first = t.substring(0, textEdit.cursorPosition) let last = t.substring(textEdit.cursorPosition) t = first + pre + last; @@ -53,6 +54,8 @@ QQC2.Control { signal editingFinished; property alias readOnly: textEdit.readOnly property alias inputMethodHints: textEdit.inputMethodHints + property alias inputMethodComposing: textEdit.inputMethodComposing + property alias cursorPosition: textEdit.cursorPosition implicitHeight: textEdit.implicitHeight + 10 implicitWidth: 100 diff --git a/guis/Flowee/ObjectShaker.qml b/guis/Flowee/ObjectShaker.qml index 2443178..608bbe8 100644 --- a/guis/Flowee/ObjectShaker.qml +++ b/guis/Flowee/ObjectShaker.qml @@ -51,11 +51,11 @@ Item { onFinished: { if (loop === 0) { from = to - to = to + 10 + to = to + 20 } else if (loop === 1) { from = to - to = to - 10 + to = to - 20 } else if (loop === 2) { from = to diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 8fa1d62..bc8121f 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,169 +25,409 @@ Page { id: importAccount headerText: qsTr("Import Wallet") - property var typedData: Pay.identifyString(secrets.totalText) - property bool finished: typedData === Wallet.PrivateKey || ((typedData === Wallet.CorrectMnemonic - || typedData === Wallet.ElectrumMnemonic) - && derivationPath.derivationOk); - property bool isMnemonic: typedData === Wallet.CorrectMnemonic || typedData === Wallet.PartialMnemonic || typedData === Wallet.PartialMnemonicWithTypo || typedData === Wallet.ElectrumMnemonic; - property bool isElectrumMnemonic: typedData === Wallet.ElectrumMnemonic - property bool isPrivateKey: typedData === Wallet.PrivateKey + states: [ + State { + name: "entryPage" + PropertyChanges { target: entryPage; x: 0 } + PropertyChanges { target: privKeydetailsPage; x: width + 10 } + PropertyChanges { target: seedDetailsPage; x: width + 10 } + }, + State { + name: "privKeyDetailsPage" + PropertyChanges { target: entryPage; x: -10 - width } + PropertyChanges { target: privKeydetailsPage; x: 0 } + }, + State { + name: "seedDetailsPage" + PropertyChanges { target: entryPage; x: -10 - width } + PropertyChanges { target: seedDetailsPage; x: 0 } + } + ] + state: "entryPage" - Flickable { - anchors.fill: parent - anchors.topMargin: 10 - anchors.bottomMargin: 10 - contentWidth: parent.width - contentHeight: column.height + Keys.onPressed: (event)=> { + if (state !== "entryPage" && (event.key === Qt.Key_Escape || event.key === Qt.Key_Back)) { + event.accepted = true; + state = "entryPage"; + } + } + backHandler: function handler() { + // we practically have two pages inside this on Page object, we should make the 'back' + // button (header of Page) be aware of this. + if (state !== "entryPage") + state = "entryPage"; + else + thePile.pop(); + } - ColumnLayout { - id: column + function toNextPage() { + var type = entryPage.typedData; + if (type === Wallet.PrivateKey) { + state = "privKeyDetailsPage"; + privKeydetailsPage.takeFocus(); + } else if (type === Wallet.CorrectMnemonic || type === Wallet.ElectrumMnemonic) { + state = "seedDetailsPage"; + seedDetailsPage.takeFocus(); + } + } + + Column { + id: entryPage + width: parent.width + spacing: 20 + + property var typedData: Pay.identifyString(secretText.totalText) + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic || typedData === Wallet.ElectrumMnemonic + + onFinishedChanged: importAccount.toNextPage(); + PageTitledBox { + id: buttonsBox + title: qsTr("Select import method") width: parent.width - spacing: 10 - Flowee.Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.fillWidth: true - wrapMode: Text.Wrap - } - - PageTitledBox { - title: qsTr("Secret", "The seed-phrase or private key") - Item { - width: parent.width - height: secrets.height + 10 - Flowee.MultilineTextField { - id: secrets - focus: true - nextFocusTarget: accountName - clip: true - anchors.left: parent.left - anchors.right: feedback.left + Item { + width: parent.width + height: scanButton.height + Flowee.ImageButton { + id: scanButton + source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + onClicked: scanner.start(); + iconSize: Math.min(entryPage.width / 4, 100) + x: (parent.width - width) / 2 // while NFC is not enabled.. + // x: (parent.width - width * 2 - 20) / 2 + text: qsTr("Camera") + } + QRScanner { + id: scanner + scanType: QRScanner.SeedOrPrivKey + onFinished: { + if (scanResult !== "") + secretText.text = scanResult; + // make sure to give focus back to this page after the camera took it. + scanButton.forceActiveFocus(); } + } + Rectangle { + id: nfcButton + width: scanButton.width + visible: false + height: width + radius: 90 + color: "#00000000" + border.color: "yellow" + border.width: 2 + x: (parent.width - width * 2 - 20) / 2 + width + 20 + Flowee.Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - anchors.right: startScan.left - } - Flowee.ImageButton { - id: startScan - source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - onClicked: scanner.start(); - iconSize: 25 - anchors.verticalCenter: feedback.verticalCenter - anchors.right: parent.right - - QRScanner { - id: scanner - scanType: QRScanner.SeedOrPrivKey - onFinished: { - if (scanResult !== "") - secrets.text = scanResult; - // make sure to give focus back to this page after the camera took it. - startScan.forceActiveFocus(); - } - } + anchors.centerIn: parent + text: "NFC" } } } + } + Row { + spacing: 15 + x: (entryPage.width - width) / 2 + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } + Flowee.Label { + text: qsTr("OR") + } + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } + } + PageTitledBox { + id: textSecretBox + title: qsTr("Secret as text", "The seed-phrase or private key") + width: parent.width + Item { + width: parent.width + height: pasteButton.visibe ? pasteButton.height / 3 * 2 : 0 + secretText.height + + Flowee.MultilineTextField { + id: secretText + width: parent.width + clip: true + height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) + } + + TextPasteDecorator { + id: pasteButton + buddy: secretText + clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed + } + } Flowee.Label { - id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? mainWindow.errorRed : feedback.color text: { - var typedData = importAccount.typedData - if (typedData === Wallet.PrivateKey) - return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) { - if (forceElectrum.checked) - return qsTr("BIP 39 seed-phrase (interpreted as Electrum)", "description of type") - return qsTr("BIP 39 seed-phrase", "description of type") + if (entryPage.typedData === Wallet.PartialMnemonicWithTypo) { + var bareText = secretText.text; + // if inputMethodComposing true, that makes it simple to avoid the word that + // is being edited the 'text' property of secretText omits that one. + if (!secretText.inputMethodComposing) { + // in case we're editing without there being an inputmethod we find the word + // based on the char-pos. + let cp = secretText.cursorPosition; + let startEditWord = bareText.lastIndexOf(' ', cp); + let endEditWord = bareText.indexOf(' ', cp); + + let before = bareText.substr(0, startEditWord); + let after = endEditWord > 0 ? bareText.substr(endEditWord) : ""; + bareText = before + after; + } + + if (Pay.identifyString(bareText) === Wallet.PartialMnemonicWithTypo) + return qsTr("Unknown word(s) found"); } - if (typedData === Wallet.ElectrumMnemonic) - return qsTr("Electrum seed-phrase", "description of type") - if (typedData === Wallet.PartialMnemonicWithTypo) - return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Wallet.MissingLexicon) - return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this return "" } + color: mainWindow.errorRed + visible: text !== "" } + } + Flowee.Button { + anchors.right: parent.right + text: qsTr("Next") + visible: parent.finished + + onClicked: importAccount.toNextPage(); + } + + Behavior on x { NumberAnimation { } } + } + + Item { + id: privKeydetailsPage + x: width + 10 + width: parent.width + height: parent.height + + function takeFocus() { + singleAddress.forceActiveFocus(); + } + + ColumnLayout { + spacing: 10 + width: parent.width PageTitledBox { - title: qsTr("Name") - Flowee.TextField { - id: accountName - width: parent.width + title: qsTr("Address to import") + Layout.fillWidth: true + Flowee.LabelWithClipboard { + // this shows the bitcoincash address matching the private key + font.pixelSize: singleAddress.font.pixelSize * 0.9 + text: { + if (importAccount.state !== "privKeyDetailsPage") + return ""; + return Pay.addressForPrivKey(secretText.text); + } } } - Flowee.CheckBox { id: singleAddress Layout.fillWidth: true text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true - visible: importAccount.isPrivateKey } - Flowee.CheckBox { - id: forceElectrum - text: qsTr("Old Electrum Phrase"); - toolTipText: qsTr("When Electrum detection fails, and you are sure it was created in that wallet, enable this option."); - checked: importAccount.isElectrumMnemonic - visible: importAccount.isMnemonic - enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic - Layout.fillWidth: true - property string prevDerPath: "" - onCheckedChanged: { - derivationLabel.enabled = !checked; - derivationPath.enabled = !checked; - if (checked) { - // Electrum mnemonics always use derivation path: "m/" and never anything else. - prevDerPath = derivationPath.text; - derivationPath.text = "m/"; - } else if (prevDerPath) { - derivationPath.text = prevDerPath; - } + + PageTitledBox { + title: qsTr("Name") + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + width: parent.width } } PageTitledBox { title: qsTr("Oldest Transaction") - Flow { + Item { + implicitWidth: parent.width + implicitHeight: ageButton.height + Flowee.BigButton { + id: ageButton + text: qsTr("Check Age", "online check for wallet age") + enabled: !privKeyImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + privKeyImportHelper.secretType = Wallet.PrivateKey + privKeyImportHelper.secret = secretText.text + } + ImportHelper { + id: privKeyImportHelper + onCheckingChanged: { + if (checking) + return; + emptyPrivKeyWarningLabel.visible = false; + ageButton.isMainButton = false; + if (resultCount === 0) { + emptyPrivKeyWarningLabel.visible = true; + } + else if (resultCount === 1) { + let height = startHeight(0); + oldestTransactionChooser.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser.item.year.currentIndex + = yearOnHeight(height) - 2010; + oldestTransactionChooser.item.enabled = false; + + privImportStartButton.isMainButton = true; + } + } + } + + // TODO add warning + } + } + Loader { + id: oldestTransactionChooser width: parent.width - spacing: 10 - Flowee.ComboBox { - id: month - model: { - let locale = Qt.locale(); - var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { - list.push(locale.monthName(i)); - } - return list; - } - width: implicitWidth * 1.3 // this makes it fit for bigger fonts. + sourceComponent: oldestTransactionChooser_component + } + } + Flowee.Label { + id: emptyPrivKeyWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for wallet") + visible: false + } + + Flowee.BigButton { + id: privImportStartButton + text: qsTr("Start") + Layout.alignment: Qt.AlignRight + + onClicked: { + if (privKeyImportHelper.resultCount > 0) { + var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) + } else { + var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2010, oldestTransactionChooser.item.month.currentIndex, 1); + options = Pay.createImportedWallet(secretText.text, accountName.text, sh) + options.forceSingleAddress = singleAddress.checked; } - Flowee.ComboBox { - id: year - model: { - var list = []; - let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { - list.push(i); - } - return list; + + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; } - currentIndex: 9; } + thePile.pop(); + thePile.pop(); + } + } + } + + Behavior on x { NumberAnimation { } } + } + + Column { + id: seedDetailsPage + x: width + 10 + width: parent.width + height: parent.height + + function takeFocus() { + seedCheckButton.forceActiveFocus(); + } + PageTitledBox { + title: qsTr("Name") + width: parent.width + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName2 + width: parent.width + } + } + + PageTitledBox { + title: qsTr("Password") + width: parent.width + Flowee.TextField { + id: passwordField + width: parent.width + placeholderText: qsTr("Optional") + } + } + PageTitledBox { + title: qsTr("Details") + width: parent.width + + Item { + implicitWidth: parent.width + implicitHeight: seedCheckButton.height + Flowee.BigButton { + id: seedCheckButton + text: qsTr("Lookup Details", "online check for wallet details") + enabled: !seedImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + seedImportHelper.secretType = entryPage.typedData + seedImportHelper.secret = secretText.text + seedImportHelper.password = passwordField.text + } + ImportHelper { + id: seedImportHelper + onCheckingChanged: { + if (checking) + return; + emptySeedWarningLabel.visible = false; + if (resultCount === 0) { // empty + seedCheckButton.isMainButton = false; + emptySeedWarningLabel.visible = true; + } + else if (resultCount >= 1) { + // TODO what to do if there are more then 1? + + let height = startHeight(0); + oldestTransactionChooser2.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser2.item.year.currentIndex + = yearOnHeight(height) - 2010; + oldestTransactionChooser2.item.enabled = false; + derivationPath.text = derivation(0); + derivationPath.enabled = false; + + seedCheckButton.isMainButton = false; + seedStartButton.isMainButton = true; + } + } + } + + // TODO add warning } } PageTitledBox { id: derivationLabel title: qsTr("Derivation") - visible: !importAccount.isPrivateKey + width: parent.width Flowee.TextField { id: derivationPath property bool derivationOk: Pay.checkDerivation(text); @@ -197,41 +437,85 @@ Page { } } PageTitledBox { - title: qsTr("Alternate phrase") - visible: !importAccount.isPrivateKey - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField + title: qsTr("Oldest Transaction") + width: parent.width + Loader { + id: oldestTransactionChooser2 width: parent.width + sourceComponent: oldestTransactionChooser_component } } - - Item { + Flowee.Label { + id: emptySeedWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for seed") + visible: false width: parent.width - height: createButton.implicitHeight - Flowee.Button { - id: createButton - enabled: finished - anchors.right: parent.right - text: qsTr("Create") - onClicked: { - var sh = new Date(year.currentIndex + 2010, month.currentIndex, 1); - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - options.forceSingleAddress = singleAddress.checked; + } - for (let a of portfolio.accounts) { - if (a.id === options.accountId) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); + Flowee.BigButton { + id: seedStartButton + text: qsTr("Start") + anchors.right: parent.right + onClicked: { + if (seedImportHelper.resultCount > 0) { + var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), + seedImportHelper.isElectrumSeed(0)); + } else { + var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2010, oldestTransactionChooser2.item.month.currentIndex, 1); + options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, sh); } + + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } + thePile.pop(); + thePile.pop(); + } + } + } + + Behavior on x { NumberAnimation { } } + } + + Item { + // non-gui items below. + + Component { + id: oldestTransactionChooser_component + Flow { + property alias month: inner_month + property alias year: inner_year + width: parent.width + spacing: 10 + Flowee.ComboBox { + id: inner_month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; + } + width: implicitWidth * 1.3 // this makes it fit for bigger fonts. + } + Flowee.ComboBox { + id: inner_year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 10; } } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index b98d595..523f94d 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -14,6 +14,7 @@ images/Flowee-Symbols.otf Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml + Flowee/BigButton.qml Flowee/Button.qml Flowee/CardTypeSelector.qml Flowee/CheckBox.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 088d41f..e032a3b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ set (PAY_SOURCES PriceHistoryDataProvider.cpp QRCreator.cpp QMLClipboardHelper.cpp + QMLImportHelper.cpp TransactionInfo.cpp TxInfoObject.cpp Wallet.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 3f557d7..0812e0d 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1266,9 +1266,7 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const } } else if (index == -1) { // a not-first-word failed the lookup. - if (word != words.last()) // this is the last word, don't highlight while writing. - return WalletEnums::PartialMnemonicWithTypo; - break; + return WalletEnums::PartialMnemonicWithTypo; } // if we get to this point in the loop then we have a real word that we found in the dictionary. // Lets continue checking words and check if the rest of the words are part of the lexicon too. diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp new file mode 100644 index 0000000..ee7c4d3 --- /dev/null +++ b/src/QMLImportHelper.cpp @@ -0,0 +1,231 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "QMLImportHelper.h" +#include "FloweePay.h" +#include "IndexerServices.h" + +#include +#include +#include + +QMLImportHelper::QMLImportHelper(QObject *parent) + : QObject{parent}, + m_importHandler(new ImportHandler(this)) +{ + // make sure that we'll have a list of indexer services when we + // need them later. + FloweePay::instance()->indexerServices()->populate(); + + connect (m_importHandler, SIGNAL(finished()), + this, SLOT(checkFinished()), Qt::QueuedConnection); +} + +bool QMLImportHelper::checking() const +{ + return m_checking; +} + +void QMLImportHelper::checkFinished() +{ + assert(m_checking); + if (m_importHandler->errored()) { + logWarning() << "Import lookup failed, lets punish host and retry"; + FloweePay::instance()->indexerServices()->punish(m_importHandler->service(), 60); + m_checking = false; // to lure startCheck() into a sense of normalcy + startCheck(); + return; + } + m_results = m_importHandler->found(); + emit resultCountChanged(); + setChecking(false); +} + +void QMLImportHelper::setChecking(bool newChecking) +{ + if (m_checking == newChecking) + return; + m_checking = newChecking; + emit checkingChanged(); +} + +void QMLImportHelper::dataChanged() +{ + if (m_dataChanged || m_checking) + return; + m_dataChanged = true; + if (!m_results.isEmpty()) { + m_results.clear(); + emit resultCountChanged(); + } + QTimer::singleShot(0, this, SLOT(startCheck())); +} + +QString QMLImportHelper::error() const +{ + return m_error; +} + +void QMLImportHelper::setError(const QString &newError) +{ + if (m_error == newError) + return; + m_error = newError; + emit errorChanged(); +} + +WalletEnums::StringType QMLImportHelper::secretType() const +{ + return m_secretType; +} + +void QMLImportHelper::setSecretType(const WalletEnums::StringType &newSecretType) +{ + if (m_secretType == newSecretType) + return; + m_secretType = newSecretType; + emit secretTypeChanged(); + dataChanged(); +} + +void QMLImportHelper::startCheck() +{ + assert(m_checking == false); + m_dataChanged = false; + + auto service = FloweePay::instance()->indexerServices()->service(); + if (service.hostname.empty()) { + // lets not translate this, since this is likely an + // internal error (aka bug) or simply a lack of Internet. + setError("Failed to find available service"); + return; + } + m_importHandler->setService(service); + ImportHandler::WalletType type; + switch (m_secretType) { + case WalletEnums::PrivateKey: + type = ImportHandler::FromPrivateKey; + break; + case WalletEnums::CorrectMnemonic: + case WalletEnums::ElectrumMnemonic: + type = ImportHandler::FromSeed; + break; + case WalletEnums::XPub: + type = ImportHandler::FromXPub; + break; + case WalletEnums::XPriv: + type = ImportHandler::FromXPriv; + break; + default: + return; + } + + setChecking(true); + m_importHandler->startCheck(type, m_secret, m_password); +} + +QString QMLImportHelper::password() const +{ + return m_password; +} + +void QMLImportHelper::setPassword(const QString &newPassword) +{ + if (m_password == newPassword) + return; + m_password = newPassword; + emit passwordChanged(); + dataChanged(); +} + +QString QMLImportHelper::secret() const +{ + return m_secret; +} + +void QMLImportHelper::setSecret(const QString &newSecret) +{ + if (m_secret == newSecret) + return; + m_secret = newSecret; + emit secretChanged(); + dataChanged(); +} + +int QMLImportHelper::startHeight(int resultIndex) const +{ + if (m_results.size() <= resultIndex) + return -1; + return m_results.at(resultIndex).firstBlockHeight; +} + +QString QMLImportHelper::derivation(int resultIndex) const +{ + if (m_results.size() <= resultIndex) + return QString(); + QString answer("m/"); + auto path = m_results.at(resultIndex).derivation; + if (path.size() == 5 && path.at(3) == 0 && path.at(4) == 0) { + // the importer finds the first address, but we need the + // base, so we cut off the last two zero's. + path.resize(3); + } + + for (size_t i = 0; i < path.size(); ++i) { + uint32_t x = path.at(i); + const bool hard = x >= HDMasterKey::Hardened; + if (hard) + x -= HDMasterKey::Hardened; + answer += QString::number(x); + if (hard) + answer += "'"; + if (i < path.size() - 1) + answer += "/"; + } + return answer; +} + +bool QMLImportHelper::isElectrumSeed(int resultIndex) const +{ + if (m_results.size() <= resultIndex) + return false; + return m_results.at(resultIndex).electrum; +} + +int QMLImportHelper::monthOnHeight(int height) const +{ + assert(height > 10000); + try { + auto block = FloweePay::instance()->p2pNet()->blockchain().block(height); + QDateTime date = QDateTime::fromSecsSinceEpoch(block.nTime); + return date.date().month(); + } catch (...) { + return 0; + } +} + +int QMLImportHelper::yearOnHeight(int height) const +{ + assert(height > 10000); + try { + auto block = FloweePay::instance()->p2pNet()->blockchain().block(height); + QDateTime date = QDateTime::fromSecsSinceEpoch(block.nTime); + return date.date().year(); + } catch (...) { + return 0; + } +} diff --git a/src/QMLImportHelper.h b/src/QMLImportHelper.h new file mode 100644 index 0000000..5a0e1af --- /dev/null +++ b/src/QMLImportHelper.h @@ -0,0 +1,95 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QMLIMPORTHELPER_H +#define QMLIMPORTHELPER_H + +#include + +#include "ImportHandler.h" +#include "WalletEnums.h" + +class QMLImportHelper : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool checking READ checking WRITE setChecking NOTIFY checkingChanged FINAL) + Q_PROPERTY(QString secret READ secret WRITE setSecret NOTIFY secretChanged FINAL) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged FINAL) + Q_PROPERTY(QString error READ error NOTIFY errorChanged FINAL) + Q_PROPERTY(WalletEnums::StringType secretType READ secretType WRITE setSecretType NOTIFY secretTypeChanged FINAL) + Q_PROPERTY(int resultCount READ resultCount NOTIFY resultCountChanged FINAL) +public: + explicit QMLImportHelper(QObject *parent = nullptr); + + // returns true while the async checking is in progress + bool checking() const; + + QString secret() const; + void setSecret(const QString &newSecret); + + QString password() const; + void setPassword(const QString &newPassword); + + WalletEnums::StringType secretType() const; + void setSecretType(const WalletEnums::StringType &newSecretType); + + int resultCount() const { + return m_results.count(); + } + + QString error() const; + void setError(const QString &newError); + + // present to the QML side a single field from the result-set. + Q_INVOKABLE int startHeight(int resultIndex) const; + Q_INVOKABLE QString derivation(int resultIndex) const; + Q_INVOKABLE bool isElectrumSeed(int resultIndex) const; + + Q_INVOKABLE int monthOnHeight(int height) const; + Q_INVOKABLE int yearOnHeight(int height) const; + +signals: + void checkingChanged(); + void secretChanged(); + void passwordChanged(); + void secretTypeChanged(); + void resultCountChanged(); + void errorChanged(); + +private slots: + void startCheck(); + void checkFinished(); + +private: + void setChecking(bool newChecking); + // will make sure that we call startCheck in the next event-loop iteration. + void dataChanged(); + + + QList m_results; + + bool m_checking = false; + bool m_dataChanged = false; + QString m_secret; + QString m_password; + QString m_error; + WalletEnums::StringType m_secretType = WalletEnums::Unknown; + + ImportHandler *m_importHandler; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 0b3d802..154c71f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ #include "PaymentBackend.h" #include "QRCreator.h" #include "QMLClipboardHelper.h" +#include "QMLImportHelper.h" #include "MenuModel.h" #include "ModuleManager.h" #ifdef MOBILE @@ -97,6 +98,7 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentBackend"); qmlRegisterType("Flowee.org.pay", 1, 0, "ClipboardHelper"); + qmlRegisterType("Flowee.org.pay", 1, 0, "ImportHelper"); auto cld = createCLD(qapp); initLogger(cld); -- 2.54.0 From dbb399fc7a54421a1c9810892ba4b5267bf5827b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 13:05:17 +0200 Subject: [PATCH 0867/1428] Simplify the CardTypeSelector Make the selector more compact, Instead of showing empty space, make the size just fit the actual content. This makes it more usable on mobile layouts. Also improve the UX of the NewAccount page on mobile, less implicit text and more explicit intention based titles. Last, make the title-label's font scale to fit the size it is given. --- guis/Flowee/CardTypeSelector.qml | 68 ++++++++++++++++--------- guis/mobile/NewAccount.qml | 86 ++++++++++++-------------------- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index 2524563..a5caaeb 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -1,5 +1,5 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,40 +18,45 @@ import QtQuick Item { id: root - property int key: 0 property alias title: name.text property var features: [] + property bool checkable: false + property bool checked: false signal clicked; - property bool selected: parent.selectedKey === key - width: name.width * 2 - height: 270 + implicitWidth: name.implicitWidth * 1.8 + implicitHeight: Math.min(contentArea.height + name.height + 15 + 16 + 10 + 10 + 20, 270) clip: true - Rectangle { - color: { - var base = palette.base - if (Pay.useDarkSkin) - return parent.selected ? Qt.lighter(base, 1.4) : Qt.darker(base, 0.9) - return parent.selected ? Qt.darker(base, 1.04) : Qt.darker(base, 1.1) - } - border.width: parent.selected ? 5 : 0.8 - border.color: parent.selected ? palette.mid : palette.alternateBase - width: parent.width - height: parent.selected ? parent.height : parent.height - 50 - radius: 10 - y: parent.selected ? 0 : 25 - Behavior on height { NumberAnimation { } } - Behavior on y { NumberAnimation { } } + Rectangle { + id: background + property bool hover: false + property bool selected: root.checkable && root.checked + + color: selected ? palette.light : palette.window + border.width: selected ? 5 : 2 + border.color: { + if (selected) + return mainWindow.floweeGreen + if (hover) + return Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue; + return Pay.useDarkSkin ? "#EEE" : "#7b7f7f" + } + + anchors.fill: parent + radius: 10 + Behavior on border.width { NumberAnimation { } } Behavior on color { ColorAnimation { } } - Behavior on border. color { ColorAnimation { } } + Behavior on border.color { ColorAnimation { } } } Label { id: name - anchors.horizontalCenter: parent.horizontalCenter + width: root.width + fontSizeMode: Text.HorizontalFit + horizontalAlignment: Text.AlignHCenter font.pixelSize: mainWindow.font.pixelSize * 1.3 font.bold: true y: 25 @@ -78,6 +83,23 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: root.clicked() + hoverEnabled: true + onEntered: background.hover = true + onExited: background.hover = false + onClicked: { + var p = root.parent; + if (p != null) { + var list = p.children; + for (let i = 0; i < list.length; ++i) { + let item = list[i]; + if (typeof(item) === typeof(root)) { + item.checked = false; + } + } + } + if (root.checkable) + root.checked = true; + root.clicked(); + } } } diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index 97ad817..becfde7 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,62 +35,42 @@ Page { y: 10 spacing: 20 - PageTitledBox { - title: qsTr("Create a New Wallet") - width: parent.width - spacing: 10 + Flowee.CardTypeSelector { + title: qsTr("New HD Wallet") + onClicked: thePile.push(newHDWalletScreen); + width: parent.width * 0.75 + anchors.horizontalCenter: parent.horizontalCenter - Flowee.CardTypeSelector { - title: qsTr("HD wallet") - onClicked: thePile.push(newHDWalletScreen); - width: parent.width * 0.75 - anchors.horizontalCenter: parent.horizontalCenter - selected: true - - features: [ - qsTr("Seed-phrase based", "Context: wallet type"), - qsTr("Easy to backup", "Context: wallet type"), - qsTr("Most compatible", "The most compatible wallet type") - ] - Rectangle { - anchors.fill: parent - color: "#00000000" - radius: 10 - border.width: 5 - border.color: mainWindow.floweeGreen - } - } - - Flowee.CardTypeSelector { - title: qsTr("Basic") - width: parent.width * 0.75 - onClicked: thePile.push(newBaseWalletScreen); - selected: true - anchors.horizontalCenter: parent.horizontalCenter - - features: [ - qsTr("Private keys based", "Property of a wallet"), - qsTr("Difficult to backup", "Context: wallet type"), - qsTr("Great for brief usage", "Context: wallet type") - ] - } + features: [ + qsTr("Seed-phrase based", "Context: wallet type"), + qsTr("Easy to backup", "Context: wallet type"), + qsTr("Most compatible", "The most compatible wallet type") + ] } - PageTitledBox { - title: qsTr("Import Existing Wallet") - width: parent.width - Flowee.CardTypeSelector { - title: qsTr("Import") - width: parent.width * 0.75 - onClicked: thePile.push("./ImportWalletPage.qml"); - anchors.horizontalCenter: parent.horizontalCenter - selected: true + Flowee.CardTypeSelector { + title: qsTr("New Basic Wallet") + width: parent.width * 0.75 + onClicked: thePile.push(newBaseWalletScreen); + anchors.horizontalCenter: parent.horizontalCenter - features: [ - qsTr("Imports seed-phrase"), - qsTr("Imports private key") - ] - } + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] + } + + Flowee.CardTypeSelector { + title: qsTr("Import Existing Wallet") + width: parent.width * 0.75 + onClicked: thePile.push("./ImportWalletPage.qml"); + anchors.horizontalCenter: parent.horizontalCenter + + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] } } } -- 2.54.0 From 16d52b938310149547987302eac72f319de0b54e Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 22:36:21 +0200 Subject: [PATCH 0868/1428] Make camera stuff available for desktop app In case the configure step found the QtMultimedia libs, we also include the camera / QR-scanner functionality in the desktop app since users may use a laptop which contains a camera. --- CMakeLists.txt | 5 +---- src/CMakeLists.txt | 6 ++++++ src/QRScanner.cpp | 8 +++++++- src/main.cpp | 6 +++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5804cb6..a95a11e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,7 @@ endif() if (NOT "${Qt6Multimedia_FOUND}") set (build_mobile_pay OFF) + add_compile_definitions(NO_MULTIMEDIA) endif () set (PAY_MOBILE_LIBS "") @@ -247,8 +248,6 @@ if (ANDROID AND build_mobile_pay) set (SOURCES_PAY_MOBILE src/main.cpp - src/CameraController.cpp - src/QRScanner.cpp ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp ${CMAKE_BINARY_DIR}/modules/modules-load.cpp ) @@ -285,8 +284,6 @@ if(NOT ANDROID AND build_mobile_pay) set (SOURCES_PAY_MOBILE src/main.cpp - src/CameraController.cpp - src/QRScanner.cpp ${CMAKE_BINARY_DIR}/modules/modules-load.cpp ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e032a3b..c697177 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ set (PAY_SOURCES PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp + QRScanner.cpp PaymentProtocol.cpp PortfolioDataProvider.cpp @@ -69,6 +70,11 @@ else () list(APPEND PAY_SOURCES main_utils.cpp) endif () +if ("${Qt6Multimedia_FOUND}") + list(APPEND PAY_SOURCES CameraController.cpp) + list(APPEND PayLib_PRIVATE_LIBS Qt6::Multimedia) +endif () + add_library(pay_lib STATIC ${PAY_SOURCES}) target_link_libraries(pay_lib diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index 0d66c40..60cc673 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -18,7 +18,9 @@ #include "QRScanner.h" #include "FloweePay.h" -#include "CameraController.h" +#ifndef NO_MULTIMEDIA +# include "CameraController.h" +#endif #include @@ -32,17 +34,21 @@ QRScanner::QRScanner(QObject *parent) void QRScanner::start() { +#ifndef NO_MULTIMEDIA resetScanResult(); if (m_scanType == static_cast(100)) throw std::runtime_error("Required property scanType not set"); setIsScanning(true); FloweePay::instance()->cameraController()->startRequest(this); +#endif } void QRScanner::abort() { +#ifndef NO_MULTIMEDIA FloweePay::instance()->cameraController()->abortRequest(this); setIsScanning(false); +#endif } QRScanner::ScanType QRScanner::scanType() const diff --git a/src/main.cpp b/src/main.cpp index 154c71f..72587e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,9 +28,9 @@ #include "QMLImportHelper.h" #include "MenuModel.h" #include "ModuleManager.h" -#ifdef MOBILE -#include "CameraController.h" #include "QRScanner.h" +#ifndef NO_MULTIMEDIA +# include "CameraController.h" #endif #include // for ECC_Start() @@ -146,8 +146,8 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("MenuModel", &menuModel); engine.rootContext()->setContextProperty("ModuleManager", &modules); -#ifdef MOBILE qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); +#ifndef NO_MULTIMEDIA CameraController *cc = new CameraController(app); app->setCameraController(cc); engine.rootContext()->setContextProperty("CameraController", cc); -- 2.54.0 From 2a507a6a969824fd90c8b3cca703275bfc0d4adb Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 18:33:23 +0200 Subject: [PATCH 0869/1428] Move the TextPasteDecorator (and icon) to common The widget actually is pretty useful on desktop too. --- guis/{mobile => Flowee}/TextPasteDecorator.qml | 4 ++-- guis/{mobile => }/images/edit-clipboard-light.svg | 0 guis/{mobile => }/images/edit-clipboard.svg | 0 guis/mobile.qrc | 3 --- guis/mobile/ImportWalletPage.qml | 2 +- guis/widgets.qrc | 3 +++ modules/build-transaction/PayToOthers.qml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename guis/{mobile => Flowee}/TextPasteDecorator.qml (98%) rename guis/{mobile => }/images/edit-clipboard-light.svg (100%) rename guis/{mobile => }/images/edit-clipboard.svg (100%) diff --git a/guis/mobile/TextPasteDecorator.qml b/guis/Flowee/TextPasteDecorator.qml similarity index 98% rename from guis/mobile/TextPasteDecorator.qml rename to guis/Flowee/TextPasteDecorator.qml index 5f99039..e1d53f3 100644 --- a/guis/mobile/TextPasteDecorator.qml +++ b/guis/Flowee/TextPasteDecorator.qml @@ -29,8 +29,8 @@ Item { id: root y: buddy.height - height / 3 anchors.horizontalCenter: buddy.horizontalCenter - width: 10 - height: labelText.height + 10 + implicitWidth: 10 + implicitHeight: labelText.height + 10 required property Item buddy; property alias clipboardTypes: cbh.filter diff --git a/guis/mobile/images/edit-clipboard-light.svg b/guis/images/edit-clipboard-light.svg similarity index 100% rename from guis/mobile/images/edit-clipboard-light.svg rename to guis/images/edit-clipboard-light.svg diff --git a/guis/mobile/images/edit-clipboard.svg b/guis/images/edit-clipboard.svg similarity index 100% rename from guis/mobile/images/edit-clipboard.svg rename to guis/images/edit-clipboard.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index b2c1264..06f7ef2 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -35,8 +35,6 @@ mobile/images/tx-coinbase-light.svg mobile/images/tx-move.svg mobile/images/tx-move-light.svg - mobile/images/edit-clipboard.svg - mobile/images/edit-clipboard-light.svg mobile/images/ribbon.svg mobile/images/flash.svg mobile/images/flash-light.svg @@ -89,6 +87,5 @@ mobile/UnlockWalletPanel.qml mobile/UnlockApplication.qml mobile/LockApplication.qml - mobile/TextPasteDecorator.qml diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index bc8121f..33e105d 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -159,7 +159,7 @@ Page { height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) } - TextPasteDecorator { + Flowee.TextPasteDecorator { id: pasteButton buddy: secretText clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 523f94d..8be1fa8 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -6,6 +6,8 @@ images/eye-open.png images/edit-copy.svg images/edit-copy-light.svg + images/edit-clipboard.svg + images/edit-clipboard-light.svg images/internet.svg images/CashTokens.svg images/cashfusion.svg @@ -29,6 +31,7 @@ Flowee/ScrollThumb.qml Flowee/TabBar.qml Flowee/TextField.qml + Flowee/TextPasteDecorator.qml Flowee/ComboBox.qml Flowee/MoneyValueField.qml Flowee/FiatValueField.qml diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index c3946a2..0097d43 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -376,7 +376,7 @@ Page { } } } - TextPasteDecorator { + Flowee.TextPasteDecorator { buddy: destination clipboardTypes: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses y: destination.y + destination.height - height / 3 -- 2.54.0 From 5d131178f24bb2db51bae763a7c8e2b18d65e8e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 19:39:53 +0200 Subject: [PATCH 0870/1428] Make the groupBox fall in line with the rest This adjusts the colors and qt6 qml standards. Also various small bugfixes for non-collapsable boxes. --- guis/Flowee/GroupBox.qml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index 3ca0dc8..f179a7c 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,7 +35,7 @@ QQC2.Control { default property alias content: child.children property alias columns: child.columns activeFocusOnTab: collapsable - clip: true + clip: collapsable onCollapsedChanged: effectiveCollapsed = collapsed @@ -46,9 +46,9 @@ QQC2.Control { width: parent.width y: titleArea.visible ? titleLabel.height / 2 : arrowPoint.height / 2 height: root.effectiveCollapsed ? 1 : parent.height - y; - color: root.palette.light - border.color: root.palette.mid - border.width: 1.3 + color: palette.alternateBase + border.color: palette.mid + border.width: root.collapsable ? 1.3 : 0 radius: 3 } MouseArea { @@ -65,18 +65,18 @@ QQC2.Control { visible: titleLabel.text !== "" width: titleLabel.width + 6 + (summaryLabel.visible ? summaryLabel.width + 6 : 0) height: titleLabel.height - anchors.left: arrowPoint.right + x: arrowPoint.x + (root.collapsable ? arrowPoint.width : 0) Rectangle { // erase the groupbox outline behind the text width: parent.width - color: titleLabel.palette.window + color: palette.window height: 2 y: parent.height / 2 - 1 } Label { id: titleLabel x: 3 - color: root.palette.windowText + color: palette.windowText // focus indicator Rectangle { @@ -84,7 +84,7 @@ QQC2.Control { anchors.leftMargin: -2 anchors.rightMargin: -2 color: "#00000000" - border.color: parent.palette.highlight + border.color: palette.highlight border.width: 0.8 visible: root.activeFocus } @@ -96,14 +96,14 @@ QQC2.Control { anchors.leftMargin: 20 visible: root.effectiveCollapsed && text !== "" font.italic: true - color: root.palette.windowText + color: palette.windowText } } Rectangle { id: arrowPoint visible: parent.collapsable - color: titleLabel.palette.window + color: palette.window width: point.width + 12 height: point.height + 2 x: 6 -- 2.54.0 From 84e72bc606db978945bff3497bfd5ff80ef2a09c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 19:40:27 +0200 Subject: [PATCH 0871/1428] Move image from mobile to common. --- guis/{mobile => }/images/qr-code-scan-light.svg | 0 guis/{mobile => }/images/qr-code-scan.svg | 0 guis/mobile.qrc | 2 -- guis/widgets.qrc | 2 ++ 4 files changed, 2 insertions(+), 2 deletions(-) rename guis/{mobile => }/images/qr-code-scan-light.svg (100%) rename guis/{mobile => }/images/qr-code-scan.svg (100%) diff --git a/guis/mobile/images/qr-code-scan-light.svg b/guis/images/qr-code-scan-light.svg similarity index 100% rename from guis/mobile/images/qr-code-scan-light.svg rename to guis/images/qr-code-scan-light.svg diff --git a/guis/mobile/images/qr-code-scan.svg b/guis/images/qr-code-scan.svg similarity index 100% rename from guis/mobile/images/qr-code-scan.svg rename to guis/images/qr-code-scan.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 06f7ef2..bfcd000 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -17,8 +17,6 @@ mobile/images/receive.svg mobile/images/qr-code.svg mobile/images/qr-code-light.svg - mobile/images/qr-code-scan.svg - mobile/images/qr-code-scan-light.svg mobile/images/backspace.svg mobile/images/backspace-light.svg mobile/images/confirmIcon.svg diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 8be1fa8..d28e0f2 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -14,6 +14,8 @@ images/lock-light.svg images/lock.svg images/Flowee-Symbols.otf + images/qr-code-scan.svg + images/qr-code-scan-light.svg Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml Flowee/BigButton.qml -- 2.54.0 From ad53051b3abf6068eefe6e351577eec1bffc08fc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 20:32:03 +0200 Subject: [PATCH 0872/1428] Go with the times. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 0812e0d..1e72551 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1106,7 +1106,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons auto wallet = createWallet(walletName); wallet->setSingleAddressWallet(true); if (startHeight <= 1) - startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; + startHeight = m_chain == P2PNet::MainChain ? 750000 : 1000; wallet->addPrivateKey(words.first().toString(), startHeight); emit walletsChanged(); if (!m_offline) -- 2.54.0 From b0ca7d8f8d065664a1650abd1590209baa553bf8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 May 2024 22:15:51 +0200 Subject: [PATCH 0873/1428] Re-do the 'import wallet' screen (desktop) This follows the re-designed the 'import wallet' to now have the same design and featureset from mobile also on desktop. The 'new-wallet' pages in general have also been changed as to avoid wasting most of the space on the advertisment style content and aim to have a vertical design so we avoid forcing people having to scroll. --- guis/desktop/NewAccountCreateBasicAccount.qml | 77 ++- guis/desktop/NewAccountCreateHDAccount.qml | 116 ++-- guis/desktop/NewAccountImportAccount.qml | 631 +++++++++++++----- guis/desktop/NewAccountPane.qml | 166 ++--- 4 files changed, 643 insertions(+), 347 deletions(-) diff --git a/guis/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml index f8f4ed1..d415815 100644 --- a/guis/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021, 2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,36 +15,57 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + import QtQuick -import QtQuick.Controls import QtQuick.Layouts import "../Flowee" as Flowee -ColumnLayout { - spacing: 10 - Label { - id: title - text: qsTr("This creates a new empty wallet with simple multi-address capability") + +Item { + implicitWidth: columnWidth // columnWidth is defined by loader in NewAccountPane + height: col.height + + // ColumnLayoud is trying to outsmart us and when I supply implicitWidth it ends up giving + // one that is based on its content instead of the one I specifically told it to use. + // Which is why we wrap the columnLayout in an Item. + + ColumnLayout { + id: col + spacing: 10 width: parent.width - } - RowLayout { - id: nameRow - Label { - text: qsTr("Name") + ":"; - Layout.alignment: Qt.AlignBaseline + + Flowee.Label { + id: title + text: qsTr("Create a new empty wallet with simple multi-address capability ") + columnWidth + Layout.fillWidth: true + font.bold: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } - Flowee.TextField { - id: accountName + RowLayout { + id: nameRow + spacing: 6 + Flowee.Label { + text: qsTr("Name") + ":"; + Layout.alignment: Qt.AlignBaseline + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } } - } - Item { - height: button.height - Layout.fillWidth: true + + Flowee.CheckBox { + id: singleAddress + text: qsTr("Force Single Address"); + toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") + checked: true + } + Flowee.Button { id: button text: qsTr("Go") - anchors.right: parent.right + Layout.alignment: Qt.AlignRight onClicked: { var options = Pay.createNewBasicWallet(accountName.text); options.forceSingleAddress = singleAddress.checked; @@ -54,22 +75,8 @@ ColumnLayout { break; } } - root.visible = false; + newAccountsPane.visible = false; } } } - - Flowee.GroupBox { - id: gb - title: qsTr("Advanced Options") - collapsed: true - Layout.fillWidth: true - columns: 1 - - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - toolTipText: qsTr("When enabled, this wallet will be limited to one address.\nThis ensures only one private key will need to be backed up") - } - } } diff --git a/guis/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml index 78debfd..9d61c78 100644 --- a/guis/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021, 2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,71 +16,73 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls import QtQuick.Layouts import "../Flowee" as Flowee -ColumnLayout { +Item { id: newAccountCreateHDAccount - spacing: 10 - Label { - id: title - text: qsTr("This creates a new empty wallet with smart creation of addresses from a single seed-phrase") - width: parent.width - } - RowLayout { - id: nameRow - Label { - text: qsTr("Name") + ":"; - Layout.alignment: Qt.AlignBaseline - } - Flowee.TextField { - id: accountName - } - } - Item { - height: button.height - Layout.fillWidth: true + height: col.height + implicitWidth: columnWidth // columnWidth is defined by loader in NewAccountPane - Flowee.Button { - id: button - text: qsTr("Go") - anchors.right: parent.right - onClicked: { - var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); - for (let a of portfolio.accounts) { - if (a.id === options.accountId) { - portfolio.current = a; - break; - } - } - root.visible = false; + ColumnLayout { + id: col + spacing: 10 + width: parent.width + Flowee.Label { + id: title + text: qsTr("Creates a new wallet with smart creation of addresses from a single seed-phrase") + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.bold: true + } + RowLayout { + id: nameRow + Flowee.Label { + text: qsTr("Name") + ":"; + Layout.alignment: Qt.AlignBaseline + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true } } - } + Item { + height: button.height + Layout.fillWidth: true - Flowee.GroupBox { - title: qsTr("Advanced Options") - Layout.fillWidth: true - collapsed: true - columns: 3 + Flowee.Button { + id: button + text: qsTr("Go") + anchors.right: parent.right + onClicked: { + var options = Pay.createNewWallet(derivationPath.text, /* password */"", accountName.text); + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } + newAccountsPane.visible = false; + } + } + } - /* - Flowee.CheckBox { - id: schnorr - text: qsTr("Default to signing using ECDSA"); - toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") - Layout.columnSpan: 2 - } */ - Label { - text: qsTr("Derivation") + ":" - Layout.fillWidth: false + Flowee.GroupBox { + title: qsTr("Advanced Options") + Layout.fillWidth: true + columns: 3 + + Flowee.Label { + text: qsTr("Derivation") + ":" + Layout.fillWidth: false + } + Flowee.TextField { + id: derivationPath + text: "m/44'/0'/0'" // What most wallets use to import by default + color: Pay.checkDerivation(text) ? palette.text : "red" + Layout.fillWidth: true + } + Item { width: 1; height: 1; Layout.fillWidth: true } // spacer } - Flowee.TextField { - id: derivationPath - text: "m/44'/0'/0'" // What most wallets use to import by default - color: Pay.checkDerivation(text) ? palette.text : "red" - } - Item { width: 1; height: 1; Layout.fillWidth: true } // spacer } } diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 645de65..7ec6cd9 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -16,183 +16,500 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay -GridLayout { - id: importAccount - columns: 3 - rowSpacing: 10 +Item { + id: root - property var typedData: Pay.identifyString(secrets.text) - property bool finished: typedData === Wallet.PrivateKey - || ((typedData === Wallet.CorrectMnemonic - || typedData === Wallet.ElectrumMnemonic) - && derivationPath.derivationOk); - property bool isMnemonic: typedData === Wallet.CorrectMnemonic - || typedData === Wallet.PartialMnemonic - || typedData === Wallet.PartialMnemonicWithTypo - || typedData === Wallet.ElectrumMnemonic; - property bool isElectrumMnemonic: typedData === Wallet.ElectrumMnemonic - property bool isPrivateKey: typedData === Wallet.PrivateKey + implicitWidth: { + var w = columnWidth // columnWidth is defined by loader in NewAccountPane + if (privKeyColumn.visible) + w += columnWidth + 10 + return w; + } + implicitHeight: 200 - Label { - text: qsTr("Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key.") - Layout.columnSpan: 3 - wrapMode: Text.Wrap - } - Label { - text: qsTr("Secret", "The seed-phrase or private key") + ":" - Layout.alignment: Qt.AlignBaseline - } - Flowee.MultilineTextField { - id: secrets - Layout.fillWidth: true - nextFocusTarget: accountName - placeholderText: qsTr("Example: %1", "placeholder text").arg("L5bxhjPeQqVFgCLALiFaJYpptdX6Nf6R9TuKgHaAikcNwg32Q4aL") - } - Label { - id: feedback - text: importAccount.finished ? "✔" : " " - color: Pay.useDarkSkin ? "#37be2d" : "green" - font.pixelSize: 24 - Layout.alignment: Qt.AlignTop - } - Label { - text: qsTr("Name") + ":" - Layout.alignment: Qt.AlignBaseline - } - Flowee.TextField { - id: accountName - onAccepted: if (startImport.enabled) startImport.clicked() - Layout.columnSpan: 2 - } - RowLayout { - Layout.fillWidth: true - Layout.columnSpan: 3 - - Label { - id: detectedType - color: typedData === Wallet.PartialMnemonicWithTypo ? "red" : feedback.color - text: { - var typedData = importAccount.typedData - if (typedData === Wallet.PrivateKey) - return qsTr("Private key", "description of type") // TODO print address to go with it - if (typedData === Wallet.CorrectMnemonic) { - if (forceElectrum.checked) - return qsTr("BIP 39 seed-phrase (interpreted as Electrum format)", "description of type") - return qsTr("BIP 39 seed-phrase", "description of type") - } - if (typedData === Wallet.ElectrumMnemonic) - return qsTr("Electrum seed-phrase", "description of type") - if (typedData === Wallet.PartialMnemonicWithTypo) - return qsTr("Unrecognized word", "Word from the seed-phrases lexicon") - if (typedData === Wallet.MissingLexicon) - return "Installation error; no lexicon found"; // intentionally not translated, end-users should not see this - return "" - } + states: [ + State { + name: "inputState" + PropertyChanges { target: privKeyColumn; opacity: 0 } + PropertyChanges { target: seedDetailsColumn; opacity: 0 } + }, + State { + name: "privKeyDetailsState" + PropertyChanges { target: inputColumn; opacity: 0.65 } + PropertyChanges { target: privKeyColumn; opacity: 1 } + PropertyChanges { target: seedDetailsColumn; opacity: 0 } + }, + State { + name: "seedDetailsState" + PropertyChanges { target: inputColumn; opacity: 0.65 } + PropertyChanges { target: privKeyColumn; opacity: 0 } + PropertyChanges { target: seedDetailsColumn; opacity: 1 } } - Item { width: 1; height: 1; Layout.fillWidth: true } // spacer - Flowee.Button { - id: startImport - enabled: importAccount.finished + ] + state: "inputState" - text: qsTr("Import wallet") - onClicked: { - var sh = parseInt("0" + startHeight.text, 10); - if (sh === 0) // the genesis was block 1, zero doesn't exist - sh = 1; - if (importAccount.isMnemonic) - var options = Pay.createImportedHDWallet(secrets.text, passwordField.text, derivationPath.text, accountName.text, sh, forceElectrum.checked); - else - options = Pay.createImportedWallet(secrets.text, accountName.text, sh) - options.forceSingleAddress = singleAddress.checked; + function toNextPage() { + var type = inputColumn.typedData; + if (type === Wallet.PrivateKey) { + state = "privKeyDetailsState"; + } else if (type === Wallet.CorrectMnemonic || type === Wallet.ElectrumMnemonic) { + state = "seedDetailsState"; + } + } - for (let a of portfolio.accounts) { - if (a.id === options.accountId) { - portfolio.current = a; - break; + Column { + id: inputColumn + width: columnWidth + spacing: 10 + + property var typedData: Pay.identifyString(secretText.totalText) + property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic || typedData === Wallet.ElectrumMnemonic + + onFinishedChanged: root.toNextPage(); + Flowee.GroupBox { + id: buttonsBox + title: qsTr("Select import method") + collapsable: false + width: parent.width + Item { + width: parent.width + height: scanButton.height + Flowee.ImageButton { + id: scanButton + source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + onClicked: scanner.start(); + iconSize: Math.min(inputColumn.width / 4, 100) + x: (parent.width - width) / 2 // while NFC is not enabled.. + // x: (parent.width - width * 2 - 20) / 2 + text: qsTr("Camera") + } + QRScanner { + id: scanner + scanType: QRScanner.SeedOrPrivKey + onFinished: { + if (scanResult !== "") + secretText.text = scanResult; + // make sure to give focus back to this page after the camera took it. + scanButton.forceActiveFocus(); } } - root.visible = false; - } - } - } + Rectangle { + id: nfcButton + width: scanButton.width + visible: false + height: width + radius: 90 + color: "#00000000" + border.color: "yellow" + border.width: 2 + x: (parent.width - width * 2 - 20) / 2 + width + 20 - Flowee.GroupBox { - title: qsTr("Advanced Options") - collapsed: true - Layout.columnSpan: 3 - Layout.fillWidth: true - - columns: 2 - /* - Flowee.CheckBox { - id: schnorr - text: qsTr("Default to signing using ECDSA"); - toolTipText: qsTr("When enabled, newer style Schnorr signatures are not set as default for this wallet.") - Layout.columnSpan: 2 - } */ - Flowee.CheckBox { - id: singleAddress - text: qsTr("Force Single Address"); - toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") - checked: true - visible: importAccount.isPrivateKey - Layout.columnSpan: 2 - } - Flowee.CheckBox { - id: forceElectrum - text: qsTr("Old Electrum Phrase"); - toolTipText: qsTr("When Electrum detection fails, and you are sure it was created in that wallet, enable this option.") - checked: importAccount.isElectrumMnemonic - visible: importAccount.isMnemonic - enabled: importAccount.isMnemonic && !importAccount.isElectrumMnemonic - property string prevDerPath: "" - onCheckedChanged: { - derivationLabel.enabled = !checked; - derivationPath.enabled = !checked; - if (checked) { - // Electrum mnemonics always use derivation path: "m/" and never anything else. - prevDerPath = derivationPath.text; - derivationPath.text = "m/"; - } else if (prevDerPath) { - derivationPath.text = prevDerPath; + Flowee.Label { + anchors.centerIn: parent + text: "NFC" + } } } - Layout.columnSpan: 2 } - Label { - text: qsTr("Start Height") + ":" + Row { + spacing: 15 + x: (inputColumn.width - width) / 2 + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } + Flowee.Label { + text: qsTr("OR") + } + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } } - Flowee.TextField { - id: startHeight - validator: IntValidator{bottom: 0; top: 999999} + + Flowee.GroupBox { + id: textSecretBox + title: qsTr("Secret as text", "The seed-phrase or private key") + collapsable: false + width: parent.width + Item { + width: parent.width + height: pasteButton.height / 3 * 2 + secretText.height + + Flowee.MultilineTextField { + id: secretText + width: parent.width + clip: true + height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) + } + + Flowee.TextPasteDecorator { + id: pasteButton + buddy: secretText + clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed + } + } + Flowee.Label { + text: { + if (inputColumn.typedData === Wallet.PartialMnemonicWithTypo) { + var bareText = secretText.text; + // if inputMethodComposing true, that makes it simple to avoid the word that + // is being edited the 'text' property of secretText omits that one. + if (!secretText.inputMethodComposing) { + // in case we're editing without there being an inputmethod we find the word + // based on the char-pos. + let cp = secretText.cursorPosition; + let startEditWord = bareText.lastIndexOf(' ', cp); + let endEditWord = bareText.indexOf(' ', cp); + + let before = bareText.substr(0, startEditWord); + let after = endEditWord > 0 ? bareText.substr(endEditWord) : ""; + bareText = before + after; + } + + if (Pay.identifyString(bareText) === Wallet.PartialMnemonicWithTypo) + return qsTr("Unknown word(s) found"); + } + return "" + } + color: mainWindow.errorRed + visible: text !== "" + } } - Label { - id: derivationLabel - text: qsTr("Derivation") + ":" - visible: !importAccount.isPrivateKey + + Behavior on opacity { NumberAnimation { } } + } + Item { + id: privKeyColumn + anchors.left: inputColumn.right + anchors.leftMargin: 10 + width: columnWidth + height: parent.height + visible: opacity > 0.3 + enabled: visible + + ColumnLayout { + spacing: 10 + width: parent.width + Flowee.GroupBox { + title: qsTr("Address to import") + collapsable: false + Layout.fillWidth: true + Flowee.LabelWithClipboard { + // this shows the bitcoincash address matching the private key + font.pixelSize: singleAddress.font.pixelSize * 0.9 + text: { + if (root.state !== "privKeyDetailsState") + return ""; + return Pay.addressForPrivKey(secretText.text); + } + } + } + Flowee.CheckBox { + id: singleAddress + Layout.fillWidth: true + text: qsTr("Force Single Address"); + toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") + checked: true + } + + Flowee.GroupBox { + title: qsTr("Name") + collapsable: false + Layout.fillWidth: true + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + } + + Flowee.GroupBox { + title: qsTr("Oldest Transaction") + collapsable: false + Layout.fillWidth: true + Item { + implicitWidth: parent.width + implicitHeight: ageButton.height + Flowee.BigButton { + id: ageButton + text: qsTr("Check Age", "online check for wallet age") + enabled: !privKeyImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + privKeyImportHelper.secretType = Wallet.PrivateKey + privKeyImportHelper.secret = secretText.text + } + ImportHelper { + id: privKeyImportHelper + onCheckingChanged: { + if (checking) + return; + emptyPrivKeyWarningLabel.visible = false; + ageButton.isMainButton = false; + if (resultCount === 0) { + emptyPrivKeyWarningLabel.visible = true; + } + else if (resultCount === 1) { + let height = startHeight(0); + oldestTransactionChooser.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser.item.year.currentIndex + = yearOnHeight(height) - 2010; + oldestTransactionChooser.item.enabled = false; + + privImportStartButton.isMainButton = true; + } + } + } + + // TODO add warning + } + } + Loader { + id: oldestTransactionChooser + Layout.fillWidth: true + sourceComponent: oldestTransactionChooser_component + } + } + Flowee.Label { + id: emptyPrivKeyWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for wallet") + visible: false + } + + Flowee.BigButton { + id: privImportStartButton + text: qsTr("Start") + Layout.alignment: Qt.AlignRight + + onClicked: { + if (privKeyImportHelper.resultCount > 0) { + var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) + } else { + var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2010, oldestTransactionChooser.item.month.currentIndex, 1); + options = Pay.createImportedWallet(secretText.text, accountName.text, sh) + options.forceSingleAddress = singleAddress.checked; + } + + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } + newAccountsPane.visible = false; + } + } } - Flowee.TextField { - id: derivationPath - property bool derivationOk: Pay.checkDerivation(text); - text: "m/44'/0'/0'" // What most BCH wallets are created with - visible: !importAccount.isPrivateKey - color: derivationOk ? palette.text : "red" + + Behavior on opacity { NumberAnimation { } } + } + + Column { + id: seedDetailsColumn + anchors.left: inputColumn.right + anchors.leftMargin: 10 + width: columnWidth + height: parent.height + visible: opacity > 0.3 + enabled: visible + + Flowee.GroupBox { + title: qsTr("Name") + width: parent.width + collapsable: false + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName2 + Layout.fillWidth: true + } } - Label { - text: qsTr("Alternate phrase") + ":" - visible: !importAccount.isPrivateKey + + Flowee.GroupBox { + title: qsTr("Password") + width: parent.width + collapsable: false + Flowee.TextField { + id: passwordField + Layout.fillWidth: true + placeholderText: qsTr("Optional") + } } - Flowee.TextField { - // according to the BIP39 spec this is the 'password', but from a UX - // perspective that is confusing and no actual wallet uses it - id: passwordField - visible: !importAccount.isPrivateKey - Layout.fillWidth: true + Flowee.GroupBox { + title: qsTr("Details") + width: parent.width + collapsable: false + + Item { + implicitWidth: parent.width + implicitHeight: seedCheckButton.height + Flowee.BigButton { + id: seedCheckButton + text: qsTr("Lookup Details", "online check for wallet details") + enabled: !seedImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + seedImportHelper.secretType = inputColumn.typedData + seedImportHelper.secret = secretText.text + seedImportHelper.password = passwordField.text + } + ImportHelper { + id: seedImportHelper + onCheckingChanged: { + if (checking) + return; + emptySeedWarningLabel.visible = false; + if (resultCount === 0) { // empty + seedCheckButton.isMainButton = false; + emptySeedWarningLabel.visible = true; + } + else if (resultCount >= 1) { + // TODO what to do if there are more then 1? + + let height = startHeight(0); + oldestTransactionChooser2.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser2.item.year.currentIndex + = yearOnHeight(height) - 2010; + oldestTransactionChooser2.item.enabled = false; + derivationPath.text = derivation(0); + derivationPath.enabled = false; + + seedCheckButton.isMainButton = false; + seedStartButton.isMainButton = true; + } + } + } + + // TODO add warning + } + } + + Flowee.GroupBox { + id: derivationLabel + title: qsTr("Derivation") + width: parent.width + collapsable: false + Flowee.TextField { + id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" + } + } + Flowee.GroupBox { + title: qsTr("Oldest Transaction") + width: parent.width + collapsable: false + Loader { + id: oldestTransactionChooser2 + Layout.fillWidth: true + sourceComponent: oldestTransactionChooser_component + } + } + Flowee.Label { + id: emptySeedWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for seed") + visible: false + width: parent.width + } + + Flowee.BigButton { + id: seedStartButton + text: qsTr("Start") + Layout.alignment: Qt.AlignRight + onClicked: { + if (seedImportHelper.resultCount > 0) { + var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), + seedImportHelper.isElectrumSeed(0)); + } else { + var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2010, oldestTransactionChooser2.item.month.currentIndex, 1); + options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, sh); + } + + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; + } + } + newAccountsPane.visible = false; + } + } + } + + Behavior on opacity { NumberAnimation { } } + } + + Item { + // non-gui items below. + + Component { + id: oldestTransactionChooser_component + Flow { + property alias month: inner_month + property alias year: inner_year + width: parent.width + spacing: 10 + Flowee.ComboBox { + id: inner_month + model: { + let locale = Qt.locale(); + var list = []; + for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + list.push(locale.monthName(i)); + } + return list; + } + width: implicitWidth * 1.3 // this makes it fit for bigger fonts. + } + Flowee.ComboBox { + id: inner_year + model: { + var list = []; + let last = new Date().getFullYear(); + for (let i = 2010; i <= last; ++i) { + list.push(i); + } + return list; + } + currentIndex: 10; + } + } } } + } diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index aeae48f..cf5a6c2 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -1,5 +1,5 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ import QtQuick.Layouts import "../Flowee" as Flowee FocusScope { - id: root + id: newAccountsPane anchors.fill: parent focus: true @@ -30,82 +30,41 @@ FocusScope { opacity: 0.4 MouseArea { anchors.fill: parent - onClicked: root.visible = false + hoverEnabled: true // to eat the hover events from the guys in the background. + onClicked: newAccountsPane.visible = false } } Rectangle { color: palette.window anchors.fill: contentArea anchors.margins: -10 // have an inset - MouseArea { anchors.fill: parent } } - Flickable { + Item { id: contentArea - anchors.fill: parent - anchors.margins: 50 - - contentWidth: width - contentHeight: contentAreaColumn.height + 20 - flickableDirection: Flickable.VerticalFlick + x: 50 + y: 50 + height: Math.min(parent.height - 100, 800); + width: parent.width - 100 clip: true - ScrollBar.vertical: ScrollBar { } - Flowee.CloseIcon { - id: closeIcon - anchors.margins: 6 - anchors.right: parent.right - onClicked: root.visible = false - } + MouseArea { anchors.fill: parent } + Item { + id: door + width: parent + height: parent + ColumnLayout { + id: verticalTabs + width: accountTypePreferred.implicitWidth + spacing: 10 + opacity: mainArea.item === null ? 1 : 0.65 - Column { - id: contentAreaColumn - width: contentArea.width - y: 10 - spacing: 20 - Label { - text: qsTr("New Bitcoin Cash Wallet") - anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 14 - } - Flow { - id: optionsRow - anchors.horizontalCenter: parent.horizontalCenter - activeFocusOnTab: true - focus: true - height: 320 - spacing: 20 - width: Math.min(contentArea.width - 30, 900) // smaller is OK, wider not - - property int selectorWidth: (width - spacing * 2) / 3; - property int selectedKey: 1 - - function cardClicked(key) { - if (selectedKey !== key) - selectedKey = key; - else // move scroll area. - contentArea.flick(0, -1000); - } - - Flowee.CardTypeSelector { - id: accountTypeBasic - key: 0 - title: qsTr("Basic") - width: parent.selectorWidth - onClicked: parent.cardClicked(key); - - features: [ - qsTr("Private keys based", "Property of a wallet"), - qsTr("Difficult to backup", "Context: wallet type"), - qsTr("Great for brief usage", "Context: wallet type") - ] - } Flowee.CardTypeSelector { id: accountTypePreferred - key: 1 - title: qsTr("HD wallet") - width: parent.selectorWidth - onClicked: parent.cardClicked(key); + title: qsTr("New HD wallet") + checkable: true + Layout.fillWidth: true + onClicked: mainArea.source = "./NewAccountCreateHDAccount.qml" features: [ qsTr("Seed-phrase based", "Context: wallet type"), @@ -115,57 +74,68 @@ FocusScope { } Flowee.CardTypeSelector { id: accountTypeImport - key: 2 - title: qsTr("Import") - width: parent.selectorWidth - onClicked: parent.cardClicked(key); + title: qsTr("Import Existing Wallet") + Layout.fillWidth: true + checkable: true + onClicked: mainArea.source = "./NewAccountImportAccount.qml" features: [ qsTr("Imports seed-phrase"), qsTr("Imports private key") ] } - } + Flowee.CardTypeSelector { + id: accountTypeBasic + Layout.fillWidth: true + title: qsTr("New Basic Wallet") + checkable: true + onClicked: mainArea.source = "./NewAccountCreateBasicAccount.qml" - Label { - id: description - anchors.left: optionsRow.left - text: { - var type = optionsRow.selectedKey - if (type == 0) - return qsTr("Basic wallet with private keys") - if (type == 1) - return qsTr("Seed-phrase wallet") - return qsTr("Import of an existing wallet") + features: [ + qsTr("Private keys based", "Property of a wallet"), + qsTr("Difficult to backup", "Context: wallet type"), + qsTr("Great for brief usage", "Context: wallet type") + ] } - font.pointSize: 14 } - StackLayout { - id: stack - currentIndex: optionsRow.selectedKey - anchors.left: optionsRow.left - anchors.right: optionsRow.right - width: parent.width + Loader { + id: mainArea + x: verticalTabs.width + 10 + property double columnWidth: verticalTabs.width * 1.4 - NewAccountCreateBasicAccount { } - NewAccountCreateHDAccount { } - NewAccountImportAccount { } + width: { + var i = item; + if (i === null) + return door.width - x; + return i.implicitWidth + } + onWidthChanged: { + if (mainArea.item == null) { + door.x = 0; + return; + } + var avail = contentArea.width + var taken = verticalTabs.width + mainArea.item.implicitWidth + if (taken < avail) + door.x = 0; + else + door.x = avail - taken; + } } + + Behavior on x { NumberAnimation { }} + } + Flowee.CloseIcon { + id: closeIcon + anchors.right: parent.right + onClicked: newAccountsPane.visible = false } } Component.onCompleted: forceActiveFocus() // we assume this component is used in a Loader Keys.onPressed: (event)=> { if (event.key === Qt.Key_Escape) { - root.visible = false; - event.accepted = true; - } - else if (event.key === Qt.Key_Left && optionsRow.activeFocus) { - optionsRow.selectedKey = Math.max(0, optionsRow.selectedKey - 1) - event.accepted = true; - } - else if (event.key === Qt.Key_Right && optionsRow.activeFocus) { - optionsRow.selectedKey = Math.min(2, optionsRow.selectedKey + 1) + newAccountsPane.visible = false; event.accepted = true; } } -- 2.54.0 From cb6fdd412f6e8faf8fa75e402a4aeb255a48d21e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 21:55:31 +0200 Subject: [PATCH 0874/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 6397b9d..2ea6e0d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="26" android:versionName="2024.05.0"> diff --git a/src/main.cpp b/src/main.cpp index 72587e8..4cc8145 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.02.0"); + qapp.setApplicationVersion("2024.05.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); -- 2.54.0 From 97c1485560001f367ffccb0c9e3dd08cc17902e9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 5 May 2024 21:55:57 +0200 Subject: [PATCH 0875/1428] Move the 'import' card up one. --- guis/mobile/NewAccount.qml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/guis/mobile/NewAccount.qml b/guis/mobile/NewAccount.qml index becfde7..bad0078 100644 --- a/guis/mobile/NewAccount.qml +++ b/guis/mobile/NewAccount.qml @@ -48,6 +48,18 @@ Page { ] } + Flowee.CardTypeSelector { + title: qsTr("Import Existing Wallet") + width: parent.width * 0.75 + onClicked: thePile.push("./ImportWalletPage.qml"); + anchors.horizontalCenter: parent.horizontalCenter + + features: [ + qsTr("Imports seed-phrase"), + qsTr("Imports private key") + ] + } + Flowee.CardTypeSelector { title: qsTr("New Basic Wallet") width: parent.width * 0.75 @@ -61,17 +73,6 @@ Page { ] } - Flowee.CardTypeSelector { - title: qsTr("Import Existing Wallet") - width: parent.width * 0.75 - onClicked: thePile.push("./ImportWalletPage.qml"); - anchors.horizontalCenter: parent.horizontalCenter - - features: [ - qsTr("Imports seed-phrase"), - qsTr("Imports private key") - ] - } } } -- 2.54.0 From bb9dfc9d4180952f95b20034789f2d936a9f7b7d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 May 2024 21:17:17 +0200 Subject: [PATCH 0876/1428] Small UX fix --- guis/mobile/GuiSettings.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index 0ce6a3e..a7d90a6 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -77,10 +77,12 @@ Page { text: "Follow System" checked: Pay.skinFollowsPlatform onClicked: Pay.skinFollowsPlatform = true; + width: parent.width } Flowee.RadioButton { text: "Dark" checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin + width: parent.width onClicked: { Pay.skinFollowsPlatform = false; Pay.useDarkSkin = true; @@ -89,6 +91,7 @@ Page { Flowee.RadioButton { text: "Light" checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin + width: parent.width onClicked: { Pay.skinFollowsPlatform = false; Pay.useDarkSkin = false; -- 2.54.0 From 3926268b5d8ba21de2c1d68f8c18ce68534f9801 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 May 2024 21:31:08 +0200 Subject: [PATCH 0877/1428] UX bugfix; only show instapay limit on payment scan This makes sure that if we use the QR scanning page for scanning a private key, this does not list our intapay limits. --- guis/mobile/QRScannerOverlay.qml | 2 ++ src/CameraController.cpp | 9 +++++++++ src/CameraController.h | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/mobile/QRScannerOverlay.qml index b860ee5..2f5d2d5 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/mobile/QRScannerOverlay.qml @@ -200,6 +200,8 @@ FocusScope { text: { if (isLoading) return ""; + if (!CameraController.isPayment) + return ""; // slight hack, make this field be re-calculated on camera active change in order to ensure // we have the latest limit (which doesn't have a signal of its owo) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 4ce2cdd..1fcd62c 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -65,6 +65,7 @@ public: QObject *videoSink = nullptr; QPointer scanRequest; + bool isPaymentType = true; // follows ScanType from the request mutable QMutex lock; QVideoFrame currentFrame; @@ -509,6 +510,9 @@ void CameraController::startRequest(QRScanner *request) { assert(request); d->scanRequest = request; + d->isPaymentType = request->scanType() == QRScanner::PaymentDetails + || request->scanType() == QRScanner::PaymentDetailsTestnet; + emit isPaymentChanged(); if (!d->visible) { d->visible = true; @@ -600,6 +604,11 @@ bool CameraController::supportsPaste() const return d->scanRequest->scanType() == QRScanner::PaymentDetails; } +bool CameraController::isPayment() const +{ + return d->isPaymentType; +} + bool CameraController::torchEnabled() const { return d->torchEnabled; diff --git a/src/CameraController.h b/src/CameraController.h index f6ead41..33d2b93 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -44,6 +44,8 @@ class CameraController : public QObject Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) /// return true if calling importScanFromClipboard can work with current QRScanner::ScanType Q_PROPERTY(bool supportsPaste READ supportsPaste NOTIFY visibleChanged) + /// if the QRScanner::ScanType is a payment. + Q_PROPERTY(bool isPayment READ isPayment NOTIFY isPaymentChanged) Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) Q_PROPERTY(bool torchEnabled READ torchEnabled WRITE setTorchEnabled NOTIFY torchEnabledChanged) @@ -71,6 +73,7 @@ public: bool cameraActive() const; bool visible() const; bool supportsPaste() const; + bool isPayment() const; bool torchEnabled() const; void setTorchEnabled(bool on); @@ -82,6 +85,7 @@ signals: void cameraActiveChanged(); void visibleChanged(); void torchEnabledChanged(); + void isPaymentChanged(); // \internal (used to move thread) void startCheckState(); -- 2.54.0 From 1e9cf9792bf1314922c15e996aa1b5e86fd6153b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 May 2024 22:56:13 +0200 Subject: [PATCH 0878/1428] Fix importing a HD wallet from an exact height. Now we actually start reading that exact block given. --- src/Wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index fad4b6c..748f1fd 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -856,8 +856,8 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons m_hdData->lastChangeKey = -1; QMutexLocker locker(&m_lock); if (startHeight < 1000000) { // when its a blockheight and not a timestamp. - m_segment->blockSynched(startHeight); - m_segment->blockSynched(startHeight); // yes, twice + m_segment->blockSynched(startHeight - 1); + m_segment->blockSynched(startHeight - 1); // yes, twice m_walletIsImporting = true; } deriveHDKeys(200, 200, startHeight); -- 2.54.0 From 2d0647409d47f36f74a3b04ae3d48452fbeb7864 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 May 2024 10:42:37 +0200 Subject: [PATCH 0879/1428] Print SSL warnings in -v mode Various self-signed electrum cash nodes exists, this shows in the debug output actual specific reason why they are rejected on connect. --- src/ImportHandler.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 765d130..2bad83a 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -41,6 +41,13 @@ ImportHandler::ImportHandler(QObject *parent) connect (m_electronServer, SIGNAL(disconnected()), this, SLOT(disconnected())); connect (m_electronServer, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect (m_electronServer, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); + + connect (m_electronServer, &QSslSocket::sslErrors, [=](const QList &errors) { + logInfo(10005) << "sslErrors" << errors.size(); + for (const auto &e : errors) { + logInfo(10005) << " " << e.errorString(); + } + }); } void ImportHandler::startCheck(WalletType type, const QString &text, const QString &password) @@ -81,11 +88,11 @@ void ImportHandler::connectionEstablished() // The second version is the highest we've tested to work. QString call("{\"jsonrpc\":\"2.0\"," "\"method\":\"server.version\"," - "\"params\": [\"Electron Cash 4.2.12\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); + "\"params\": [\"%1\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); -// QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); -// QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); -// call = call.arg(useragent); + QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); + QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); + call = call.arg(useragent); m_electronServer->write(call.toUtf8()); m_electronServer->write("\n"); -- 2.54.0 From 9efafda55ddb59b374aa01efb0088048d0e128de Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 May 2024 15:40:36 +0200 Subject: [PATCH 0880/1428] Reuse the QRScanner widget on desktop. "Desktop" in most people's cases means laptop, of which many have a camera. So why not allow the camera to be used to scan things? --- .../QRScanner.qml} | 15 +++++++------ guis/desktop/main.qml | 21 +++++++++++++++++-- guis/{mobile => }/images/flash-light.svg | 0 guis/{mobile => }/images/flash.svg | 0 guis/mobile.qrc | 3 --- guis/mobile/main.qml | 3 ++- guis/widgets.qrc | 3 +++ 7 files changed, 33 insertions(+), 12 deletions(-) rename guis/{mobile/QRScannerOverlay.qml => Flowee/QRScanner.qml} (97%) rename guis/{mobile => }/images/flash-light.svg (100%) rename guis/{mobile => }/images/flash.svg (100%) diff --git a/guis/mobile/QRScannerOverlay.qml b/guis/Flowee/QRScanner.qml similarity index 97% rename from guis/mobile/QRScannerOverlay.qml rename to guis/Flowee/QRScanner.qml index 2f5d2d5..b264072 100644 --- a/guis/mobile/QRScannerOverlay.qml +++ b/guis/Flowee/QRScanner.qml @@ -19,7 +19,6 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Shapes import QtMultimedia -import "../Flowee" as Flowee import Flowee.org.pay; FocusScope { @@ -28,6 +27,8 @@ FocusScope { enabled: visible onVisibleChanged: if (visible) root.forceActiveFocus(); + property bool showCloseButton: false + Rectangle { id: background anchors.fill: parent @@ -118,7 +119,7 @@ FocusScope { width: pasteButton.width height: pasteButton.height color: palette.base - Flowee.ImageButton { + ImageButton { id: pasteButton source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); text: qsTr("Paste") @@ -142,7 +143,7 @@ FocusScope { anchors.top: pasteButton.bottom visible: false - Flowee.Label { + Label { id: errorLabel anchors.centerIn: parent text: qsTr("Failed") @@ -166,7 +167,7 @@ FocusScope { width: flashButton.width height: flashButton.height color: palette.base - Flowee.ImageButton { + ImageButton { id: flashButton source: "qrc:/flash" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); opacity: CameraController.torchEnabled ? 0.3 : 1 @@ -179,16 +180,18 @@ FocusScope { width: parent.width height: Math.max(closeIcon.height, instaLabel.height) + 20 color: mainWindow.floweeBlue + visible: root.showCloseButton || instaLabel.text !== "" - Flowee.CloseIcon { + CloseIcon { id: closeIcon + visible: root.showCloseButton anchors.right: parent.right anchors.rightMargin: 10 y: 10 onClicked: CameraController.abort(); } - Flowee.Label { + Label { id: instaLabel anchors.left: parent.left anchors.top: parent.top diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 051a870..710b526 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors @@ -163,7 +163,7 @@ ApplicationWindow { layer.effect: effects.item.biggerBlur } } - Label { + Flowee.Label { id: totalFiatLabel anchors.baseline: balanceInHeader.baseline anchors.right: balanceInHeader.left @@ -811,5 +811,22 @@ ApplicationWindow { } } } + // QRScanner window + QQC2.ApplicationWindow { + id: scannerWindow + visible: CameraController.visible + minimumWidth: 300 + minimumHeight: 600 + width: 400 + height: 700 + title: qsTr("QR-Scan") + flags: Qt.Dialog + + onClosing: CameraController.abort(); + + Flowee.QRScanner { + anchors.fill: parent + } + } } } diff --git a/guis/mobile/images/flash-light.svg b/guis/images/flash-light.svg similarity index 100% rename from guis/mobile/images/flash-light.svg rename to guis/images/flash-light.svg diff --git a/guis/mobile/images/flash.svg b/guis/images/flash.svg similarity index 100% rename from guis/mobile/images/flash.svg rename to guis/images/flash.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index bfcd000..dc69369 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -34,8 +34,6 @@ mobile/images/tx-move.svg mobile/images/tx-move-light.svg mobile/images/ribbon.svg - mobile/images/flash.svg - mobile/images/flash-light.svg mobile/images/keyboard.svg mobile/images/keyboard-light.svg mobile/images/num-keyboard-light.svg @@ -56,7 +54,6 @@ mobile/TextButton.qml mobile/AccountSyncState.qml mobile/SendTransactionsTab.qml - mobile/QRScannerOverlay.qml mobile/PayWithQR.qml mobile/SlideToApprove.qml mobile/ReceiveTab.qml diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 7704e94..9c9c734 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -86,9 +86,10 @@ ApplicationWindow { id: menuOverlay anchors.fill: parent } - QRScannerOverlay { + Flowee.QRScanner { id: scannerOverlay anchors.fill: parent + showCloseButton: true } Loader { source: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "" diff --git a/guis/widgets.qrc b/guis/widgets.qrc index d28e0f2..86dcfd3 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -13,6 +13,8 @@ images/cashfusion.svg images/lock-light.svg images/lock.svg + images/flash.svg + images/flash-light.svg images/Flowee-Symbols.otf images/qr-code-scan.svg images/qr-code-scan-light.svg @@ -52,6 +54,7 @@ Flowee/WalletSecretsView.qml Flowee/HamburgerMenu.qml Flowee/QRWidget.qml + Flowee/QRScanner.qml Flowee/AddressInfoWidget.qml Flowee/FiatTxInfo.qml -- 2.54.0 From dc3ebbcd0961799a05f637e785e8a1af87eb34fd Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 May 2024 21:06:47 +0200 Subject: [PATCH 0881/1428] Import translations from Crowdin --- translations/floweepay-common_en.ts | 136 ++-- translations/floweepay-common_nl.ts | 136 ++-- translations/floweepay-desktop_en.ts | 730 +++++++++++--------- translations/floweepay-desktop_nl.ts | 730 +++++++++++--------- translations/floweepay-mobile_en.ts | 418 ++++++----- translations/floweepay-mobile_nl.ts | 416 ++++++----- translations/module-build-transaction_en.ts | 23 +- translations/module-build-transaction_nl.ts | 23 +- translations/module-peers-view_en.ts | 96 ++- 9 files changed, 1515 insertions(+), 1193 deletions(-) diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index c11e88f..ab31ac7 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Wallet: Up to date - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Behind: %1 day @@ -31,17 +31,17 @@ - + Up to date Up to date - + Updating Updating - + Still %1 hours behind Still an hour behind @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity Coin has been fused for increased anonymity + + FiatTxInfo + + + Value now + Value now + + + + Value then + Value then + + FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -286,12 +299,12 @@ Transaction too large. Amount selected needs too many coins. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -310,6 +323,29 @@ Payment request expired + + QRScanner + + + Paste + Paste + + + + Failed + Failed + + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + + QRWidget @@ -318,17 +354,25 @@ Copied to clipboard + + TextPasteDecorator + + + Paste + Paste + + Wallet - - + + Change #%1 Change #%1 - - + + Main #%1 Main #%1 @@ -336,12 +380,12 @@ WalletCoinsModel - + Unconfirmed Unconfirmed - + %1 hours age, like: hours old @@ -350,7 +394,7 @@ - + %1 days age, like: days old @@ -359,7 +403,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +412,7 @@ - + %1 months age, like: months old @@ -377,7 +421,7 @@ - + Change #%1 Change #%1 @@ -385,27 +429,27 @@ WalletHistoryModel - + Today Today - + Yesterday Yesterday - + Earlier this week Earlier this week - + This week This week - + Earlier this month Earlier this month @@ -413,12 +457,12 @@ WalletSecretsView - + Explanation Explanation - + Coins a / b a) active coin-count. b) historical coin-count. @@ -427,23 +471,33 @@ b) historical coin-count. - - + + Copy Address Copy Address - + + QR of Address + QR of Address + + + Copy Private Key Copy Private Key - + + QR of Private Key + QR of Private Key + + + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signed with Schnorr signatures in the past diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index bc3bd58..bac30cc 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Portemonnee: gesynchroniseerd - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days %1 dag oude data @@ -31,17 +31,17 @@ - + Up to date Is volledig bijgewerkt - + Updating Aan het bijwerken - + Still %1 hours behind Nog één uur @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity Munt is gefuseerd voor verhoogde anonimiteit + + FiatTxInfo + + + Value now + Waarde nu + + + + Value then + Waarde toen + + FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -286,12 +299,12 @@ Transactie te groot. Geselecteerd bedrag vereist te veel munten. - + Request received over insecure channel. Anyone could have altered it! Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! - + Download of payment request failed. Downloaden van betalingsverzoek mislukt. @@ -310,6 +323,29 @@ Betalingsverzoek verlopen + + QRScanner + + + Paste + Plak + + + + Failed + Mislukt + + + + Instant Pay limit is %1 + Directbetalen limiet is %1 + + + + Selected wallet: '%1' + Geselecteerde portemonnee: '%1' + + QRWidget @@ -318,17 +354,25 @@ Naar klembord gekopieerd + + TextPasteDecorator + + + Paste + Plak + + Wallet - - + + Change #%1 Wisselmunt #%1 - - + + Main #%1 Standaard#%1 @@ -336,12 +380,12 @@ WalletCoinsModel - + Unconfirmed Onbevestigd - + %1 hours age, like: hours old @@ -350,7 +394,7 @@ - + %1 days age, like: days old @@ -359,7 +403,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +412,7 @@ - + %1 months age, like: months old @@ -377,7 +421,7 @@ - + Change #%1 Wisselmunt #%1 @@ -385,27 +429,27 @@ WalletHistoryModel - + Today Vandaag - + Yesterday Gisteren - + Earlier this week Eerder deze week - + This week Deze week - + Earlier this month Eerder deze maand @@ -413,12 +457,12 @@ WalletSecretsView - + Explanation Uitleg - + Coins a / b a) active coin-count. b) historical coin-count. @@ -427,23 +471,33 @@ b) historische munten. - - + + Copy Address Kopieer adres - + + QR of Address + QR van het adres + + + Copy Private Key Kopieer privésleutel - + + QR of Private Key + QR van de privésleutel + + + Coins: %1 / %2 Munten: %1 / %2 - + Signed with Schnorr signatures in the past In het verleden ondertekend met Schnorr diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 37d1803..2793fc4 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -19,28 +19,28 @@ Archive Wallet - + Make Primary Make Primary - + ★ Primary ★ Primary - + Protect With Pin... Protect With Pin... - + Open Open encrypted wallet Open - + Close Close encrypted wallet Close @@ -64,92 +64,92 @@ Sync Status - + Encryption Encryption - + Password Password - + Open Open - + Invalid PIN Invalid PIN - + Include balance in total Include balance in total - + Hide in private mode Hide in private mode - + Address List Address List - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Backup details Backup details - + Seed-phrase Seed-phrase - + Seed format Seed format - + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. @@ -162,10 +162,58 @@ Last Transaction + + AddressDbStats + + + IP Addresses + IP Addresses + + + + Total found + Total found + + + + Tried + Tried + + + + Punished count + Punished count + + + + Banned count + Banned count + + + + IP-v4 count + IP-v4 count + + + + IP-v6 count + IP-v6 count + + + + Pardon the Banned + Pardon the Banned + + + + Close + Close + + NetView - + Peers (%1) One peer @@ -173,48 +221,38 @@ - + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - initializing connection - - - - Verifying peer - Verifying peer - - - - Assigning... - Assigning... - - - + Peer for wallet: %1 Peer for wallet: %1 - - Peer for initial wallet - Peer for initial wallet + + Disconnect Peer + Disconnect Peer - + + Ban Peer + Ban Peer + + + Close Close @@ -222,32 +260,27 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - This creates a new empty wallet with simple multi-address capability + + Create a new empty wallet with simple multi-address capability + Create a new empty wallet with simple multi-address capability - + Name Name - + Go Go - - Advanced Options - Advanced Options - - - + Force Single Address Force Single Address - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. @@ -257,27 +290,27 @@ This ensures only one private key will need to be backed up NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - This creates a new empty wallet with smart creation of addresses from a single seed-phrase + + Creates a new wallet with smart creation of addresses from a single seed-phrase + Creates a new wallet with smart creation of addresses from a single seed-phrase - + Name Name - + Go Go - + Advanced Options Advanced Options - + Derivation Derivation @@ -285,188 +318,172 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - - - - Secret - The seed-phrase or private key - Secret - - - - Example: %1 - placeholder text - Example: %1 - - - + + Name Name - - Private key - description of type - Private key - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 seed-phrase (interpreted as Electrum format) - - - - BIP 39 seed-phrase - description of type - BIP 39 seed-phrase + + Select import method + Select import method - Electrum seed-phrase - description of type - Electrum seed-phrase + Camera + Camera - - Unrecognized word - Word from the seed-phrases lexicon - Unrecognized word + + OR + OR - - Import wallet - Import wallet + + Secret as text + The seed-phrase or private key + Secret as text - - Advanced Options - Advanced Options + + Unknown word(s) found + Unknown word(s) found - + + Address to import + Address to import + + + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - Old Electrum Phrase - Old Electrum Phrase + + + Oldest Transaction + Oldest Transaction - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + Check Age + online check for wallet age + Check Age - - Start Height - Start Height + + Nothing found for wallet + Nothing found for wallet - + + + Start + Start + + + + Password + Password + + + + Optional + Optional + + + + Details + Details + + + + Lookup Details + online check for wallet details + Lookup Details + + + + Nothing found for seed + Nothing found for seed + + + Derivation Derivation - - - Alternate phrase - Alternate phrase - NewAccountPane - - New Bitcoin Cash Wallet - New Bitcoin Cash Wallet + + New HD wallet + New HD wallet - - Basic - Basic + + Import Existing Wallet + Import Existing Wallet - + + New Basic Wallet + New Basic Wallet + + + Private keys based Property of a wallet Private keys based - + Difficult to backup Context: wallet type Difficult to backup - + Great for brief usage Context: wallet type Great for brief usage - - HD wallet - HD wallet - - - + Seed-phrase based Context: wallet type Seed-phrase based - + Easy to backup Context: wallet type Easy to backup - + Most compatible The most compatible wallet type Most compatible - - Import - Import - - - + Imports seed-phrase Imports seed-phrase - + Imports private key Imports private key - - - Basic wallet with private keys - Basic wallet with private keys - - - - Seed-phrase wallet - Seed-phrase wallet - - - - Import of an existing wallet - Import of an existing wallet - PaymentTweakingPanel @@ -662,6 +679,7 @@ Change will come back to the imported key. + Copy Address Copy Address @@ -764,60 +782,251 @@ Change will come back to the imported key. Settings - + Unit Unit - + Show Bitcoin Cash value on Activity page Show Bitcoin Cash value on Activity page - + Show Block Notifications Show Block Notifications - + When a new block is mined, Flowee Pay shows a desktop notification When a new block is mined, Flowee Pay shows a desktop notification - + Night Mode Night Mode - + Private Mode Private Mode - + Hides private wallets while enabled Hides private wallets while enabled - + + Font sizing + Font sizing + + + Version Version - + Library Version Library Version - + Synchronization Synchronization - + Network Status Network Status + + + Address Stats + Address Stats + + + + Transaction + + + Miner Reward + Miner Reward + + + + Fused + Fused + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + rejected + rejected + + + + TransactionDetails + + + Transaction Details + Transaction Details + + + + First Seen + First Seen + + + + Rejected + Rejected + + + + Unconfirmed + Unconfirmed + + + + Mined at + Mined at + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + + + + + Comment + Comment + + + + Fees paid + Fees paid + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Size + Size + + + + %1 bytes + %1 bytes + + + + Is Coinbase + Is Coinbase + + + + Copy transaction-ID + Copy transaction-ID + + + + Fused from my addresses + Fused from my addresses + + + + Sent from my addresses + Sent from my addresses + + + + Sent from addresses + Sent from addresses + + + + Fused into my addresses + Fused into my addresses + + + + Received at addresses + Received at addresses + + + + Received at my addresses + Received at my addresses + + + + TransactionInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 blocks ago + %1 blocks ago + + + + + Fees + Fees + + + + Copy transaction-ID + Copy transaction-ID + + + + Holds a token + Holds a token + + + + Opening Website + Opening Website + WalletEncryption @@ -946,16 +1155,16 @@ Change will come back to the imported key. WalletEncryptionStatus - - - Pin to Pay - Pin to Pay - Pin to Open Pin to Open + + + Pin to Pay + Pin to Pay + (Opened) @@ -963,206 +1172,98 @@ Change will come back to the imported key. (Opened) - - WalletTransaction - - - Miner Reward - Miner Reward - - - - Fused - Fused - - - - Received - Received - - - - Moved - Moved - - - - Sent - Sent - - - - rejected - rejected - - - - unconfirmed - unconfirmed - - - - WalletTransactionDetails - - - Copy transaction-ID - Copy transaction-ID - - - - Status - Status - - - - rejected - rejected - - - - unconfirmed - unconfirmed - - - - %1 confirmations (mined in block %2) - - %1 confirmation (mined in block %2) - %1 confirmations (mined in block %2) - - - - - Copy block height - Copy block height - - - - Fees - Fees - - - - Size - Size - - - - %1 bytes - - %1 bytes - %1 bytes - - - - - Inputs - Inputs - - - - - Copy Address - Copy Address - - - - Outputs - Outputs - - main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + Offline Offline - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1171,9 +1272,14 @@ Change will come back to the imported key. - + Preparing... Preparing... + + + QR-Scan + QR-Scan + diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 6459fd5..17fce03 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -19,28 +19,28 @@ Portemonnee Archiveren - + Make Primary Maak primair - + ★ Primary ★ Primair - + Protect With Pin... Bescherm met Pin... - + Open Open encrypted wallet Open - + Close Close encrypted wallet Sluiten @@ -64,92 +64,92 @@ Synchronisatie status - + Encryption Encryptie - + Password Wachtwoord - + Open Open - + Invalid PIN Ongeldige PIN - + Include balance in total Saldo opnemen in totaal - + Hide in private mode Verbergen in privémodus - + Address List Adreslijst - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Backup details Back-up details - + Seed-phrase Herstelzin - + Seed format Herstelzin formaat - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -162,10 +162,58 @@ Laatste Transactie + + AddressDbStats + + + IP Addresses + IP-adressen + + + + Total found + Totaal gevonden + + + + Tried + Geprobeerd + + + + Punished count + Aantal bestraft + + + + Banned count + Aantal verbannen + + + + IP-v4 count + IP-v4-teller + + + + IP-v6 count + IP-v6 teller + + + + Pardon the Banned + Pardon de verbannen + + + + Close + Sluiten + + NetView - + Peers (%1) Peer (%1) @@ -173,48 +221,38 @@ - + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - verbinding initialiseren - - - - Verifying peer - Peer verifiëren - - - - Assigning... - Toewijzen... - - - + Peer for wallet: %1 Peer voor portemonnee: %1 - - Peer for initial wallet - Peer voor initiële portemonnee + + Disconnect Peer + Verbreek verbinding met peer - + + Ban Peer + Verban Peer + + + Close Sluiten @@ -222,32 +260,27 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden + + Create a new empty wallet with simple multi-address capability + Maak een nieuwe lege portemonnee met eenvoudige multi-address mogelijkheden - + Name Naam - + Go Start - - Advanced Options - Geavanceerde Opties - - - + Force Single Address Forceer één adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. @@ -257,27 +290,27 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - Dit maakt een nieuwe lege portemonnee die slim adressen maakt van één enkele herstelzin + + Creates a new wallet with smart creation of addresses from a single seed-phrase + Maakt een nieuwe portemonnee met slimme aanmaak van adressen van één enkele herstelzin - + Name Naam - + Go Start - + Advanced Options Geavanceerde Opties - + Derivation Derivatie @@ -285,188 +318,172 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - - - - Secret - The seed-phrase or private key - Geheim - - - - Example: %1 - placeholder text - Voorbeeld: %1 - - - + + Name Naam - - Private key - description of type - Privé-sleutel - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 herstelzin (geïnterpreteerd als Electrum formaat) - - - - BIP 39 seed-phrase - description of type - BIP 39 herstelzin + + Select import method + Kies import-methode - Electrum seed-phrase - description of type - Electron-Cash herstelzin + Camera + Kamera - - Unrecognized word - Word from the seed-phrases lexicon - Niet herkend woord + + OR + OF - - Import wallet - Portemonnee importeren + + Secret as text + The seed-phrase or private key + Geheim als tekst - - Advanced Options - Geavanceerde Opties + + Unknown word(s) found + Onbekende woord(en) gevonden - + + Address to import + Te importeren adres + + + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - - Old Electrum Phrase - Oude Electrum herstelzin + + + Oldest Transaction + Oudste transactie - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Zet deze optie aan als Electrum detectie mislukt en u zeker weet dat je herstelzin in die portemonnee is gemaakt. + + Check Age + online check for wallet age + Leeftijd opzoeken - - Start Height - Beginhoogte + + Nothing found for wallet + Niets gevonden voor portemonnee - + + + Start + Start + + + + Password + Wachtwoord + + + + Optional + Optioneel + + + + Details + Details + + + + Lookup Details + online check for wallet details + Details opzoeken + + + + Nothing found for seed + Niets gevonden voor herstelzin + + + Derivation Derivatie - - - Alternate phrase - Alternatieve zin - NewAccountPane - - New Bitcoin Cash Wallet - Nieuwe Bitcoin Cash Portemonnee + + New HD wallet + Nieuwe HD portemonnee - - Basic - Eenvoudig + + Import Existing Wallet + Importeer bestaande portemonnee - + + New Basic Wallet + Nieuwe Basic Portemonnee + + + Private keys based Property of a wallet Privé sleutels gebaseerd - + Difficult to backup Context: wallet type Back-up moeilijk - + Great for brief usage Context: wallet type Ideaal voor kort gebruik - - HD wallet - HD wallet - - - + Seed-phrase based Context: wallet type Herstelzin gebaseerd - + Easy to backup Context: wallet type Back-upt makkelijk - + Most compatible The most compatible wallet type Meest compatibel - - Import - Importeer - - - + Imports seed-phrase Importeert herstelzin - + Imports private key Importeert Privésleutel - - - Basic wallet with private keys - Basis portemonnee met privésleutels - - - - Seed-phrase wallet - Herstelzin portemonnee - - - - Import of an existing wallet - Importeer een bestaande portemonnee - PaymentTweakingPanel @@ -662,6 +679,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. + Copy Address Kopieer adres @@ -764,60 +782,251 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Instellingen - + Unit Eenheid - + Show Bitcoin Cash value on Activity page Toon Bitcoin Cash waarde op de activiteitenpagina - + Show Block Notifications Toon blok notificaties - + When a new block is mined, Flowee Pay shows a desktop notification Wanneer een nieuw blok is gedolven, toont Flowee Pay een desktop notificatie - + Night Mode Nachtmodus - + Private Mode Privé modus - + Hides private wallets while enabled Verbergt privé portemonnees - + + Font sizing + Lettertypegrootte + + + Version Versie - + Library Version Bibliotheek versie - + Synchronization Synchronisatie - + Network Status Netwerk Status + + + Address Stats + Adres statistieken + + + + Transaction + + + Miner Reward + Miner Beloning + + + + Fused + Gefuseerd + + + + Received + Ontvangen + + + + Moved + Verschoven + + + + Sent + Verzonden + + + + rejected + afgewezen + + + + TransactionDetails + + + Transaction Details + Transactiedetails + + + + First Seen + Eerst gezien + + + + Rejected + Geweigerd + + + + Unconfirmed + Onbevestigd + + + + Mined at + Gedolven in + + + + %1 blocks ago + + %1 blok terug + %1 blokken terug + + + + + Comment + Opmerking + + + + Fees paid + Betaalde kosten + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Size + Grootte + + + + %1 bytes + %1 bytes + + + + Is Coinbase + Is Coinbase + + + + Copy transaction-ID + Kopieer transactie-ID + + + + Fused from my addresses + Mijn gefuseerde adressen + + + + Sent from my addresses + Verzonden vanaf mijn adressen + + + + Sent from addresses + Verzonden vanaf adressen + + + + Fused into my addresses + Gefuseerd naar mijn adressen + + + + Received at addresses + Ontvangen op adressen + + + + Received at my addresses + Ontvangen op mijn adressen + + + + TransactionInfoSmall + + + Transaction is rejected + Transactie geweigerd + + + + Processing + In behandeling + + + + Mined + Gedolven + + + + %1 blocks ago + Confirmations + + %1 blok terug + %1 blokken terug + + + + + Fees + Kosten + + + + Copy transaction-ID + Kopieer transactie-ID + + + + Holds a token + Heeft een token + + + + Opening Website + Open Website + WalletEncryption @@ -946,16 +1155,16 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletEncryptionStatus - - - Pin to Pay - PIN bij betalen - Pin to Open PIN bij openen + + + Pin to Pay + PIN bij betalen + (Opened) @@ -963,206 +1172,98 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. (Geopend) - - WalletTransaction - - - Miner Reward - Miner Beloning - - - - Fused - Gefuseerd - - - - Received - Ontvangen - - - - Moved - Zelf-betaling - - - - Sent - Verzonden - - - - rejected - geweigerd - - - - unconfirmed - onbevestigd - - - - WalletTransactionDetails - - - Copy transaction-ID - Kopieer transactie-ID - - - - Status - Status - - - - rejected - geweigerd - - - - unconfirmed - onbevestigd - - - - %1 confirmations (mined in block %2) - - %1 bevestiging (gedolven in blok %2) - %1 bevestigingen (gedolven in blok %2) - - - - - Copy block height - Blokhoogte kopiëren - - - - Fees - Kosten - - - - Size - Grootte - - - - %1 bytes - - %1 byte - %1 bytes - - - - - Inputs - In - - - - - Copy Address - Kopieer adres - - - - Outputs - Uitgaand - - main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1171,9 +1272,14 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... + + + QR-Scan + QR-Scannen + diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index b6d110f..8d9c35b 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - © 2020-2023 Tom Zander and contributors + © 2020-2024 Tom Zander and contributors + © 2020-2024 Tom Zander and contributors - + Project Home Project Home - + With git repository and issues tracker With git repository and issues tracker - + Telegram Telegram @@ -63,42 +63,32 @@ Receive - + Miner Reward Miner Reward - + Fused Fused - + Received Received - + Moved Moved - + Sent Sent - - - Sending - Sending - - Seen - Seen - - - Rejected Rejected @@ -126,99 +116,100 @@ Backup Details - + Wallet seed-phrase Wallet seed-phrase - + Seed format Seed format - + + Starting Height height refers to block-height Starting Height - + Derivation Path Derivation Path - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + Wallet keys Wallet keys - + Show Index toggle to show numbers Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between still in use addresses and formerly used, new empty, addresses Switches between still in use addresses and formerly used, new empty, addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet @@ -241,7 +232,7 @@ Needs PIN to open - + Balance Total Balance Total @@ -342,22 +333,22 @@ Dark Theme - + Unit Unit - + Change Currency (%1) Change Currency (%1) - + Main View Main View - + Show Bitcoin Cash value Show Bitcoin Cash value @@ -370,93 +361,113 @@ Import Wallet - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - - - - Secret - The seed-phrase or private key - Secret - - - - Private key - description of type - Private key - - - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 seed-phrase (interpreted as Electrum) - - - - BIP 39 seed-phrase - description of type - BIP 39 seed-phrase - - - - Electrum seed-phrase - description of type - Electrum seed-phrase - - - - Unrecognized word - Word from the seed-phrases lexicon - Unrecognized word - - - + + Name Name - + Force Single Address Force Single Address - + + Select import method + Select import method + + + + Camera + Camera + + + + OR + OR + + + + Secret as text + The seed-phrase or private key + Secret as text + + + + Unknown word(s) found + Unknown word(s) found + + + + Next + Next + + + + Address to import + Address to import + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - Old Electrum Phrase - Old Electrum Phrase + + Nothing found for seed + Nothing found for seed - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - - - + + Oldest Transaction Oldest Transaction - + + Check Age + online check for wallet age + Check Age + + + + Nothing found for wallet + Nothing found for wallet + + + + + Start + Start + + + + Password + Password + + + + Optional + Optional + + + + Details + Details + + + + Lookup Details + online check for wallet details + Lookup Details + + + Derivation Derivation - - - Alternate phrase - Alternate phrase - - - - Create - Create - InstaPayConfigButton @@ -495,21 +506,21 @@ Change will come back to the imported key. - Requests for payment can be approved automatically using Instant Pay. - Requests for payment can be approved automatically using Instant Pay. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Protected wallets can not be used for InstaPay because they require a PIN before usage - + Enable Instant Pay for this wallet Enable Instant Pay for this wallet - + Maximum Amount Maximum Amount @@ -583,122 +594,112 @@ Change will come back to the imported key. New Bitcoin Cash Wallet - - Create a New Wallet - Create a New Wallet - - - - HD wallet - HD wallet - - - + Seed-phrase based Context: wallet type Seed-phrase based - + Easy to backup Context: wallet type Easy to backup - + Most compatible The most compatible wallet type Most compatible - - Basic - Basic - - - + Private keys based Property of a wallet Private keys based - + Difficult to backup Context: wallet type Difficult to backup - + Great for brief usage Context: wallet type Great for brief usage - + Import Existing Wallet Import Existing Wallet - - Import - Import + + New HD Wallet + New HD Wallet - + Imports seed-phrase Imports seed-phrase - + Imports private key Imports private key - + + New Basic Wallet + New Basic Wallet + + + New Wallet New Wallet - + This creates a new empty wallet with simple multi-address capability This creates a new empty wallet with simple multi-address capability - - + + Name Name - + Force Single Address Force Single Address - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up - - + + Create Create - + New HD-Wallet New HD-Wallet - + This creates a new wallet that can be backed up with a seed-phrase This creates a new wallet that can be backed up with a seed-phrase - + Derivation Derivation @@ -759,7 +760,7 @@ This ensures only one private key will need to be backed up Destination Address - + Unlock Wallet Unlock Wallet @@ -799,29 +800,6 @@ This ensures only one private key will need to be backed up All Currencies - - QRScannerOverlay - - - Paste - Paste - - - - Failed - Failed - - - - Instant Pay limit is %1 - Instant Pay limit is %1 - - - - Selected wallet: '%1' - Selected wallet: '%1' - - ReceiveTab @@ -999,168 +977,162 @@ This ensures only one private key will need to be backed up Transaction Details - + + Open in Explorer + Open in Explorer + + + Transaction Hash Transaction Hash - + + First Seen + First Seen + + + Rejected Rejected - + Unconfirmed Unconfirmed - + Mined at Mined at - + %1 blocks ago - %1 block ago + %1 blocks ago %1 blocks ago - + Transaction comment Transaction comment - + Size: %1 bytes Size: %1 bytes - + Coinbase Coinbase - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - - - Copy Address - Copy Address - - - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected Transaction is rejected - + Processing Processing - + Mined Mined - + %1 blocks ago Confirmations - %1 block ago + %1 blocks ago %1 blocks ago - + Miner Reward Miner Reward - + Fees Fees - + Received Received - + Payment to self Payment to self - + Sent Sent - + Holds a token Holds a token - + Sent to Sent to - - Value now - Value now - - - - Value then - Value then - - - + Transaction Details Transaction Details @@ -1173,7 +1145,7 @@ This ensures only one private key will need to be backed up Enter your wallet passcode - + Open open wallet with PIN Open diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index bdabd4e..749408f 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - © 2020-2023 Tom Zander en bijdragers + © 2020-2024 Tom Zander and contributors + © 2020-2024 Tom Zander en bijdragers - + Project Home Startpagina project - + With git repository and issues tracker Met git data en takenlijst - + Telegram Telegram @@ -63,42 +63,32 @@ Ontvang - + Miner Reward Mijnwerker Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Zelf-betaling - + Sent Verzonden - - - Sending - Verzenden - - Seen - Gezien - - - Rejected Geweigerd @@ -126,99 +116,100 @@ Back-up gegevens - + Wallet seed-phrase Herstelzin opslaan - + Seed format Herstelzin formaat - + + Starting Height height refers to block-height Beginhoogte - + Derivation Path Derivatie pad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between still in use addresses and formerly used, new empty, addresses Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren @@ -241,7 +232,7 @@ Benodigd pincode bij openen - + Balance Total Totale saldo @@ -342,22 +333,22 @@ Donker Thema - + Unit Eenheid - + Change Currency (%1) Verander valuta (%1) - + Main View Hoofd overzicht - + Show Bitcoin Cash value Toon Bitcoin Cash waarde @@ -370,93 +361,113 @@ Portemonnee importeren - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Voer de geheimen in van de te importeren portemonnee. Dit kan een herstelzin of een privésleutel zijn. - - - - Secret - The seed-phrase or private key - Geheim - - - - Private key - description of type - Privé-sleutel - - - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 herstelzin (geïnterpreteerd als Electrum formaat) - - - - BIP 39 seed-phrase - description of type - BIP 39 herstelzin - - - - Electrum seed-phrase - description of type - Electron-Cash herstelzin - - - - Unrecognized word - Word from the seed-phrases lexicon - Niet herkend woord - - - + + Name Naam - + Force Single Address Forceer één adres - + + Select import method + Kies import-methode + + + + Camera + Kamera + + + + OR + OF + + + + Secret as text + The seed-phrase or private key + Geheim als tekst + + + + Unknown word(s) found + Onbekende woord(en) gevonden + + + + Next + Volgende + + + + Address to import + Te importeren adres + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - Old Electrum Phrase - Oude Electrum herstelzin + + Nothing found for seed + Niets gevonden voor herstelzin - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Zet deze optie aan als Electrum detectie mislukt en u zeker weet dat je herstelzin in die portemonnee is gemaakt. - - - + + Oldest Transaction Oudste transactie - + + Check Age + online check for wallet age + Leeftijd opzoeken + + + + Nothing found for wallet + Niets gevonden voor portemonnee + + + + + Start + Start + + + + Password + Wachtwoord + + + + Optional + Optioneel + + + + Details + Details + + + + Lookup Details + online check for wallet details + Details opzoeken + + + Derivation Derivatie - - - Alternate phrase - Alternatieve zin - - - - Create - Creëer - InstaPayConfigButton @@ -495,21 +506,21 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. - Requests for payment can be approved automatically using Instant Pay. - Betalingsverzoeken worden automatisch goedgekeurd met behulp van Direct Betalen. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Bij lezen van QR-code met Direct Betalen aan wordt de betaling direct verstuurd, zonder bevestiging. Tenminste als deze onder de ingestelde limiet blijft. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Beveiligde portemonnees kunnen niet worden gebruikt voor Direct Betalen ze een PIN vereisen voor gebruik - + Enable Instant Pay for this wallet Actief maken voor portemonnee - + Maximum Amount Maximum Bedrag @@ -583,122 +594,112 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Nieuwe Bitcoin Cash Portemonnee - - Create a New Wallet - Maak een nieuwe portemonnee - - - - HD wallet - HD portemonnee - - - + Seed-phrase based Context: wallet type Herstelzin gebaseerd - + Easy to backup Context: wallet type Back-upt makkelijk - + Most compatible The most compatible wallet type Meest compatibel - - Basic - Eenvoudig - - - + Private keys based Property of a wallet Privé sleutels gebaseerd - + Difficult to backup Context: wallet type Back-up moeilijk - + Great for brief usage Context: wallet type Ideaal voor kort gebruik - + Import Existing Wallet Importeer bestaande portemonnee - - Import - Importeer + + New HD Wallet + Nieuwe HD portemonnee - + Imports seed-phrase Importeert herstelzin - + Imports private key Importeert Privésleutel - + + New Basic Wallet + Nieuwe Basic Portemonnee + + + New Wallet Nieuwe Portemonnee - + This creates a new empty wallet with simple multi-address capability Dit maakt een nieuwe lege portemonnee met eenvoudige multi-adres mogelijkheden - - + + Name Naam - + Force Single Address Forceer één adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - - + + Create Creëer - + New HD-Wallet Nieuwe Portemonnee - + This creates a new wallet that can be backed up with a seed-phrase Dit start een nieuwe portemonnee die gebackupt kan worden met een herstelzin - + Derivation Derivatie @@ -759,7 +760,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -799,29 +800,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Alle valuta's - - QRScannerOverlay - - - Paste - Plak - - - - Failed - Mislukt! - - - - Instant Pay limit is %1 - Directbetalen limiet is %1 - - - - Selected wallet: '%1' - Geselecteerde portemonnee: '%1' - - ReceiveTab @@ -999,27 +977,37 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Transactiedetails - + + Open in Explorer + Open in Verkenner + + + Transaction Hash Transactie hash - + + First Seen + Eerst gezien + + + Rejected Afgewezen - + Unconfirmed Onbevestigd - + Mined at Gedolven in - + %1 blocks ago Meest recente blok @@ -1027,86 +1015,80 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + Transaction comment Transactie omschrijving - + Size: %1 bytes Grootte: %1 bytes - + Coinbase Coinbase - + Fees paid Betaalde kosten - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Mijn gefuseerde adressen - + Sent from my addresses Verzonden vanaf mijn adressen - + Sent from addresses Verzonden vanaf adressen - - - Copy Address - Kopieer adres - - - + Fused into my addresses Gefuseerd naar mijn adressen - + Received at addresses Ontvangen op adressen - + Received at my addresses Ontvangen op mijn adressen - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected Transactie geweigerd - + Processing In behandeling - + Mined Gedolven - + %1 blocks ago Confirmations @@ -1115,52 +1097,42 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + Miner Reward - Mijnwerker Beloning + Miner Beloning - + Fees Kosten - + Received Ontvangen - + Payment to self Betaling aan uzelf - + Sent Verzonden - + Holds a token Heeft een token - + Sent to Verstuurd naar - - Value now - Waarde nu - - - - Value then - Waarde toen - - - + Transaction Details Transactiedetails @@ -1173,7 +1145,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Voer pincode voor portemonnee in - + Open open wallet with PIN Open diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index 08c04ef..4a27303 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -40,7 +40,7 @@ - + Add Destination Add Destination @@ -110,7 +110,7 @@ - + Copy Address Copy Address @@ -131,42 +131,37 @@ Bitcoin Cash Address - - Paste - Paste - - - + Warning Warning - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + I am certain I am certain - + Drag to Edit Drag to Edit - + Drag to Delete Drag to Delete - + Unlock Wallet Unlock Wallet - + Prepare Payment... Prepare Payment... diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index 7f7c73e..ac344e8 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -40,7 +40,7 @@ - + Add Destination Voeg bestemming toe @@ -110,7 +110,7 @@ - + Copy Address Kopieer adres @@ -131,42 +131,37 @@ Bitcoin Cash-adres - - Paste - Plak - - - + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + I am certain Ik ben zeker - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - + Unlock Wallet Portemonnee ontgrendelen - + Prepare Payment... Betaling voorbereiden... diff --git a/translations/module-peers-view_en.ts b/translations/module-peers-view_en.ts index 5028c86..79a1e01 100644 --- a/translations/module-peers-view_en.ts +++ b/translations/module-peers-view_en.ts @@ -4,45 +4,65 @@ NetView - + Peers Peers - + + Statistics + Statistics + + + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - initializing connection + + Opening Connection + Opening Connection - - Verifying peer - Verifying peer + + Validating peer + Validating peer - + + Validated + Validated + + + + Good Peer + Good Peer + + + Peer for wallet: %1 Peer for wallet: %1 - - Peer for wallet - Peer for wallet + + Disconnect Peer + Disconnect Peer + + + + Ban Peer + Ban Peer @@ -63,4 +83,52 @@ Network Details + + StatsPage + + + IP-Address Statistics + IP-Address Statistics + + + + Counts + Counts + + + + Total found + Total found + + + + Tried + Tried + + + + Misbehaving IPs + Misbehaving IPs + + + + Bad + Bad + + + + Banned + Banned + + + + Network + Network + + + + Pardon the Banned + Pardon the Banned + + -- 2.54.0 From 736ba60732cf484d67aa66d0db942b5eb8630cee Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 May 2024 22:30:45 +0200 Subject: [PATCH 0882/1428] add user-friendly comment --- CMakeLists.txt | 3 ++- src/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a95a11e..9c4d1e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,7 +355,8 @@ if (build_desktop_pay) endif () endif() if (NOT ${Qt6Multimedia_FOUND}) - message("ww Missing QtMultimedia libs, not building Pay for mobile ") + message("ww Missing QtMultimedia libs, not building Pay for mobile") + message(" not including camera options in desktop Pay") endif () if (${build_mobile_pay}) message ("-> Building Pay for mobile") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c697177..3ef074f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,7 +70,7 @@ else () list(APPEND PAY_SOURCES main_utils.cpp) endif () -if ("${Qt6Multimedia_FOUND}") +if (${Qt6Multimedia_FOUND}) list(APPEND PAY_SOURCES CameraController.cpp) list(APPEND PayLib_PRIVATE_LIBS Qt6::Multimedia) endif () -- 2.54.0 From 164256f696c33c0e077b36bb11043add31a0461a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 22:38:53 +0200 Subject: [PATCH 0883/1428] Fix misuse of signal Avoid warnings on changing theme. --- guis/desktop/main.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 710b526..d18b6f0 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -47,16 +47,16 @@ ApplicationWindow { target: Pay function onFontScalingChanged() { updateFontSize(mainWindow); - if (txDetailsWindow.loaded()) + if (txDetailsWindow.status === Loader.Ready) updateFontSize(txDetailsWindow.item); - if (netView.loaded()) + if (netView.status === Loader.Ready) updateFontSize(netView.item); } function onUseDarkSkinChanged() { ControlColors.applySkin(mainWindow); - if (txDetailsWindow.loaded()) + if (txDetailsWindow.status === Loader.Ready) ControlColors.applySkin(txDetailsWindow.item); - if (netView.loaded()) + if (netView.status === Loader.Ready) ControlColors.applySkin(netView.item); } } -- 2.54.0 From 3bcbf58b1dd61b48dd35184f1f651a18bef8d939 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 23:23:31 +0200 Subject: [PATCH 0884/1428] Fix color theme settings on Wayland Seems that the problem we saw on Android is also present on Linux and Wayland. A lot of components no longer use the palette which makes it not possible to use them as-is with our light/dark theme feature. This changes the many components to the Flowee specific ones where we already solved this for the Android UI. --- guis/Flowee/DialogButtonBox.qml | 9 +++- guis/desktop/AccountConfigMenu.qml | 14 +++--- guis/desktop/AccountDetails.qml | 32 ++++++------- guis/desktop/AccountListItem.qml | 12 ++--- guis/desktop/AddressDbStats.qml | 28 ++++++----- guis/desktop/ConfigItem.qml | 4 +- guis/desktop/NetView.qml | 23 +++++---- guis/desktop/NewAccountPane.qml | 2 +- guis/desktop/PaymentTweakingPanel.qml | 8 ++-- guis/desktop/ReceiveTransactionPane.qml | 30 +++++++----- guis/desktop/SendTransactionPane.qml | 64 ++++++++++++------------- guis/desktop/SettingsPane.qml | 32 ++++++------- guis/desktop/Transaction.qml | 8 ++-- guis/desktop/WalletEncryption.qml | 20 ++++---- guis/desktop/WalletEncryptionStatus.qml | 5 +- 15 files changed, 156 insertions(+), 135 deletions(-) diff --git a/guis/Flowee/DialogButtonBox.qml b/guis/Flowee/DialogButtonBox.qml index 1ab0865..95c84a4 100644 --- a/guis/Flowee/DialogButtonBox.qml +++ b/guis/Flowee/DialogButtonBox.qml @@ -29,7 +29,7 @@ import QtQuick.Controls as QQC2 */ QQC2.DialogButtonBox { id: root - spacing: 0 + spacing: 10 padding: 0 contentItem: ListView { @@ -40,6 +40,13 @@ QQC2.DialogButtonBox { snapMode: ListView.SnapToItem } + // fix the background color + background: Rectangle { + implicitWidth: 100 + implicitHeight: 40 + color: palette.window + } + function setEnabled(buttonType, on) { let but = standardButton(buttonType) if (but !== null) diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index 377fa47..b242722 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -16,20 +16,20 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 ConfigItem { id: root property QtObject account: null - property QtObject detailsAction: Action { + property QtObject detailsAction: QQC2.Action { text: qsTr("Details") onTriggered: { portfolio.current = root.account; accountOverlay.state = "accountDetails"; } } - property QtObject archiveAction: Action { + property QtObject archiveAction: QQC2.Action { text: root.account != null && root.account.isArchived ? qsTr("Unarchive") : qsTr("Archive Wallet") onTriggered: { /* @@ -42,24 +42,24 @@ ConfigItem { root.account.isArchived = !root.account.isArchived } } - property QtObject primaryAction: Action { + property QtObject primaryAction: QQC2.Action { enabled: root.account != null && !root.account.isPrimaryAccount text: enabled ? qsTr("Make Primary") : qsTr("★ Primary") onTriggered: root.account.isPrimaryAccount = !root.account.isPrimaryAccount } - property QtObject encryptAction: Action { + property QtObject encryptAction: QQC2.Action { text: qsTr("Protect With Pin...") onTriggered: { portfolio.current = root.account; accountOverlay.state = "startWalletEncryption"; } } - property QtObject openWalletAction: Action { + property QtObject openWalletAction: QQC2.Action { text: qsTr("Open", "Open encrypted wallet") onTriggered: tabbar.currentIndex = 0 } - property QtObject closeWalletAction: Action { + property QtObject closeWalletAction: QQC2.Action { text: qsTr("Close", "Close encrypted wallet") onTriggered: root.account.closeWallet(); } diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 80a289d..31efe0c 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee @@ -45,7 +45,7 @@ Item { } } - Label { + Flowee.Label { id: walletDetailsLabel text: qsTr("Wallet Details") font.pointSize: 18 @@ -70,7 +70,7 @@ Item { columns: 2 rowSpacing: 10 - Label { + Flowee.Label { text: qsTr("Name") + ":" } Flowee.TextField { @@ -100,7 +100,7 @@ Item { x: 10 columns: 2 - Label { + Flowee.Label { id: syncLabel Layout.columnSpan: 2 property string time: "" @@ -146,7 +146,7 @@ Item { Layout.columnSpan: 2 account: root.account } - Label { + Flowee.Label { id: xpubLabel // at the moment I don't see a point if making this translatable. Let me know if that should change! text: "xpub" + ":" @@ -158,7 +158,7 @@ Item { text: root.account.xpub } - Label { + Flowee.Label { id: encLabel text: qsTr("Encryption") + ":" visible: encStatus.visible @@ -169,7 +169,7 @@ Item { account: root.account } - Label { + Flowee.Label { id: pwdLabel text: qsTr("Password") + ":" visible: encStatus.visible @@ -266,7 +266,7 @@ Item { id: historyView Layout.fillWidth: true clip: true - ScrollBar.vertical: Flowee.ScrollThumb { + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 20 / activityView.height visible: size < 0.9 @@ -296,10 +296,10 @@ Item { visible: root.account.isDecrypted width: parent.width columns: 2 - Label { + Flowee.Label { text: qsTr("Seed-phrase") + ":" } - TextArea { + QQC2.TextArea { readOnly: true text: root.account.mnemonic Layout.fillWidth: true @@ -308,24 +308,24 @@ Item { wrapMode: TextEdit.WordWrap padding: 0 } - Label { + Flowee.Label { text: qsTr("Seed format") + ":" visible: root.account.isElectrumMnemonic } - Label { + Flowee.Label { id: seedPhraseFormat font.bold: true text: "Electrum" visible: root.account.isElectrumMnemonic } - Label { + Flowee.Label { text: qsTr("Derivation") + ":" } Flowee.LabelWithClipboard { text: root.account.hdDerivationPath } } - Label { + Flowee.Label { id: helpText visible: grid.visible width: parent.width @@ -335,7 +335,7 @@ Item { textFormat: Text.StyledText wrapMode: Text.WrapAtWordBoundaryOrAnywhere } - Label { + Flowee.Label { id: warningText visible: grid.visible width: parent.width @@ -345,7 +345,7 @@ Item { textFormat: Text.StyledText wrapMode: Text.WrapAtWordBoundaryOrAnywhere } - Label { + Flowee.Label { id: infoText visible: !root.account.isDecrypted width: parent.width diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 3190062..66c0c99 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee @@ -53,11 +53,11 @@ Item { y: 6 width: parent.width // - 13 /* - Label { + Flowee.Label { text: "Savings Account" opacity: 0.6 }*/ - Label { + Flowee.Label { text: root.account.name font.bold: true width: parent.width - 20 @@ -72,15 +72,15 @@ Item { width: parent.width spacing: 10 visible: lastReceive.text !== "" && !root.account.isArchived - Label { + Flowee.Label { text: qsTr("Last Transaction") + ":" } - Label { + Flowee.Label { id: lastReceive text: Pay.formatDate(account.lastMinedTransaction) } } - Label { + Flowee.Label { visible: root.account.isArchived text: visible ? account.timeBehind : "" } diff --git a/guis/desktop/AddressDbStats.qml b/guis/desktop/AddressDbStats.qml index 989b46a..df28848 100644 --- a/guis/desktop/AddressDbStats.qml +++ b/guis/desktop/AddressDbStats.qml @@ -33,6 +33,10 @@ QQC2.ApplicationWindow { property QtObject stats: net.createStats(root); + background: Rectangle { + color: palette.window + } + GridLayout { columns: 2 rowSpacing: 10 @@ -41,49 +45,49 @@ QQC2.ApplicationWindow { x: 10 y: 10 - QQC2.Label { + Flowee.Label { text: qsTr("Total found") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.count; Layout.fillWidth: true } - QQC2.Label { + Flowee.Label { text: qsTr("Tried") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.tried; } - QQC2.Label { + Flowee.Label { text: qsTr("Punished count") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.partialBanned; } - QQC2.Label { + Flowee.Label { text: qsTr("Banned count") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.banned; } - QQC2.Label { + Flowee.Label { text: qsTr("IP-v4 count") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.count - root.stats.ipv6Addresses font.strikeout: root.stats.usesIPv4 === false } - QQC2.Label { + Flowee.Label { text: qsTr("IP-v6 count") + ":" Layout.alignment: Qt.AlignRight } - QQC2.Label { + Flowee.Label { text: root.stats.ipv6Addresses font.strikeout: root.stats.usesIPv6 === false } diff --git a/guis/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml index 4a5449c..b7b12b8 100644 --- a/guis/desktop/ConfigItem.qml +++ b/guis/desktop/ConfigItem.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee Item { @@ -51,7 +51,7 @@ Item { acceptedButtons: Qt.RightButton | Qt.LeftButton cursorShape: Qt.PointingHandCursor - Menu { + QQC2.Menu { id: ourMenu } onClicked: { diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index 1218fe8..6179574 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2; +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; @@ -32,12 +32,15 @@ QQC2.ApplicationWindow { modality: Qt.NonModal flags: Qt.Dialog + background: Rectangle { + color: palette.window + } ListView { id: listView model: net clip: true - QQC2.ScrollBar.vertical: QQC2.ScrollBar { } + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { } anchors.fill: parent focus: true @@ -60,7 +63,7 @@ QQC2.ApplicationWindow { return 0.7; return 1; } - QQC2.Label { + Flowee.Label { text: "(" + model.connectionId + ")" anchors.right: parent.right anchors.rightMargin: 10 @@ -73,24 +76,24 @@ QQC2.ApplicationWindow { x: 10 y: 6 - QQC2.Label { + Flowee.Label { text: model.userAgent } - QQC2.Label { + Flowee.Label { text: qsTr("Address", "network address (IP)") + ": " + model.address } RowLayout { height: secondRow.height spacing: 0 - QQC2.Label { + Flowee.Label { id: secondRow text: qsTr("Start-height: %1").arg(model.startHeight) } - QQC2.Label { + Flowee.Label { text: ", " + qsTr("ban-score: %1").arg(model.banScore) } } - QQC2.Label { + Flowee.Label { id : accountStatus font.bold: true text: { @@ -115,7 +118,7 @@ QQC2.ApplicationWindow { return "Internal Error"; } } - QQC2.Label { + Flowee.Label { text: "Downloading!" visible: model.isDownloading } @@ -151,7 +154,7 @@ QQC2.ApplicationWindow { width: parent.width height: closeButton.height + 20 - QQC2.Button { + Flowee.Button { id: closeButton anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index cf5a6c2..d30f482 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -15,7 +15,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml index 682dcf5..faaa86e 100644 --- a/guis/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee Item { @@ -74,7 +74,7 @@ Item { width: parent.width height: randomRect.height + title.height + 30 - Label { + Flowee.Label { id: title text: qsTr("Add Detail") font.pointSize: 20 @@ -103,14 +103,14 @@ Item { } } } - Label { + Flowee.Label { id: helpText anchors.top: randomRect.top anchors.topMargin: 20 anchors.left: randomRect.right anchors.leftMargin: 10 anchors.right: parent.right - wrapMode: Label.Wrap + wrapMode: Flowee.Label.Wrap text: qsTr("To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible.") } diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index 3295735..f83a4ae 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -16,13 +16,13 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import QtQuick.Shapes import Flowee.org.pay import "../Flowee" as Flowee -Pane { +Item { id: receivePane property QtObject account: portfolio.current @@ -44,7 +44,7 @@ Pane { request.start(); } - Label { + Flowee.Label { id: instructions anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 20 @@ -84,7 +84,9 @@ Pane { anchors.top: parent.top anchors.topMargin: 20 anchors.left: parent.left + anchors.leftMargin: 10 anchors.right: parent.right + anchors.rightMargin: 10 anchors.bottom: qr.bottom radius: 10 gradient: Gradient { @@ -139,14 +141,14 @@ Pane { } } - Label { + Flowee.Label { anchors.centerIn: parent text: qsTr("Checking") // checking security } Behavior on opacity { OpacityAnimator {} } } - Label { + Flowee.Label { color: "green" text: "✔" opacity: 1 - circleShape.opacity @@ -156,7 +158,7 @@ Pane { } } - Label { + Flowee.Label { id: feedbackLabel text: { var s = request.state; @@ -178,7 +180,7 @@ Pane { wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 20 } - Label { + Flowee.Label { visible: request.state === PaymentRequest.DoubleSpentSeen anchors.top: feedbackLabel.bottom anchors.right: parent.right @@ -199,8 +201,11 @@ Pane { anchors.right: parent.right anchors.top: qr.bottom anchors.topMargin: 30 + anchors.leftMargin: 10 + anchors.rightMargin: 10 + columns: 2 - Label { + Flowee.Label { text: qsTr("Description") + ":" } Flowee.TextField { @@ -211,7 +216,7 @@ Pane { focus: true } - Label { + Flowee.Label { id: payAmount text: qsTr("Amount") + ":" } @@ -222,7 +227,7 @@ Pane { enabled: request.state === PaymentRequest.Unpaid onValueChanged: request.amount = value } - Label { + Flowee.Label { Layout.alignment: Qt.AlignBaseline anchors.baselineOffset: bitcoinValueField.baselineOffset text: Fiat.formattedPrice(bitcoinValueField.value, Fiat.price) @@ -232,10 +237,11 @@ Pane { RowLayout { Layout.columnSpan: 2 Layout.fillWidth: true - Pane { + Item { Layout.fillWidth: true + width: 1; height: 1 } - Button { + Flowee.Button { text: request.state === PaymentRequest.Unpaid ? qsTr("Clear") : qsTr("Done") onClicked: { request.clear(); diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 70a9e00..3cb02a4 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import QtQuick.Window import "../Flowee" as Flowee @@ -162,7 +162,7 @@ Item { } Flowee.Dialog { title: qsTr("Warning") - standardButtons: DialogButtonBox.Ok + standardButtons: QQC2.DialogButtonBox.Ok text: { var warnings = payment.warnings if (warnings.length === 0) @@ -193,7 +193,7 @@ Item { columns: 2 property bool txOk: payment.txPrepared - Label { + Flowee.Label { // no need translating this one. text: "TxId:" Layout.alignment: Qt.AlignRight | Qt.AlignTop @@ -208,7 +208,7 @@ Item { : Qt.darker(palette.windowText, (Pay.useDarkSkin ? 1.6 : 0.4)) menuText: qsTr("Copy transaction-ID") } - Label { + Flowee.Label { text: qsTr("Fee") + ":" Layout.alignment: Qt.AlignRight } @@ -218,11 +218,11 @@ Item { colorize: false color: txid.color } - Label { + Flowee.Label { text: qsTr("Transaction size") + ":" Layout.alignment: Qt.AlignRight } - Label { + Flowee.Label { text: { if (!parent.txOk) return ""; @@ -231,11 +231,11 @@ Item { } color: txid.color } - Label { + Flowee.Label { text: qsTr("Fee per byte") + ":" Layout.alignment: Qt.AlignRight } - Label { + Flowee.Label { text: { if (!parent.txOk) return ""; @@ -253,9 +253,9 @@ Item { Flowee.DialogButtonBox { id: box width: parent.width - standardButtons: DialogButtonBox.Cancel | DialogButtonBox.Ok + standardButtons: QQC2.DialogButtonBox.Cancel | QQC2.DialogButtonBox.Ok onStandardButtonsChanged: { - var okButton = standardButton(DialogButtonBox.Ok) + var okButton = standardButton(QQC2.DialogButtonBox.Ok) if (okButton !== null) { okButton.text = qsTr("Send") okButton.enabled = canSend @@ -263,7 +263,7 @@ Item { } property bool canSend: payment.isValid && payment.txPrepared && prepareButton.portfolioUsed === portfolio.current; // also make sure we prepared for the current portfolio. - onCanSendChanged: setEnabled(DialogButtonBox.Ok, canSend) + onCanSendChanged: setEnabled(QQC2.DialogButtonBox.Ok, canSend) onRejected: payment.reset(); onAccepted: payment.markUserApproved(); } @@ -344,7 +344,7 @@ Item { } enabled: paymentDetail.editable } - Label { + Flowee.Label { color: "green" font.pixelSize: 24 text: addressInfo.addressOk ? "✔" : " " @@ -364,7 +364,7 @@ Item { addressType: destination.addressType } - Label { + Flowee.Label { id: payAmount text: qsTr("Amount") + ":" } @@ -428,12 +428,12 @@ Item { y: destination.height + 10 width: parent.width spacing: 10 - Label { + Flowee.Label { font.bold: true font.pixelSize: warning.font.pixelSize * 1.2 text: qsTr("Warning") } - Label { + Flowee.Label { id: warning width: parent.width text: qsTr("This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address?") @@ -447,11 +447,11 @@ Item { Layout.fillWidth: true } - Button { + Flowee.Button { text: qsTr("Continue") onClicked: paymentDetail.forceLegacyOk = true } - Button { + Flowee.Button { text: qsTr("Cancel") onClicked: destination.text = "" } @@ -484,14 +484,14 @@ Item { columns: 4 - Label { + Flowee.Label { text: qsTr("Total", "Number of coins") + ":" } - Label { + Flowee.Label { text: coinsListView.count Layout.fillWidth: true } - Label { + Flowee.Label { text: qsTr("Needed") +":" } Flowee.BitcoinAmountLabel { @@ -501,14 +501,14 @@ Item { colorize: false } // next row - Label { + Flowee.Label { text: qsTr("Selected") + ":" } - Label { + Flowee.Label { text: inputsPane.paymentDetail.selectedCount Layout.fillWidth: true } - Label { + Flowee.Label { text: qsTr("Value") + ":" } Flowee.BitcoinAmountLabel { @@ -545,20 +545,20 @@ Item { anchors.fill: parent visible: locked // if the UTXO is user-locked - ToolTip { + QQC2.ToolTip { delay: 600 text: qsTr("Locked coins will never be used for payments. Right-click for menu.") visible: locked && rowMouseArea.containsMouse } } - CheckBox { + Flowee.CheckBox { y: 6 id: selectedBox checked: model.selected visible: !lockedRect.visible } - Label { + Flowee.Label { id: mainText y: 6 anchors.right: amountLabel.left @@ -571,7 +571,7 @@ Item { return address; } - elide: Label.ElideRight + elide: Flowee.Label.ElideRight } Flowee.BitcoinAmountLabel { id: amountLabel @@ -581,7 +581,7 @@ Item { // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 } - Label { + Flowee.Label { id: ageLabel text: qsTr("Age") + ": " + age anchors.left: mainText.left @@ -618,9 +618,9 @@ Item { Item { id: mousePos width: 1; height: 1 - Menu { + QQC2.Menu { id: lockingMenu - MenuItem { + QQC2.MenuItem { text: selectedBox.checked ? qsTr("Unselect All") : qsTr("Select All") onClicked: { coinsListView.menuIsOpen = false @@ -630,14 +630,14 @@ Item { inputsPane.paymentDetail.selectAll(); } } - MenuItem { + QQC2.MenuItem { text: locked ? qsTr("Unlock coin") : qsTr("Lock coin") onClicked: { inputsPane.paymentDetail.setOutputLocked(index, !locked) coinsListView.menuIsOpen = false } } - MenuItem { + QQC2.MenuItem { text: qsTr("Copy Address") onClicked: Pay.copyToClipboard(Pay.chainPrefix + address); } diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index ce15641..ebe04cc 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -16,13 +16,13 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors import "../Flowee" as Flowee -Pane { +Item { property string title: qsTr("Settings") property string icon: "qrc:/settingsIcon-light.png" @@ -32,7 +32,7 @@ Pane { columnSpacing: 6 width: parent.width - Label { + Flowee.Label { text: qsTr("Unit") + ":" Layout.alignment: Qt.AlignRight } @@ -64,7 +64,7 @@ Pane { columns: 3 x: 5; y: 5 rowSpacing: 0 - Label { + Flowee.Label { text: { var answer = "1"; for (let i = Pay.unitAllowedDecimals; i < 8; ++i) { @@ -74,12 +74,12 @@ Pane { } Layout.alignment: Qt.AlignRight } - Label { text: "=" } - Label { text: "1 Bitcoin Cash" } + Flowee.Label { text: "=" } + Flowee.Label { text: "1 Bitcoin Cash" } - Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} - Label { text: "="; visible: Pay.isMainChain} - Label { + Flowee.Label { text: "1 " + Pay.unitName; Layout.alignment: Qt.AlignRight; visible: Pay.isMainChain} + Flowee.Label { text: "="; visible: Pay.isMainChain} + Flowee.Label { text: { var amount = 1; for (let i = 0; i < Pay.unitAllowedDecimals; ++i) { @@ -189,28 +189,28 @@ Pane { } } - Label { + Flowee.Label { text: qsTr("Version") + ":" Layout.alignment: Qt.AlignRight } - Label { + Flowee.Label { text: Pay.version Layout.columnSpan: 2 } - Label { + Flowee.Label { text: qsTr("Library Version") + ":" Layout.alignment: Qt.AlignRight } - Label { + Flowee.Label { text: Pay.libsVersion Layout.columnSpan: 2 } - Label { + Flowee.Label { Layout.alignment: Qt.AlignRight text: qsTr("Synchronization") + ":" } - Button { + Flowee.Button { Layout.columnSpan: 2 text: qsTr("Network Status") onClicked: { @@ -220,7 +220,7 @@ Pane { } Item { width: 1; height: 1 } // empty row - Button { + Flowee.Button { Layout.columnSpan: 2 text: qsTr("Address Stats") onClicked: { diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index e28ca62..653371f 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee Rectangle { @@ -55,7 +55,7 @@ Rectangle { radius: width color: Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue } - Label { + Flowee.Label { id: mainLabel anchors.left: isNewIndicator.right anchors.leftMargin: model.isNew ? 3 : 0 @@ -72,7 +72,7 @@ Rectangle { return qsTr("Sent") } } - Label { + Flowee.Label { id: date anchors.top: mainLabel.bottom function updateText() { @@ -103,7 +103,7 @@ Rectangle { height: 16 } - Label { + Flowee.Label { id: userComment y: (date.y + date.height - height) / 2 diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index 9999aff..f0faf42 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors @@ -35,7 +35,7 @@ FocusScope { contentHeight: contentAreaColumn.height + 20 flickableDirection: Flickable.VerticalFlick clip: true - ScrollBar.vertical: ScrollBar { } + QQC2.ScrollBar.vertical: QQC2.ScrollBar { } function encryptAndExit() { var account = root.account; @@ -59,7 +59,7 @@ FocusScope { y: 10 x: 20 spacing: 20 - Label { + Flowee.Label { text: qsTr("Protect your wallet with a password") anchors.horizontalCenter: parent.horizontalCenter font.pointSize: 14 @@ -110,7 +110,7 @@ FocusScope { ] } } - Label { + Flowee.Label { id: description anchors.left: optionsRow.left text: { @@ -126,7 +126,7 @@ FocusScope { currentIndex: optionsRow.selectedKey width: parent.width - Label { + Flowee.Label { text: { if (root.account.needsPinToOpen) return qsTr("Wallet already has pin to open applied") @@ -138,7 +138,7 @@ FocusScope { width: stack.width } Column { - Label { + Flowee.Label { text: { if (root.account.needsPinToOpen) return qsTr("Wallet already has pin to open applied") @@ -147,7 +147,7 @@ FocusScope { wrapMode: Text.WordWrap width: stack.width } - Label { + Flowee.Label { visible: root.account.needsPinToPay && !root.account.needPinToOpen text: "This wallet already has pin to pay applied, you may upgrade it to pin to open but it will remove pin to pay. The password you provide must be the same as the one for pin to pay"; wrapMode: Text.WordWrap @@ -167,7 +167,7 @@ FocusScope { width: parent.width columns: 2 - Label { + Flowee.Label { text: qsTr("Password") + ":" onEnabledChanged: updateColors(); @@ -189,11 +189,11 @@ FocusScope { echoMode: TextInput.Password onAccepted: encryptButton.clicked() } - Label { + Flowee.Label { visible: !portfolio.singleAccountSetup text: qsTr("Wallet") + ":" } - Label { + Flowee.Label { visible: !portfolio.singleAccountSetup text: root.account.name } diff --git a/guis/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml index ff72697..60b1158 100644 --- a/guis/desktop/WalletEncryptionStatus.qml +++ b/guis/desktop/WalletEncryptionStatus.qml @@ -16,7 +16,8 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee Item { id: root @@ -33,7 +34,7 @@ Item { opacity: root.account.isDecrypted ? 0.65 : 1 y: -3 } - Label { + Flowee.Label { id: encryptionStatusLabel wrapMode: Text.WordWrap font.italic: true -- 2.54.0 From 3969cdcdcbb010c48eb44a80225b86b712e4e156 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 23:29:51 +0200 Subject: [PATCH 0885/1428] Make the selection work again. This fixes the regression on usage of the CardTypeSelector which changed last release. --- guis/desktop/WalletEncryption.qml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index f0faf42..0ff6d9f 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -56,20 +56,20 @@ FocusScope { Column { id: contentAreaColumn width: contentArea.width - 40 - y: 10 + y: 25 x: 20 spacing: 20 Flowee.Label { text: qsTr("Protect your wallet with a password") anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 14 + font.pixelSize: description.font.pixelSize * 1.3 } Flow { id: optionsRow anchors.horizontalCenter: parent.horizontalCenter activeFocusOnTab: true focus: true - height: 320 + height: 240 spacing: 20 width: Math.min(contentArea.width - 30, 900) // smaller is OK, wider not @@ -85,10 +85,9 @@ FocusScope { Flowee.CardTypeSelector { id: pinToPay - key: 0 title: qsTr("Pin to Pay") width: parent.selectorWidth - onClicked: parent.cardClicked(key); + onClicked: parent.cardClicked(0); features: [ qsTr("Protect your funds", "pin to pay"), @@ -98,10 +97,9 @@ FocusScope { } Flowee.CardTypeSelector { id: pinToOpen - key: 1 title: qsTr("Pin to Open") width: parent.selectorWidth - onClicked: parent.cardClicked(key); + onClicked: parent.cardClicked(1); features: [ qsTr("Protect your entire wallet", "pin to open"), -- 2.54.0 From fcabe49d9b105f9eeddce63b30a9a9356af23880 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 23:39:08 +0200 Subject: [PATCH 0886/1428] Fix colors and font-size --- guis/desktop/TransactionDetails.qml | 2 ++ guis/desktop/main.qml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index be78818..19e0f33 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -40,6 +40,8 @@ QQC2.ApplicationWindow { } property var transactions: [] + background: Rectangle { color: palette.window } + Rectangle { width: parent.width height: tabbar.headerHeight diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index d18b6f0..b2dadf1 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -204,6 +204,7 @@ ApplicationWindow { anchors.fill: parent Pane { + id: activityTab property string title: qsTr("Activity") property string icon: "qrc:/activityIcon-light.png" anchors.fill: parent @@ -267,6 +268,7 @@ ApplicationWindow { horizontalAlignment: Text.AlignHCenter color: "black" + font.pixelSize: activityTab.font.pixelSize font.bold: true wrapMode: Text.WordWrap text: qsTr("This wallet needs a password to open.") @@ -276,6 +278,7 @@ ApplicationWindow { anchors.left: decryptText.left anchors.verticalCenter: decryptPwd.verticalCenter color: decryptText.color + font: activityTab.font text: qsTr("Password:") } @@ -294,6 +297,7 @@ ApplicationWindow { anchors.left: decryptPwd.left anchors.verticalCenter: decryptButton.verticalCenter color: "#830000" + font.pixelSize: activityTab.font.pixelSize font.bold: true text: qsTr("Invalid password") visible: false -- 2.54.0 From 4f3d38b5de832a2829d222f218ccc2a2108140d3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 23:44:57 +0200 Subject: [PATCH 0887/1428] Make (desktop) app-icon work again Qt uses the desktop file directly on Wayland and the windowIcon concept is obsolete. This makes the app-icon show properly on Wayland. --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index 4cc8145..27afe76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,6 +90,7 @@ int main(int argc, char *argv[]) qapp.setApplicationName("pay"); qapp.setApplicationVersion("2024.05.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); + qapp.setDesktopFileName("org.flowee.pay"); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); -- 2.54.0 From bd3184f2d83edd8ef1af8d8d50994522277c6df5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 May 2024 23:50:01 +0200 Subject: [PATCH 0888/1428] Port to Wayland: protect against null On Wayland the mime can be a nullptr due to the different clipboard strategy, we now check for null dereference. --- src/BitcoinValue.cpp | 4 +++- src/QMLClipboardHelper.cpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BitcoinValue.cpp b/src/BitcoinValue.cpp index 1d9461e..a31d5fd 100644 --- a/src/BitcoinValue.cpp +++ b/src/BitcoinValue.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -121,6 +121,8 @@ bool BitcoinValue::addSeparator() void BitcoinValue::paste() { auto *mimeData = QGuiApplication::clipboard()->mimeData(); + if (mimeData == nullptr) + return; QString newValue; if (mimeData->hasText()) newValue = mimeData->text(); diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index e0b0318..c537e65 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -45,6 +45,8 @@ void QMLClipboardHelper::parseClipboard() if (!m_enabled) return; auto *mimeData = QGuiApplication::clipboard()->mimeData(); + if (mimeData == nullptr) // on Wayland this is possible + return; QString text; if (mimeData->hasText()) text = mimeData->text(); -- 2.54.0 From 6365d03233b0ccdab43858d1ebfab90a6d2285b1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 31 May 2024 14:22:25 +0200 Subject: [PATCH 0889/1428] Add docs. --- src/ModuleInfo.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index db282a3..922b52a 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,8 +41,12 @@ class ModuleInfo : public QObject public: explicit ModuleInfo(QObject *parent = nullptr); - QString id() const; + /** + * The module's unique ID. This is used in save-files + * and similar, and never shown to a user. + */ void setId(const QString &newId); + QString id() const; /** * >The title as shown in the module selector UI. -- 2.54.0 From c857259cb1467eac0acbd1c8791caac8e303ea21 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Jun 2024 13:45:17 +0200 Subject: [PATCH 0890/1428] Import Spanish from crowdin --- translations/floweepay-common_es.ts | 152 ++-- translations/floweepay-desktop_es.ts | 754 +++++++++++--------- translations/floweepay-mobile_es.ts | 424 +++++------ translations/module-build-transaction_es.ts | 23 +- translations/module-peers-view_es.ts | 2 +- 5 files changed, 741 insertions(+), 614 deletions(-) diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 9b335b7..82b27b1 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Sin conexión - + Wallet: Up to date Monedero: Actualizado - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Retraso: %1 día @@ -31,17 +31,17 @@ - + Up to date Actualizado - + Updating Actualizando - + Still %1 hours behind Todavía %1 hora de retraso @@ -101,12 +101,12 @@ Payment has been sent to: - Payment has been sent to: + El pago ha sido enviado a: Copied address to clipboard - Copied address to clipboard + Dirección copiada al portapapeles @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity La moneda se ha fusionado para aumentar el anonimato + + FiatTxInfo + + + Value now + Valor ahora + + + + Value then + Valor entonces + + FloweePay - + Initial Wallet Monedero inicial - - + + Today Hoy - - + + Yesterday Ayer - + Now timestamp Ahora - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp Hace ½ hora - + %1 hours ago timestamp @@ -239,9 +252,9 @@ New Transactions dialog-title - + + Nueva transacción Nuevas transacciones - New Transactions @@ -286,14 +299,14 @@ Transacción demasiado larga. La cantidad seleccionada necesita demasiadas monedas. - + Request received over insecure channel. Anyone could have altered it! - Request received over insecure channel. Anyone could have altered it! + Solicitud recibida por un canal inseguro. ¡Cualquiera podría haberla alterado! - + Download of payment request failed. - Download of payment request failed. + Descarga de solicitud de pago fallida. @@ -301,13 +314,36 @@ Payment request unreadable - Payment request unreadable + Solicitud de pago ilegible Payment request expired - Payment request expired + Solicitud de pago expirada + + + + QRScanner + + + Paste + Pegar + + + + Failed + Fallido + + + + Instant Pay limit is %1 + El límite de pago instantáneo es %1 + + + + Selected wallet: '%1' + Monedero seleccionado: '%1' @@ -318,17 +354,25 @@ Copiado al portapapeles + + TextPasteDecorator + + + Paste + Pegar + + Wallet - - + + Change #%1 Cambio #%1 - - + + Main #%1 Principal #%1 @@ -336,12 +380,12 @@ WalletCoinsModel - + Unconfirmed Sin confirmar - + %1 hours age, like: hours old @@ -350,7 +394,7 @@ - + %1 days age, like: days old @@ -359,7 +403,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +412,7 @@ - + %1 months age, like: months old @@ -377,7 +421,7 @@ - + Change #%1 Cambio #%1 @@ -385,27 +429,27 @@ WalletHistoryModel - + Today Hoy - + Yesterday Ayer - + Earlier this week Anteriormente en esta semana - + This week Esta semana - + Earlier this month Anteriormente este mes @@ -413,35 +457,45 @@ WalletSecretsView - + Explanation Explicación - + Coins a / b a) active coin-count. b) historical coin-count. Monedas a / b a) cantidad de monedas en posesión. b) historial de cantidad de monedas. - - + + Copy Address Copiar la dirección - + + QR of Address + QR de la dirección + + + Copy Private Key Copiar clave privada - + + QR of Private Key + QR de la clave privada + + + Coins: %1 / %2 Monedas: %1 / %2 - + Signed with Schnorr signatures in the past Firmado con firmas de Schnorr en el pasado diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index a5bb29e..011a02d 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -16,31 +16,31 @@ Archive Wallet - Archivar monedero + Archivar billetera - + Make Primary - Hacer principal + Convertir en Principal - + ★ Primary ★ Principal - + Protect With Pin... Proteger con Pin... - + Open Open encrypted wallet Abrir - + Close Close encrypted wallet Cerrar @@ -64,92 +64,92 @@ Estado de la sincronización - + Encryption Cifrado - + Password Contraseña - + Open Abrir - + Invalid PIN PIN inválido - + Include balance in total Incluir en el balance total - + Hide in private mode Ocultar en modo privado - + Address List Lista de direcciones - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Alterna entre direcciones donde otros pueden pagarte y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between unused and used Bitcoin addresses Alterna entre direcciones no usadas y usadas de Bitcoin - + Backup details Detalles de la copia de seguridad - + Seed-phrase Frase semilla - + Seed format - Seed format + Formato de semilla - + Derivation Derivación - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. @@ -162,59 +162,97 @@ Última transacción + + AddressDbStats + + + IP Addresses + Direcciones de IP + + + + Total found + Total encontrado + + + + Tried + Intentado + + + + Punished count + Número de castigados + + + + Banned count + Número de baneados + + + + IP-v4 count + Recuento IP-v4 + + + + IP-v6 count + Recuento IP-v6 + + + + Pardon the Banned + Perdonar los Baneados + + + + Close + Cerrar + + NetView - + Peers (%1) - + + Pares (%1) Pares (%1) - Peers (%1) - + Address network address (IP) Dirección - + Start-height: %1 Altura de inicio: %1 - + ban-score: %1 calificación de no confiabilidad: %1 - - initializing connection - Inicializando conexión - - - - Verifying peer - Verificando par - - - - Assigning... - Asignando... - - - + Peer for wallet: %1 Par para el monedero: %1 - - Peer for initial wallet - Par para la cartera inicial + + Disconnect Peer + Desconectar par - + + Ban Peer + Banear par + + + Close Cerrar @@ -222,32 +260,27 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - Esto crea un nuevo monedero vacío con capacidad de multi-dirección simple + + Create a new empty wallet with simple multi-address capability + Crear una nueva cartera vacía con una simple capacidad multi-dirección - + Name Nombre - + Go Ir - - Advanced Options - Opciones Avanzadas - - - + Force Single Address Forzar dirección única - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Cuando está habilitado, este monedero se limitará a una dirección. @@ -257,27 +290,27 @@ Esto asegura que solo una clave privada tendrá que ser respaldada NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - Esto crea un nuevo monedero vacío con la creación inteligente de direcciones a partir de una sola frase de semilla + + Creates a new wallet with smart creation of addresses from a single seed-phrase + Crea una nueva cartera con la creación inteligente de direcciones a partir de una sola frase semilla - + Name Nombre - + Go Ir - + Advanced Options Opciones Avanzadas - + Derivation Derivación @@ -285,188 +318,172 @@ Esto asegura que solo una clave privada tendrá que ser respaldada NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. - - - - Secret - The seed-phrase or private key - Secreto - - - - Example: %1 - placeholder text - Ejemplo: %1 - - - + + Name Nombre - - Private key - description of type - Llave privada - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 seed-phrase (interpreted as Electrum format) - - - - BIP 39 seed-phrase - description of type - Frase semilla BIP 39 + + Select import method + Seleccionar método de importación - Electrum seed-phrase - description of type - Electrum seed-phrase + Camera + Cámara - - Unrecognized word - Word from the seed-phrases lexicon - Palabra no reconocida + + OR + O - - Import wallet - Importar monedero + + Secret as text + The seed-phrase or private key + Secreto como texto - - Advanced Options - Opciones Avanzadas + + Unknown word(s) found + Palabra(s) desconocidas encontradas - + + Address to import + Dirección a importar + + + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - Old Electrum Phrase - Old Electrum Phrase + + + Oldest Transaction + Transacción más antigua - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + Check Age + online check for wallet age + Comprobar edad - - Start Height - Altura de inicio + + Nothing found for wallet + No se encontró nada para esta cartera - + + + Start + Comenzar + + + + Password + Contraseña + + + + Optional + Opcional + + + + Details + Detalles + + + + Lookup Details + online check for wallet details + Detalles de la búsqueda + + + + Nothing found for seed + No se encontró nada para la semilla + + + Derivation Derivación - - - Alternate phrase - Frase alternativa - NewAccountPane - - New Bitcoin Cash Wallet - Nuevo monedero Bitcoin Cash + + New HD wallet + Nuevo monedero-HD - - Basic - Básico + + Import Existing Wallet + Importar un monedero existente - + + New Basic Wallet + Nuevo monedero básico + + + Private keys based Property of a wallet Basado en Claves privadas - + Difficult to backup Context: wallet type Difícil de respaldar - + Great for brief usage Context: wallet type Ideal para un uso breve - - HD wallet - Monedero HD - - - + Seed-phrase based Context: wallet type Basado en frase semilla - + Easy to backup Context: wallet type Fácil de respaldar - + Most compatible The most compatible wallet type El más compatible - - Import - Importar - - - + Imports seed-phrase Importa la frase semilla - + Imports private key Importa la clave privada - - - Basic wallet with private keys - Monedero básico con claves privadas - - - - Seed-phrase wallet - Monedero con frase semilla - - - - Import of an existing wallet - Importar cartera existente - PaymentTweakingPanel @@ -590,7 +607,7 @@ El cambio volverá a la clave importada. Payment request warnings: - Payment request warnings: + Advertencias de la solicitud de pago: @@ -662,6 +679,7 @@ El cambio volverá a la clave importada. + Copy Address Copiar dirección @@ -699,9 +717,9 @@ El cambio volverá a la clave importada. Selected %1 %2 in %3 coins selected 2 BCH in 5 coins - - Seleccionadas %1 %2 en %3 monedas - Selected %1 %2 in %3 coins + + Se seleccionaron %1 %2 en %3 monedas + Se seleccionaron %1 %2 en %3 monedas @@ -764,60 +782,251 @@ El cambio volverá a la clave importada. Configuraciones - + Unit Unidad - + Show Bitcoin Cash value on Activity page Mostrar valor de Bitcoin Cash en la página de Actividad - + Show Block Notifications Mostrar notificaciones bloqueadas - + When a new block is mined, Flowee Pay shows a desktop notification Cuando un nuevo bloque es minado, Flowee Pay muestra una notificación de escritorio - + Night Mode Modo nocturno - + Private Mode Modo Privado - + Hides private wallets while enabled Oculta monederos privados mientras está habilitado - + + Font sizing + Tamaño de fuente + + + Version Versión - + Library Version Versión de la biblioteca - + Synchronization Sincronización - + Network Status Estado de la red + + + Address Stats + Estadísticas de dirección + + + + Transaction + + + Miner Reward + Recompensa del minero + + + + Fused + Fusionado + + + + Received + Recibido + + + + Moved + Movido + + + + Sent + Enviado + + + + rejected + rechazada + + + + TransactionDetails + + + Transaction Details + Detalles de la transacción + + + + First Seen + Visto por primera vez + + + + Rejected + Rechazado + + + + Unconfirmed + Sin confirmar + + + + Mined at + Minado en + + + + %1 blocks ago + + Hace %1 bloque + Hace %1 bloques + + + + + Comment + Comentario + + + + Fees paid + Comisiones pagadas + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Size + Tamaño + + + + %1 bytes + %1 bytes + + + + Is Coinbase + Es Coinbase + + + + Copy transaction-ID + Copiar ID de la transacción + + + + Fused from my addresses + Fusionado desde mis direcciones + + + + Sent from my addresses + Enviado desde mis direcciones + + + + Sent from addresses + Enviado desde direcciones + + + + Fused into my addresses + Fusionado en mis direcciones + + + + Received at addresses + Recibido en direcciones + + + + Received at my addresses + Recibido en mis direcciones + + + + TransactionInfoSmall + + + Transaction is rejected + Transacción rechazada + + + + Processing + Procesando + + + + Mined + Minado + + + + %1 blocks ago + Confirmations + + Hace %1 bloque + Hace %1 bloques + + + + + Fees + Comisiones + + + + Copy transaction-ID + Copiar ID de la transacción + + + + Holds a token + Contiene un token + + + + Opening Website + Abriendo Sitio Web + WalletEncryption @@ -946,16 +1155,16 @@ El cambio volverá a la clave importada. WalletEncryptionStatus - - - Pin to Pay - PIN para pagar - Pin to Open PIN para abrir + + + Pin to Pay + PIN para pagar + (Opened) @@ -963,217 +1172,114 @@ El cambio volverá a la clave importada. (Abierto) - - WalletTransaction - - - Miner Reward - Recompensa del minero - - - - Fused - Fused - - - - Received - Recibido - - - - Moved - Movido - - - - Sent - Enviado - - - - rejected - rechazada - - - - unconfirmed - sin confirmar - - - - WalletTransactionDetails - - - Copy transaction-ID - Copiar ID de la transacción - - - - Status - Estado - - - - rejected - rechazada - - - - unconfirmed - sin confirmar - - - - %1 confirmations (mined in block %2) - - %1 confirmations (mined in block %2) - %1 confirmaciones (minadas en el bloque %2) - - - - - Copy block height - Copiar altura del bloque - - - - Fees - Comisiones - - - - Size - Tamaño - - - - %1 bytes - - %1 bytes - %1 bytes - - - - - Inputs - Entradas - - - - - Copy Address - Copiar dirección - - - - Outputs - Salidas - - main - + Activity Actividad - + Archived wallets do not check for activities. Balance may be out of date. Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado. - + Unarchive Desarchivar - + This wallet needs a password to open. Esta cartera necesita una contraseña para abrirse. - + Password: Contraseña: - + Invalid password Contraseña invalida - + Open Abrir - + Send Enviar - + Receive Recibir - + Balance Balance - + Main balance (money), non specified Principal - + Unconfirmed balance (money) Sin confirmar - + Immature balance (money) Sin madurar - + 1 BCH is: %1 1 BCH es: %1 - + Network status Estado de la red - + Offline Sin conexión - + Add Bitcoin Cash wallet Añadir monedero de Bitcoin Cash - + Archived wallets [%1] Arg is wallet count - - Monederos archivados [%1] - Archived wallets [%1] + + Billeteras archivadas [%1] + Billeteras archivadas [%1] - + Preparing... Preparando... + + + QR-Scan + Escaneo de QR + diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index 602370e..be8b4db 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - © 2020-2023 Tom Zander y contribuyentes + © 2020-2024 Tom Zander and contributors + ©️ 2020-2024 Tom Zander y colaboradores - + Project Home Página del proyecto - + With git repository and issues tracker Con repositorio git y seguimiento de incidencias - + Telegram Telegram @@ -63,42 +63,32 @@ Recibir - + Miner Reward Recompensa del minero - + Fused - Fused + Fusionado - + Received Recibido - + Moved Movido - + Sent Enviado - - - Sending - Enviando - - Seen - Visto - - - Rejected Rechazado @@ -126,99 +116,100 @@ Detalles de la copia de seguridad - + Wallet seed-phrase Frase-semilla del monedero - + Seed format - Seed format + Formato de semilla - + + Starting Height height refers to block-height Altura inicial - + Derivation Path Ruta de Derivación - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que pierda su móvil. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + Wallet keys Llaves del monedero - + Show Index toggle to show numbers Mostrar índice - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Intercambia entre direcciones que otros pueden pagarle y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between still in use addresses and formerly used, new empty, addresses Alterna entre direcciones en uso, previamente usadas y direcciones sin usar - + Addresses and keys Direcciones y claves - + Sync Status Estado de la sincronización - + Hide balance in overviews Ocultar balance en vistas generales - + Hide in private mode Ocultar en modo privado - + Unarchive Wallet Desarchivar monedero - + Archive Wallet Archivar monedero @@ -241,7 +232,7 @@ Necesita PIN para abrir - + Balance Total Balance total @@ -339,25 +330,25 @@ Dark Theme - Dark Theme + Tema Oscuro - + Unit Unidad - + Change Currency (%1) Cambiar moneda (%1) - + Main View Vista principal - + Show Bitcoin Cash value Mostrar valor de Bitcoin Cash @@ -370,93 +361,113 @@ Importar monedero - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Por favor, introduzca los secretos del monedero a importar. Esto puede ser una frase semilla o una clave privada. - - - - Secret - The seed-phrase or private key - Secreto - - - - Private key - description of type - Llave privada - - - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 seed-phrase (interpreted as Electrum) - - - - BIP 39 seed-phrase - description of type - Frase semilla BIP 39 - - - - Electrum seed-phrase - description of type - Electrum seed-phrase - - - - Unrecognized word - Word from the seed-phrases lexicon - Palabra no reconocida - - - + + Name Nombre - + Force Single Address Forzar dirección única - + + Select import method + Seleccionar método de importación + + + + Camera + Cámara + + + + OR + O + + + + Secret as text + The seed-phrase or private key + Secreto como texto + + + + Unknown word(s) found + Palabra(s) desconocidas encontradas + + + + Next + Siguiente + + + + Address to import + Dirección a importar + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - Old Electrum Phrase - Old Electrum Phrase + + Nothing found for seed + No se encontró nada para la semilla - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - - - + + Oldest Transaction Transacción más antigua - + + Check Age + online check for wallet age + Comprobar edad + + + + Nothing found for wallet + No se encontró nada para esta cartera + + + + + Start + Comenzar + + + + Password + Contraseña + + + + Optional + Opcional + + + + Details + Detalles + + + + Lookup Details + online check for wallet details + Detalles de la búsqueda + + + Derivation Derivación - - - Alternate phrase - Frase alternativa - - - - Create - Crear - InstaPayConfigButton @@ -495,21 +506,21 @@ El cambio volverá a la clave importada. - Requests for payment can be approved automatically using Instant Pay. - Las solicitudes de pago pueden ser aprobadas automáticamente usando Pago Instantáneo. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Escanear el código QR con el Pago Instantáneo habilitado hará que la transferencia salga sin confirmación. Siempre y cuando no exceda el límite establecido. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Los monederos protegidos no se pueden utilizar para InstaPagos porque requieren un PIN antes de usarse - + Enable Instant Pay for this wallet Habilitar Pago Instantáneo para este monedero - + Maximum Amount Monto máximo @@ -583,122 +594,112 @@ El cambio volverá a la clave importada. Nuevo monedero Bitcoin Cash - - Create a New Wallet - Crear un nuevo monedero - - - - HD wallet - Monedero HD - - - + Seed-phrase based Context: wallet type Basado en frase semilla - + Easy to backup Context: wallet type Fácil de respaldar - + Most compatible The most compatible wallet type El más compatible - - Basic - Básico - - - + Private keys based Property of a wallet Basado en Claves privadas - + Difficult to backup Context: wallet type Difícil de respaldar - + Great for brief usage Context: wallet type Ideal para un uso breve - + Import Existing Wallet Importar un monedero existente - - Import - Importar + + New HD Wallet + Nuevo monedero-HD - + Imports seed-phrase Importa la frase semilla - + Imports private key Importa la clave privada - + + New Basic Wallet + Nuevo monedero básico + + + New Wallet Nuevo Monedero - + This creates a new empty wallet with simple multi-address capability Esto crea un nuevo monedero vacío con capacidad de multi-dirección simple - - + + Name Nombre - + Force Single Address Forzar dirección única - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Cuando está habilitado, este monedero se limitará a una dirección. Esto asegura que solo una clave privada tendrá que ser respaldada - - + + Create Crear - + New HD-Wallet Nuevo monedero-HD - + This creates a new wallet that can be backed up with a seed-phrase Esto crea un nuevo monedero que puede ser respaldado con una frase semilla - + Derivation Derivación @@ -759,7 +760,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -799,29 +800,6 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Todas las divisas - - QRScannerOverlay - - - Paste - Pegar - - - - Failed - Fallido - - - - Instant Pay limit is %1 - Instant Pay limit is %1 - - - - Selected wallet: '%1' - Selected wallet: '%1' - - ReceiveTab @@ -999,27 +977,37 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Detalles de la transacción - + + Open in Explorer + Abrir en el explorador + + + Transaction Hash Hash de transacción - + + First Seen + Visto por primera vez + + + Rejected Rechazada - + Unconfirmed Sin confirmar - + Mined at Minado en - + %1 blocks ago Hace %1 bloque @@ -1027,140 +1015,124 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - + Transaction comment Comentario de la transacción - + Size: %1 bytes Tamaño: %1 bytes - + Coinbase Coinbase - + Fees paid Comisiones pagadas - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fusionado desde mis direcciones - + Sent from my addresses Enviado desde mis direcciones - + Sent from addresses Enviado desde direcciones - - - Copy Address - Copiar dirección - - - + Fused into my addresses Fusionado en mis direcciones - + Received at addresses Recibido en direcciones - + Received at my addresses Recibido en mis direcciones - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected Transacción rechazada - + Processing Procesando - + Mined Minado - + %1 blocks ago Confirmations - + Hace %1 bloque - %1 blocks ago + Hace %1 bloques - + Miner Reward Recompensa del minero - + Fees Comisiones - + Received Recibido - + Payment to self Auto pago - + Sent Enviado - + Holds a token Contiene un token - + Sent to Enviado a - - Value now - Valor ahora - - - - Value then - Valor entonces - - - + Transaction Details Detalles de la transacción @@ -1173,7 +1145,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Introduzca su código de acceso al monedero - + Open open wallet with PIN Abrir diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts index ed6a078..5975d56 100644 --- a/translations/module-build-transaction_es.ts +++ b/translations/module-build-transaction_es.ts @@ -40,7 +40,7 @@ - + Add Destination Añadir destino @@ -110,7 +110,7 @@ - + Copy Address Copiar dirección @@ -131,42 +131,37 @@ Dirección de Bitcoin Cash - - Paste - Pegar - - - + Warning Advertencia - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + I am certain Estoy seguro - + Drag to Edit Arrastre para editar - + Drag to Delete Arrastre para eliminar - + Unlock Wallet Desbloquear Monedero - + Prepare Payment... Preparar pago... diff --git a/translations/module-peers-view_es.ts b/translations/module-peers-view_es.ts index 4ec0277..e2ad647 100644 --- a/translations/module-peers-view_es.ts +++ b/translations/module-peers-view_es.ts @@ -27,7 +27,7 @@ ban-score: %1 - calificación de no confiabilidad: %1 + puntaje: %1 -- 2.54.0 From b5eaf1534ff5c94579bc0a3d42e4748ea73bade7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Oct 2023 13:22:34 +0200 Subject: [PATCH 0891/1428] Use smaller static file --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c4d1e1..9c03b31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,7 +231,7 @@ if (ANDROID AND build_mobile_pay) # blockheaders to be included in the APK set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) if (NOT quick_deploy) - download_file(https://flowee.org/products/pay/blockheaders + download_file(https://flowee.org/products/pay/blockheaders_760000-810000 ${ASSETS_DIR}/blockheaders) endif() file(COPY ${CMAKE_SOURCE_DIR}/data/bip39-english -- 2.54.0 From 6c54a74eaa05e867276ca49f985c66ab4903b2d8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Oct 2023 13:33:38 +0200 Subject: [PATCH 0892/1428] Start new module --- modules/blocks/BlocksModuleInfo.cpp | 35 +++++++++++++++++++++++++++++ modules/blocks/BlocksModuleInfo.h | 31 +++++++++++++++++++++++++ modules/blocks/CMakeLists.txt | 24 ++++++++++++++++++++ modules/blocks/Configuration.qml | 29 ++++++++++++++++++++++++ modules/blocks/blocks-data.qrc | 6 +++++ modules/blocks/blocks.svg | 13 +++++++++++ 6 files changed, 138 insertions(+) create mode 100644 modules/blocks/BlocksModuleInfo.cpp create mode 100644 modules/blocks/BlocksModuleInfo.h create mode 100644 modules/blocks/CMakeLists.txt create mode 100644 modules/blocks/Configuration.qml create mode 100644 modules/blocks/blocks-data.qrc create mode 100644 modules/blocks/blocks.svg diff --git a/modules/blocks/BlocksModuleInfo.cpp b/modules/blocks/BlocksModuleInfo.cpp new file mode 100644 index 0000000..88f5a36 --- /dev/null +++ b/modules/blocks/BlocksModuleInfo.cpp @@ -0,0 +1,35 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "BlocksModuleInfo.h" + +ModuleInfo * BlocksModuleInfo::build() +{ + ModuleInfo *info = new ModuleInfo(); + info->setId("blocksModule"); + info->setTitle(tr("History Details")); + info->setDescription(tr("Everything about blocks")); + info->setIconSource("qrc:/blocks/blocks.svg"); + + auto menuExample = new ModuleSection(ModuleSection::MainMenuItem, info); + menuExample->setText(tr("Blocks")); + menuExample->setSubtext(tr("History of our chain")); + menuExample->setStartQMLFile("qrc:/blocks/Configuration.qml"); + info->addSection(menuExample); + + return info; +} diff --git a/modules/blocks/BlocksModuleInfo.h b/modules/blocks/BlocksModuleInfo.h new file mode 100644 index 0000000..e84e049 --- /dev/null +++ b/modules/blocks/BlocksModuleInfo.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class BlocksModuleInfo : public QObject +{ + Q_OBJECT +public: + static ModuleInfo *build(); + + static const char *translationUnit() { + return "module-blocks"; + } +}; diff --git a/modules/blocks/CMakeLists.txt b/modules/blocks/CMakeLists.txt new file mode 100644 index 0000000..27ce936 --- /dev/null +++ b/modules/blocks/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2024 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(blocks_module) + +set (SOURCES + BlocksModuleInfo.cpp +) +add_library (blocks_module_lib STATIC ${SOURCES}) +target_link_libraries(blocks_module_lib pay_lib) + diff --git a/modules/blocks/Configuration.qml b/modules/blocks/Configuration.qml new file mode 100644 index 0000000..dfd9393 --- /dev/null +++ b/modules/blocks/Configuration.qml @@ -0,0 +1,29 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee +import "../mobile"; + +Page { + headerText: qsTr("Blocks") + Flowee.Label { + y: 10 + text: "Nothing here yet" + } +} diff --git a/modules/blocks/blocks-data.qrc b/modules/blocks/blocks-data.qrc new file mode 100644 index 0000000..9829a2a --- /dev/null +++ b/modules/blocks/blocks-data.qrc @@ -0,0 +1,6 @@ + + + Configuration.qml + blocks.svg + + diff --git a/modules/blocks/blocks.svg b/modules/blocks/blocks.svg new file mode 100644 index 0000000..d8efb34 --- /dev/null +++ b/modules/blocks/blocks.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + -- 2.54.0 From 491af940408830988f84c49b0d49c69cdb3e557b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 31 May 2024 21:51:57 +0200 Subject: [PATCH 0893/1428] Provide another plugin integration. Plugins can be plugged into the main menu and the send menu already, this adds the ability for a (section of a) plugin to be loaded as custom component in the main app, among others. --- modules/blocks/BlockHeadersChecker.cpp | 7 +++++ modules/blocks/BlockHeadersChecker.h | 18 ++++++++++++ modules/blocks/BlocksModuleInfo.cpp | 24 ++++++++++++---- modules/blocks/CMakeLists.txt | 1 + modules/blocks/DownloadChecker.qml | 40 ++++++++++++++++++++++++++ modules/blocks/blocks-data.qrc | 1 + src/ModuleManager.cpp | 14 +++++++++ src/ModuleManager.h | 4 ++- src/ModuleSection.cpp | 12 +++++++- src/ModuleSection.h | 15 +++++++--- 10 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 modules/blocks/BlockHeadersChecker.cpp create mode 100644 modules/blocks/BlockHeadersChecker.h create mode 100644 modules/blocks/DownloadChecker.qml diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp new file mode 100644 index 0000000..dbb12cb --- /dev/null +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -0,0 +1,7 @@ +#include "BlockHeadersChecker.h" + + +BlockHeadersChecker::BlockHeadersChecker(QObject *parent) + : QObject(parent) +{ +} diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h new file mode 100644 index 0000000..7e49022 --- /dev/null +++ b/modules/blocks/BlockHeadersChecker.h @@ -0,0 +1,18 @@ +#ifndef BLOCKHEADERSCHECKER_H +#define BLOCKHEADERSCHECKER_H + +#include + +class BlockHeadersChecker : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString bla READ bla CONSTANT); +public: + BlockHeadersChecker(QObject *parent = nullptr); + + QString bla() const { + return "blaat"; + } +}; + +#endif diff --git a/modules/blocks/BlocksModuleInfo.cpp b/modules/blocks/BlocksModuleInfo.cpp index 88f5a36..ec460a6 100644 --- a/modules/blocks/BlocksModuleInfo.cpp +++ b/modules/blocks/BlocksModuleInfo.cpp @@ -16,20 +16,32 @@ * along with this program. If not, see . */ #include "BlocksModuleInfo.h" +#include "BlockHeadersChecker.h" + +#include // for the qmlRegisterType ModuleInfo * BlocksModuleInfo::build() { ModuleInfo *info = new ModuleInfo(); - info->setId("blocksModule"); + info->setId("blocks"); info->setTitle(tr("History Details")); info->setDescription(tr("Everything about blocks")); info->setIconSource("qrc:/blocks/blocks.svg"); - auto menuExample = new ModuleSection(ModuleSection::MainMenuItem, info); - menuExample->setText(tr("Blocks")); - menuExample->setSubtext(tr("History of our chain")); - menuExample->setStartQMLFile("qrc:/blocks/Configuration.qml"); - info->addSection(menuExample); + auto menuSection = new ModuleSection(ModuleSection::MainMenuItem, info); + menuSection->setText(tr("Blocks")); + menuSection->setSubtext(tr("History of our chain")); + menuSection->setStartQMLFile("qrc:/blocks/Configuration.qml"); + info->addSection(menuSection); + + // The custom section that is used by the main app to check if + // the blockchain currently available is should be expanded. + auto checker = new ModuleSection(ModuleSection::CustomSectionType, info); + checker->setSectionId("checker"); + checker->setStartQMLFile("qrc:/blocks/DownloadChecker.qml"); + info->addSection(checker); + + qmlRegisterType("Flowee.org.pay.blocks", 1, 0, "Checker"); return info; } diff --git a/modules/blocks/CMakeLists.txt b/modules/blocks/CMakeLists.txt index 27ce936..1872df8 100644 --- a/modules/blocks/CMakeLists.txt +++ b/modules/blocks/CMakeLists.txt @@ -18,6 +18,7 @@ project(blocks_module) set (SOURCES BlocksModuleInfo.cpp + BlockHeadersChecker.cpp ) add_library (blocks_module_lib STATIC ${SOURCES}) target_link_libraries(blocks_module_lib pay_lib) diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml new file mode 100644 index 0000000..97bc91a --- /dev/null +++ b/modules/blocks/DownloadChecker.qml @@ -0,0 +1,40 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee +import "../mobile"; +import Flowee.org.pay.blocks as Blocks; + +Page { + headerText: qsTr("Blocks test") + + + Item { + id: data + Blocks.Checker { + id: testData + } + } + + Flowee.Label { + anchors.centerIn: parent + text: testData.bla + } +} + diff --git a/modules/blocks/blocks-data.qrc b/modules/blocks/blocks-data.qrc index 9829a2a..3318465 100644 --- a/modules/blocks/blocks-data.qrc +++ b/modules/blocks/blocks-data.qrc @@ -2,5 +2,6 @@ Configuration.qml blocks.svg + DownloadChecker.qml diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index e40e2d1..f2a8b20 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -247,3 +247,17 @@ QList ModuleManager::maindMenuSections() const } return answer; } + +ModuleSection *ModuleManager::sectionOnPlugin(const QString &pluginId, const QString §ionId) const +{ + for (const auto *m : m_modules) { + if (m->id() == pluginId) { + for (auto *s : m->sections()) { + if (s->type() == ModuleSection::CustomSectionType + && s->sectionId() == sectionId) + return s; + } + } + } + return nullptr; +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 6126d07..dae5e6d 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,6 +54,8 @@ public: QList sendMenuSections() const; QList maindMenuSections() const; + Q_INVOKABLE ModuleSection* sectionOnPlugin(const QString &pluginId, const QString §ionId) const; + signals: void sendMenuSectionsChanged(); void mainMenuSectionsChanged(); diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index 9c4d649..aefcc35 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -61,6 +61,16 @@ void ModuleSection::setEnabled(bool on) emit enabledChanged(); } +QString ModuleSection::sectionId() const +{ + return m_sectionId; +} + +void ModuleSection::setSectionId(const QString &id) +{ + m_sectionId = id; +} + void ModuleSection::setStartQMLFile(const QString &filename) { m_startQMLfile = filename; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index e07044d..415ac85 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,9 +39,11 @@ class ModuleSection : public QObject public: /// The placement in the main app of this section. enum SectionType { - SendMethod, //< A specific way to send coin, shown in list of send-methods. - // BuildTxComponent, //< Inside the 'build-transactions' this adds a new buildingblock. - MainMenuItem, //< A text-button in the main menu + SendMethod, ///< A specific way to send coin, shown in list of send-methods. + MainMenuItem, ///< A text-button in the main menu + CustomSectionType, ///< Not normally shown type, but fetchable by id. + + // BuildTxComponent, ///< Inside the 'build-transactions' this adds a new buildingblock. }; explicit ModuleSection(SectionType type, QObject *parent = nullptr); @@ -84,11 +86,16 @@ public: */ void setEnabled(bool on); + /// Used instead of a type, but only when type is CustomSectionType + QString sectionId() const; + void setSectionId(const QString &id); + signals: void enabledChanged(); private: const SectionType m_type; + QString m_sectionId; QString m_text; QString m_subtext; QString m_startQMLfile; -- 2.54.0 From ef462fb9df14455338fa6b24519238580174adaf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Jun 2024 13:30:04 +0200 Subject: [PATCH 0894/1428] Give some meat to the BlockHeadersChecker This actually checks the need for downloading more headers, then decides on which headers to download (from which known checkpoint, really) and downloads them to a file. --- modules/blocks/BlockHeadersChecker.cpp | 175 +++++++++++++++++++++++++ modules/blocks/BlockHeadersChecker.h | 52 +++++++- modules/blocks/DownloadChecker.qml | 3 + 3 files changed, 226 insertions(+), 4 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index dbb12cb..4914166 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -1,7 +1,182 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "BlockHeadersChecker.h" +#include "FloweePay.h" +#include +#include +#include +#include +#include + +static constexpr const char * HEADERFILE = "https://flowee.org/products/pay/blockheaders"; BlockHeadersChecker::BlockHeadersChecker(QObject *parent) : QObject(parent) { } + +int BlockHeadersChecker::wantedHeight() const +{ + return m_wantedHeight; +} + +void BlockHeadersChecker::setWantedHeight(int h) +{ + if (m_wantedHeight == h) + return; + m_wantedHeight = h; + emit wantedHeightChanged(); + + QTimer::singleShot(0, this, SLOT(startChecking())); +} + +void BlockHeadersChecker::startChecking() +{ + logFatal() << "starting" << m_wantedHeight; + const auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); + bool have = false; + const auto sources = blockchain.dataSources(); + assert(!sources.empty()); + for (const auto &source : sources) { + if (source.startBlock <= m_wantedHeight) + have = false; + } + if (have) { + logFatal() << "Nothing to do, the blockchain has the needed headers"; + // TODO maybe set a property? + return; + } + logFatal() << "step 2"; + + + int startHeight = 0; + for (const auto &cp : blockchain.checkpoints()) { + if (cp.height < m_wantedHeight) { + startHeight = cp.height; + logFatal() << "maybe" << cp.height << cp.hash; + } + else + break; + } + + logFatal() << "Aiming to extend headers to checkpoint:" << startHeight; + m_checkpoint = startHeight; + m_downloadTo = sources.back().endBlock - 1; + + // do a HEAD + if (m_headerReply == nullptr) { + QNetworkRequest headRequest((QUrl(HEADERFILE))); + m_headerReply = m_network.head(headRequest); + + connect(m_headerReply, SIGNAL(finished()), this, SLOT(headerReturned())); + } +} + +void BlockHeadersChecker::headerReturned() +{ + assert(m_headerReply); + m_headerReply->deleteLater(); + if (m_newHeaders) { + logFatal() << "Already downloading..."; + return; + } + + uint64_t length = m_headerReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); + auto allowedRange = m_headerReply->rawHeader("Accept-Ranges"); +logFatal() << "Header returned:" << length << QString::fromLatin1(allowedRange); + m_headerReply = nullptr; + + if (allowedRange != "bytes") { + logFatal() << "Can't download partial files. Failing"; + return; + } + + // We're going to download the headers. + // On the remote server there is one file that has the genesis + // all the way up to a recent height=((length / 80) - 1) + + // We use the 'range' feature of the webserver to only download + // what we need, so lets calculate the offsets here. + assert(m_downloadTo > m_checkpoint); + assert(m_checkpoint > 0); + const int from = m_checkpoint * 80; + const int to = m_downloadTo * 80; + + QNetworkRequest downloadRequest((QUrl(HEADERFILE))); + QString range("bytes=%1-%2"); + range = range.arg(from); + range = range.arg(to - 1); // -1 because ranges are inclusive. + downloadRequest.setRawHeader("Range", range.toLatin1()); + + QString filename("newheaders_%1-%2"); + filename = filename.arg(from); + filename = filename.arg(to); + m_newHeaders = new QFile(filename, this); + if (!m_newHeaders->open(QIODevice::WriteOnly)) { + logFatal() << "Can not open file..."; + return; + } + + // actually start the download + m_downloadReply = m_network.get(downloadRequest); + connect(m_downloadReply, SIGNAL(readyRead()), this, SLOT(onBytesDownloaded())); + connect(m_downloadReply, SIGNAL(finished()), this, SLOT(downloadFinished())); +} + +void BlockHeadersChecker::downloadFinished() +{ + assert(m_downloadReply); + m_downloadReply->deleteLater(); + m_downloadReply = nullptr; + + m_newHeaders->close(); + + // TODO use the file +} + +void BlockHeadersChecker::onBytesDownloaded() +{ + assert(m_downloadReply); + assert(m_newHeaders); + assert(m_newHeaders->isOpen()); + + int downloaded = 0; + char buf[4096]; + while (true) { + auto readCount = m_downloadReply->read(buf, sizeof(buf)); + if (readCount <= 0) + break; + m_newHeaders->write(buf, readCount); + downloaded += readCount; + } + setBytesDownloaded(m_bytesDownloaded + downloaded); +} + +void BlockHeadersChecker::setBytesDownloaded(int count) +{ + if (m_bytesDownloaded == count) + return; + m_bytesDownloaded = count; + emit bytesDownloadedChanged(); +} + +int BlockHeadersChecker::bytesDownloaded() +{ + return m_bytesDownloaded; +} diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index 7e49022..29135d1 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -1,18 +1,62 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #ifndef BLOCKHEADERSCHECKER_H #define BLOCKHEADERSCHECKER_H +#include +#include #include class BlockHeadersChecker : public QObject { Q_OBJECT - Q_PROPERTY(QString bla READ bla CONSTANT); + Q_PROPERTY(int wantedHeight READ wantedHeight WRITE setWantedHeight NOTIFY wantedHeightChanged FINAL) + Q_PROPERTY(int bytesDownloaded READ bytesDownloaded WRITE setBytesDownloaded NOTIFY bytesDownloadedChanged FINAL) public: BlockHeadersChecker(QObject *parent = nullptr); - QString bla() const { - return "blaat"; - } + int wantedHeight() const; + void setWantedHeight(int h); + + void setBytesDownloaded(int count); + int bytesDownloaded(); + +signals: + void wantedHeightChanged(); + void bytesDownloadedChanged(); + +private slots: + void startChecking(); + void headerReturned(); + void downloadFinished(); + void onBytesDownloaded(); + +private: + int m_wantedHeight = 0; + int m_checkpoint = 0; // start download at checkpoint-height + int m_downloadTo = 0; // download to this point (we have the rest) + + int m_bytesDownloaded = 0; // download progress + + QNetworkAccessManager m_network; + QNetworkReply *m_headerReply = nullptr; + QNetworkReply *m_downloadReply = nullptr; + QFile *m_newHeaders = nullptr; }; #endif diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml index 97bc91a..ecfa39d 100644 --- a/modules/blocks/DownloadChecker.qml +++ b/modules/blocks/DownloadChecker.qml @@ -22,13 +22,16 @@ import "../mobile"; import Flowee.org.pay.blocks as Blocks; Page { + id: root headerText: qsTr("Blocks test") + property int wantedHeight: 0 Item { id: data Blocks.Checker { id: testData + wantedHeight: root.wantedHeight } } -- 2.54.0 From 6d1cc00eeba3a1fc07a1de1fe94cb36c7b56c25d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Jun 2024 20:28:58 +0200 Subject: [PATCH 0895/1428] Finish the basic functionality of the checker. --- modules/blocks/BlockHeadersChecker.cpp | 80 +++++++++++++++++++++----- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 4914166..7e4f929 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -48,36 +48,33 @@ void BlockHeadersChecker::setWantedHeight(int h) void BlockHeadersChecker::startChecking() { - logFatal() << "starting" << m_wantedHeight; + logInfo() << "starting" << m_wantedHeight; + if (FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) + return; const auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); bool have = false; const auto sources = blockchain.dataSources(); assert(!sources.empty()); for (const auto &source : sources) { if (source.startBlock <= m_wantedHeight) - have = false; + have = true; } if (have) { logFatal() << "Nothing to do, the blockchain has the needed headers"; // TODO maybe set a property? return; } - logFatal() << "step 2"; - int startHeight = 0; for (const auto &cp : blockchain.checkpoints()) { - if (cp.height < m_wantedHeight) { + if (cp.height < m_wantedHeight) startHeight = cp.height; - logFatal() << "maybe" << cp.height << cp.hash; - } else - break; + break; // checkpoints are ordered. } - logFatal() << "Aiming to extend headers to checkpoint:" << startHeight; m_checkpoint = startHeight; - m_downloadTo = sources.back().endBlock - 1; + m_downloadTo = sources.front().startBlock; // do a HEAD if (m_headerReply == nullptr) { @@ -99,7 +96,6 @@ void BlockHeadersChecker::headerReturned() uint64_t length = m_headerReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); auto allowedRange = m_headerReply->rawHeader("Accept-Ranges"); -logFatal() << "Header returned:" << length << QString::fromLatin1(allowedRange); m_headerReply = nullptr; if (allowedRange != "bytes") { @@ -117,6 +113,10 @@ logFatal() << "Header returned:" << length << QString::fromLatin1(allowedRange); assert(m_checkpoint > 0); const int from = m_checkpoint * 80; const int to = m_downloadTo * 80; + if (to > length) { + logCritical() << "Remote file does not have the data we want"; + return; + } QNetworkRequest downloadRequest((QUrl(HEADERFILE))); QString range("bytes=%1-%2"); @@ -124,9 +124,7 @@ logFatal() << "Header returned:" << length << QString::fromLatin1(allowedRange); range = range.arg(to - 1); // -1 because ranges are inclusive. downloadRequest.setRawHeader("Range", range.toLatin1()); - QString filename("newheaders_%1-%2"); - filename = filename.arg(from); - filename = filename.arg(to); + QString filename("module_blocks_newheaders"); m_newHeaders = new QFile(filename, this); if (!m_newHeaders->open(QIODevice::WriteOnly)) { logFatal() << "Can not open file..."; @@ -145,9 +143,61 @@ void BlockHeadersChecker::downloadFinished() m_downloadReply->deleteLater(); m_downloadReply = nullptr; + assert(m_newHeaders->isOpen()); + logDebug() << "Wrote out:" << m_newHeaders->size() << "bytes" << (m_newHeaders->size() / 80); + // --- use the file: + // the new file is the headers I didn't have yet. We need to + // create a new file that has both the old static headers as well + // as the new ones concatenated in one. + // the file we put this in has to have an application-long lifetime. + // so lets give ownership to the main app. + + QFile oldStaticHeaders("staticHeaders"); + if (!oldStaticHeaders.open(QIODevice::ReadOnly)) { + logFatal() << "Failed to find old static headers"; + return; + } + logDebug() << " old ones:" << oldStaticHeaders.size() << "=>" << oldStaticHeaders.size() / 80; + char buf[4096]; + while (true) { + auto count = oldStaticHeaders.read(buf, sizeof(buf)); + if (count <= 0) + break; + m_newHeaders->write(buf, count); + } + oldStaticHeaders.close(); m_newHeaders->close(); - // TODO use the file + // the currenty in use static headers are in the location I want the new ones to be + // because when we restart, we open by path. So the new ones shoud have the main + // static headers path... + // Knowing that this will never run on Windowz, lets just rename the open file and + // place the newly created file at the known path. + + QFile::rename("staticHeaders", "staticHeaders_old"); + QFile::rename("staticHeaders.info", "staticHeaders_old.info"); + + bool ok = m_newHeaders->rename("staticHeaders"); + if (!ok) { + logFatal() << "Failed to rename to staticHeaders"; + return; + } + if (!m_newHeaders->open(QIODevice::ReadOnly)) { + logFatal() << "Failed to re-open staticHeaders"; + return; + } + try { + auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); + blockchain.replaceStaticChain(m_newHeaders->map(0, m_newHeaders->size()), + m_newHeaders->size(), "staticHeaders.info"); + // keep the file object from getting garbage collected. + // the map() requires the QFile to stay alive. + m_newHeaders->setParent(FloweePay::instance()); + } catch (const std::exception &e) { + logCritical() << e; + m_newHeaders->remove(); + } + m_newHeaders->close(); } void BlockHeadersChecker::onBytesDownloaded() -- 2.54.0 From 56f6fcf43c24d243f8f89bc9c83da4f61070a8fe Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Jun 2024 10:49:26 +0200 Subject: [PATCH 0896/1428] Add QML properties --- modules/blocks/BlockHeadersChecker.cpp | 46 ++++++++++++++++++++++++-- modules/blocks/BlockHeadersChecker.h | 30 +++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 7e4f929..d092a35 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -49,8 +49,11 @@ void BlockHeadersChecker::setWantedHeight(int h) void BlockHeadersChecker::startChecking() { logInfo() << "starting" << m_wantedHeight; - if (FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) + if (FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) { + setStatus(Finished); return; + } + setStatus(Checking); const auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); bool have = false; const auto sources = blockchain.dataSources(); @@ -60,8 +63,8 @@ void BlockHeadersChecker::startChecking() have = true; } if (have) { - logFatal() << "Nothing to do, the blockchain has the needed headers"; - // TODO maybe set a property? + logDebug() << "Nothing to do, the blockchain has the needed headers"; + setStatus(Finished); return; } @@ -99,6 +102,7 @@ void BlockHeadersChecker::headerReturned() m_headerReply = nullptr; if (allowedRange != "bytes") { + setStatus(NetworkFailure); logFatal() << "Can't download partial files. Failing"; return; } @@ -115,6 +119,7 @@ void BlockHeadersChecker::headerReturned() const int to = m_downloadTo * 80; if (to > length) { logCritical() << "Remote file does not have the data we want"; + setStatus(NetworkFailure); return; } @@ -128,8 +133,11 @@ void BlockHeadersChecker::headerReturned() m_newHeaders = new QFile(filename, this); if (!m_newHeaders->open(QIODevice::WriteOnly)) { logFatal() << "Can not open file..."; + setStatus(DiskFailure); return; } + setTotalDownload(to - from); + setStatus(Downloading); // actually start the download m_downloadReply = m_network.get(downloadRequest); @@ -155,9 +163,11 @@ void BlockHeadersChecker::downloadFinished() QFile oldStaticHeaders("staticHeaders"); if (!oldStaticHeaders.open(QIODevice::ReadOnly)) { logFatal() << "Failed to find old static headers"; + setStatus(DiskFailure); return; } logDebug() << " old ones:" << oldStaticHeaders.size() << "=>" << oldStaticHeaders.size() / 80; + setStatus(Verifying); char buf[4096]; while (true) { auto count = oldStaticHeaders.read(buf, sizeof(buf)); @@ -180,10 +190,12 @@ void BlockHeadersChecker::downloadFinished() bool ok = m_newHeaders->rename("staticHeaders"); if (!ok) { logFatal() << "Failed to rename to staticHeaders"; + setStatus(DiskFailure); return; } if (!m_newHeaders->open(QIODevice::ReadOnly)) { logFatal() << "Failed to re-open staticHeaders"; + setStatus(DiskFailure); return; } try { @@ -195,9 +207,11 @@ void BlockHeadersChecker::downloadFinished() m_newHeaders->setParent(FloweePay::instance()); } catch (const std::exception &e) { logCritical() << e; + setStatus(NetworkFailure); m_newHeaders->remove(); } m_newHeaders->close(); + setStatus(Finished); } void BlockHeadersChecker::onBytesDownloaded() @@ -218,6 +232,32 @@ void BlockHeadersChecker::onBytesDownloaded() setBytesDownloaded(m_bytesDownloaded + downloaded); } +BlockHeadersChecker::Status BlockHeadersChecker::status() const +{ + return m_status; +} + +void BlockHeadersChecker::setStatus(Status newStatus) +{ + if (m_status == newStatus) + return; + m_status = newStatus; + emit statusChanged(); +} + +int BlockHeadersChecker::totalDownload() const +{ + return m_totalDownload; +} + +void BlockHeadersChecker::setTotalDownload(int newTotalDownload) +{ + if (m_totalDownload == newTotalDownload) + return; + m_totalDownload = newTotalDownload; + emit totalDownloadChanged(); +} + void BlockHeadersChecker::setBytesDownloaded(int count) { if (m_bytesDownloaded == count) diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index 29135d1..3d8a861 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -26,19 +26,44 @@ class BlockHeadersChecker : public QObject { Q_OBJECT Q_PROPERTY(int wantedHeight READ wantedHeight WRITE setWantedHeight NOTIFY wantedHeightChanged FINAL) - Q_PROPERTY(int bytesDownloaded READ bytesDownloaded WRITE setBytesDownloaded NOTIFY bytesDownloadedChanged FINAL) + Q_PROPERTY(int totalDownload READ totalDownload NOTIFY totalDownloadChanged FINAL) + Q_PROPERTY(int bytesDownloaded READ bytesDownloaded NOTIFY bytesDownloadedChanged FINAL) + Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged FINAL) public: BlockHeadersChecker(QObject *parent = nullptr); + enum Status { + Unset, + Finished, + Checking, + Downloading, + Verifying, + + DiskFailure, + NetworkFailure + }; + Q_ENUM(Status) + int wantedHeight() const; void setWantedHeight(int h); + /// the download progress in bytes void setBytesDownloaded(int count); int bytesDownloaded(); + /// the download target in bytes + int totalDownload() const; + void setTotalDownload(int newTotalDownload); + + Status status() const; + void setStatus(Status newStatus); + signals: void wantedHeightChanged(); void bytesDownloadedChanged(); + void totalDownloadChanged(); + + void statusChanged(); private slots: void startChecking(); @@ -50,13 +75,14 @@ private: int m_wantedHeight = 0; int m_checkpoint = 0; // start download at checkpoint-height int m_downloadTo = 0; // download to this point (we have the rest) - + int m_totalDownload = 0; int m_bytesDownloaded = 0; // download progress QNetworkAccessManager m_network; QNetworkReply *m_headerReply = nullptr; QNetworkReply *m_downloadReply = nullptr; QFile *m_newHeaders = nullptr; + Status m_status = Unset; }; #endif -- 2.54.0 From 47f991a36e98905360280719963e778c71067af8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Jun 2024 20:32:45 +0200 Subject: [PATCH 0897/1428] Make the DownloadChecker.qml actually functional. The ImportWalletPage now loads the QML provided by the module, exposed via the metadata of modules. The module gets just a blockheight property and then will do "its thing". This will either instantly close when there is nothing to do and continue instantly to the actual import. Or the module will check the server, initiate download and when all is setup and done THEN close the popup and continue with the actual import. --- guis/Flowee/Progressbar.qml | 100 +++++++++++++++++++ guis/desktop/TransactionInfoSmall.qml | 2 +- guis/mobile/AccountSyncState.qml | 83 +-------------- guis/mobile/ImportWalletPage.qml | 105 ++++++++++++++++++- guis/widgets.qrc | 1 + modules/blocks/BlockHeadersChecker.cpp | 8 +- modules/blocks/BlockHeadersChecker.h | 3 +- modules/blocks/DownloadChecker.qml | 133 +++++++++++++++++++++++-- src/FloweePay.cpp | 9 +- src/FloweePay.h | 3 +- 10 files changed, 343 insertions(+), 104 deletions(-) create mode 100644 guis/Flowee/Progressbar.qml diff --git a/guis/Flowee/Progressbar.qml b/guis/Flowee/Progressbar.qml new file mode 100644 index 0000000..1782ed1 --- /dev/null +++ b/guis/Flowee/Progressbar.qml @@ -0,0 +1,100 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +// import Flowee.org.pay; + +Rectangle { + id: progressbar + height: 40 + color: "#00000000" + border.width: 2 + border.color: palette.midlight + radius: 10 + + // should be calculated to be between 0 and 100 + required property double progress; + + Rectangle { + width: Math.min(10, parent.width * parent.progress) + y: 2 + height: parent.height - 4 + color: palette.highlight + radius: 10 + } + + Rectangle { + id: bar + x: 5 + y: 2 + width: { + var full = progressbar.width; + var w = full * progressbar.progress + w = Math.min(w, full - 5) // the right max + w -= 5; // our X offset + Math.max(0, w); + } + height: parent.height - 4 + color: palette.highlight + clip: true + Label { + id: percentLabel + text: (100 * progressbar.progress).toFixed(0) + "%" + y: 5 + x: progressbar.width / 2 - 10 - width / 2 + color: palette.highlightedText + } + } + Rectangle { + width: { + // just the last 10 pixels. + var full = progressbar.width; + return Math.max(0, full * progressbar.progress - (full - 10)); + } + x: progressbar.width - 10 + y: 2 + height: parent.height - 4 + color: palette.highlight + radius: 10 + } + Rectangle { + id: groove + y: 2 + height: parent.height - 4 + color: palette.light + clip: true + x: { + var full = progressbar.width; + var distance = progressbar.progress * full; + distance = Math.min(distance, full - 5); // and right + distance = Math.max(5, distance); // avoid the left rounding area + return distance; + } + width: { + var full = progressbar.width; + var w = full - progressbar.progress * full; + w -= 5; + w = Math.min(w, full - 10); // the rounded corners + return w; + } + Label { + text: percentLabel.text + y: 5 + x: progressbar.width / 2 - 5 - width / 2 - parent.x + } + } +} diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index 7b102a2..9f265fa 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -80,7 +80,7 @@ Item { return ""; var loc = Qt.locale(); var format = loc.dateTimeFormat(Locale.ShortFormat); - return loc.toString(Pay.blockTime(root.minedHeight), format); + return loc.toString(Pay.timeOfBlockHeight(root.minedHeight), format); } QQC2.ToolTip.delay: 800 diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index b2262ad..dcc912c 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -45,20 +45,13 @@ Item { function onLastBlockSynchedChanged() { checkIfDone(); } } - // The progressbar - Rectangle { + Flowee.Progressbar { id: progressbar - y: 10 // top spacing of widget, only when doing progress report x: 10 + y: 10 width: parent.width - 20 - height: 40 // percentLabel.height + 10 - color: "#00000000" - border.width: 2 - border.color: palette.midlight - radius: 10 visible: !root.uptodate - - property double progress: { + progress: { let startPos = root.startPos; let end = Pay.expectedChainHeight if (startPos == 0) @@ -70,76 +63,6 @@ Item { let ourProgress = currentPos - startPos; return ourProgress / totalDistance; } - - Rectangle { - width: Math.min(10, parent.width * parent.progress) - y: 2 - height: parent.height - 4 - color: palette.highlight - radius: 10 - } - - Rectangle { - id: bar - x: 5 - y: 2 - width: { - var full = progressbar.width; - var w = full * progressbar.progress - w = Math.min(w, full - 5) // the right max - w -= 5; // our X offset - Math.max(0, w); - } - height: parent.height - 4 - color: palette.highlight - clip: true - Flowee.Label { - id: percentLabel - text: (100 * progressbar.progress).toFixed(0) + "%" - y: 5 - x: progressbar.width / 2 - 10 - width / 2 - color: palette.highlightedText - } - } - Rectangle { - width: { - // just the last 10 pixels. - var full = progressbar.width; - return Math.max(0, full * progressbar.progress - (full - 10)); - } - x: progressbar.width - 10 - y: 2 - height: parent.height - 4 - color: palette.highlight - radius: 10 - } - Rectangle { - id: groove - y: 2 - height: parent.height - 4 - color: palette.light - clip: true - x: { - var full = progressbar.width; - var distance = progressbar.progress * full; - distance = Math.min(distance, full - 5); // and right - distance = Math.max(5, distance); // avoid the left rounding area - return distance; - } - width: { - var full = progressbar.width; - var w = full - progressbar.progress * full; - w -= 5; - w = Math.min(w, full - 10); // the rounded corners - return w; - } - Flowee.Label { - text: percentLabel.text - y: 5 - x: progressbar.width / 2 - 5 - width / 2 - parent.x - // color: palette.highlightedText - } - } } Flowee.Label { id: indicator diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 33e105d..2054bf9 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -315,10 +315,21 @@ Page { Layout.alignment: Qt.AlignRight onClicked: { + var bh = 0; + if (privKeyImportHelper.resultCount > 0) + bh = privKeyImportHelper.startHeight(0); + else + bh = Pay.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); + checkBlockchainPopup.oldestBlock = bh; + checkBlockchainPopup.whenDone = startImport; + checkBlockchainPopup.open(); + } + + function startImport() { if (privKeyImportHelper.resultCount > 0) { var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) } else { - var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2010, oldestTransactionChooser.item.month.currentIndex, 1); + var sh = oldestTransactionChooser.item.selectedDate; options = Pay.createImportedWallet(secretText.text, accountName.text, sh) options.forceSingleAddress = singleAddress.checked; } @@ -458,12 +469,25 @@ Page { text: qsTr("Start") anchors.right: parent.right onClicked: { + var bh = 0; + console.log("a: " + oldestTransactionChooser2.item.selectedDate); + console.log("b: " + Pay.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate)); + if (seedImportHelper.resultCount > 0) + bh = seedImportHelper.startHeight(0); + else + bh = Pay.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate); + checkBlockchainPopup.oldestBlock = bh; + checkBlockchainPopup.whenDone = startImport; + checkBlockchainPopup.open(); + } + + function startImport() { if (seedImportHelper.resultCount > 0) { var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), seedImportHelper.isElectrumSeed(0)); } else { - var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2010, oldestTransactionChooser2.item.month.currentIndex, 1); + var sh = oldestTransactionChooser2.item.selectedDate options = Pay.createImportedHDWallet(secretText.text, passwordField.text, derivationPath.text, accountName2.text, sh); } @@ -483,14 +507,87 @@ Page { Behavior on x { NumberAnimation { } } } - Item { - // non-gui items below. + Item { // non-(default) visible items below. + + + /* + * The users wallet may not actually have the old headers on the device because the + * user never needed them. So to make the import work, we first sill need to actually + * download the missing headers. Use the block-headers checker (from the module 'blocks') + * to verify and initiate this. + */ + QQC2.Popup { + id: checkBlockchainPopup + + property int oldestBlock: 0 + property var whenDone: null + + width: importAccount.width - 20 + height: checkerLoader.height + 20 + + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + modal: true + closePolicy: QQC2.Popup.NoAutoClose + Loader { + id: checkerLoader + width: parent.width - 20 + height: { + if (item == null) + return 100; + return item.implicitHeight + } + + onLoaded: { + closeMonitor.target = item; + // console.log("Loading completed, start checking on height: " + checkBlockchainPopup.oldestBlock); + item.wantedHeight = checkBlockchainPopup.oldestBlock + } + Connections { + id: closeMonitor + function onVisibleChanged() { + let i = checkerLoader.item; + if (i == null) // shouldn't really happen.. + return; + let visible = i.visible; + if (!visible) { + closeMonitor.target = null; + checkerLoader.source = ""; + checkBlockchainPopup.close(); + // call the callback to initiate the actual import. + checkBlockchainPopup.whenDone(); + } + } + } + } + + onVisibleChanged: { + if (!visible) + return; + + // Shipped in module 'blocks' we invoke the 'checker', should it exist. + var section = ModuleManager.sectionOnPlugin("blocks", "checker"); + if (section !== null) + checkerLoader.source = section.qml; // This loads the plugin. + else + close(); + } + } + Component { id: oldestTransactionChooser_component Flow { property alias month: inner_month property alias year: inner_year + property date selectedDate: { + return new Date(inner_year.currentIndex + 2010, inner_month.currentIndex, 1); + } + width: parent.width spacing: 10 Flowee.ComboBox { diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 86dcfd3..d8c9ad7 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -57,5 +57,6 @@ Flowee/QRScanner.qml Flowee/AddressInfoWidget.qml Flowee/FiatTxInfo.qml + Flowee/Progressbar.qml diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index d092a35..8d0ba49 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -49,8 +49,8 @@ void BlockHeadersChecker::setWantedHeight(int h) void BlockHeadersChecker::startChecking() { logInfo() << "starting" << m_wantedHeight; - if (FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) { - setStatus(Finished); + if (m_wantedHeight == 0 || FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) { + setStatus(NoDownloadNeeded); return; } setStatus(Checking); @@ -64,7 +64,7 @@ void BlockHeadersChecker::startChecking() } if (have) { logDebug() << "Nothing to do, the blockchain has the needed headers"; - setStatus(Finished); + setStatus(NoDownloadNeeded); return; } @@ -211,7 +211,7 @@ void BlockHeadersChecker::downloadFinished() m_newHeaders->remove(); } m_newHeaders->close(); - setStatus(Finished); + setStatus(Success); } void BlockHeadersChecker::onBytesDownloaded() diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index 3d8a861..ed4f014 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -34,10 +34,11 @@ public: enum Status { Unset, - Finished, + NoDownloadNeeded, Checking, Downloading, Verifying, + Success, DiskFailure, NetworkFailure diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml index ecfa39d..cf87025 100644 --- a/modules/blocks/DownloadChecker.qml +++ b/modules/blocks/DownloadChecker.qml @@ -21,23 +21,134 @@ import "../Flowee" as Flowee import "../mobile"; import Flowee.org.pay.blocks as Blocks; -Page { +/** + * This QML is used as a plugin from the main app, with the main goal to check if + * blocks need to be doawnloaded. + * + * To start the process, set the wantedHeight property to non zero. + * When we are done, we will hide ourselves (set visible to false) + */ +Item { id: root - headerText: qsTr("Blocks test") + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: { + var h = titleLabel.height; + if (busyIndicator.visible) + h += busyIndicator.height + 10 + if (errorLabel.visible) + h += errorLabel.height + 6; + if (downloadProgress.visible) + h += downloadProgress.height + 6; - property int wantedHeight: 0 + return h; + } - Item { - id: data - Blocks.Checker { - id: testData - wantedHeight: root.wantedHeight + /// the blockheight that is needed by the caller. + property alias wantedHeight: testData.wantedHeight + + Blocks.Checker { + // This object is representing the CPP class BlockHeadersChecker + id: testData + + onStatusChanged: { + if (status == Blocks.Checker.NoDownloadNeeded || status == Blocks.Checker.Success) + root.visible = false; } } Flowee.Label { - anchors.centerIn: parent - text: testData.bla + id: titleLabel + text: qsTr("Fetching Headers") + anchors.horizontalCenter: parent.horizontalCenter } -} + Item { + id: busyIndicator + width: 50 + height: 50 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: titleLabel.bottom + anchors.topMargin: 10 + visible: testData.status === Blocks.Checker.Checking || testData.status === Blocks.Checker.Downloading + || testData.status === Blocks.Checker.Verifying + + Repeater { + model: 6 + Item { + x: busyIndicator.width / 2 + y: busyIndicator.height / 2 + width: 12 + height: 1 + rotation: index * 360 / 6 + transformOrigin: Item.TopLeft + Behavior on width { NumberAnimation { } } + onVisibleChanged: width = visible ? 12 : 16 + + Rectangle { + x: parent.width + y: - height / 2 + width: 14 + height: 14 + radius: 7 + color: palette.text + } + } + } + + Behavior on rotation { NumberAnimation { id: animator } } + Timer { + running: parent.visible + repeat: true + interval: 400 + onTriggered: { + if (busyIndicator.rotation < 1) { + // first run-through + animator.duration = interval; + busyIndicator.rotation = 59; + } + else { + // this jumps back. + animator.duration = 0; + busyIndicator.rotation = 0; + // and then does the animation again. + animator.duration = interval; + busyIndicator.rotation = 59; + } + } + } + } + + Flowee.Label { + id: errorLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: titleLabel.bottom + anchors.topMargin: 6 + text: "Disk related Failure" // honestly so rare that it makes no sense to translate. + visible: testData.status == Blocks.Checker.DiskFailure + } + + Flowee.Progressbar { + id: downloadProgress + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: busyIndicator.bottom + anchors.topMargin: 6 + opacity: testData.status === Blocks.Checker.Downloading ? 1 : 0 + visible: opacity > 0 + progress: { + let total = testData.totalDownload; + if (total === 0) // not started yet. + return 0; + let cur = testData.bytesDownloaded; + + return cur / total; + } + Behavior on opacity { OpacityAnimator { } } + } + + /* + // TODO handle network error and offer a 'retry' button. + NetworkFailure + */ +} diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1e72551..293be78 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -712,7 +712,7 @@ QString FloweePay::formatDateTime(QDateTime date) const return date.toString(format); } -QDateTime FloweePay::blockTime(int blockHeight) const +QDateTime FloweePay::timeOfBlockHeight(int blockHeight) const { assert(blockHeight > 0); // wrap this for convenience and also ensure that we never return an insanely old @@ -721,9 +721,14 @@ QDateTime FloweePay::blockTime(int blockHeight) const m_downloadManager->blockchain().block(blockHeight).nTime)); } +int FloweePay::heightOfBlockAtTime(QDateTime time) const +{ + return m_downloadManager->blockchain().blockHeightAtTime(time.toSecsSinceEpoch()) - 1; +} + QString FloweePay::formatBlockTime(int blockHeight) const { - return formatDateTime(blockTime(blockHeight)); + return formatDateTime(timeOfBlockHeight(blockHeight)); } Wallet *FloweePay::createWallet(const QString &name) diff --git a/src/FloweePay.h b/src/FloweePay.h index 8663776..cab38ae 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -160,7 +160,8 @@ public: * Returns the formatDateTime() formatted date of the blocks timestamp. */ Q_INVOKABLE QString formatBlockTime(int blockHeight) const; - Q_INVOKABLE QDateTime blockTime(int blockHeight) const; + Q_INVOKABLE QDateTime timeOfBlockHeight(int blockHeight) const; + Q_INVOKABLE int heightOfBlockAtTime(QDateTime time) const; /// create a new HD wallet with an optional name. Q_INVOKABLE NewWalletConfig* createNewWallet(const QString &derivationPath, const QString &password = QString(), const QString &walletName = QString()); -- 2.54.0 From 70055070413bc59f32a75d7fcf131496e7c19b3b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 22 Jun 2024 23:41:54 +0200 Subject: [PATCH 0898/1428] Help resolve blockheight This is the import page, it will certainly be possible for a user to import a wallet that is older than the headers on their device. In that case using the headers to resolve the height can't work. Circular dependency: Need headers to know which headers to download... So, we hardcode the historical blockheights here for each month that the user can select. Notice that the dates are the first of each month, at the UTC-16 time-line. --- guis/mobile/ImportWalletPage.qml | 55 +++++++++++++++++++++++++++----- src/FloweePay.cpp | 9 +++++- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 2054bf9..92fb1f7 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -22,7 +22,7 @@ import "../Flowee" as Flowee import Flowee.org.pay Page { - id: importAccount + id: root headerText: qsTr("Import Wallet") states: [ @@ -71,6 +71,44 @@ Page { } } + function heightOfBlockAtTime(dateTime) { + let height = Pay.heightOfBlockAtTime(dateTime); + // This is the import page, an import can start before the + // checkpoint we have, and without headers the above method will + // not actually resolve. + if (height <= 0) { // then we need to resolve it ourselves. + // block-heights is one entry per month, first entry is Jan 2011 + let heights = [ + 100406, 105564, 111133, 115516, 120731, 127374, 133753, + 138539, 142965, 147309, 150955, 155150, 159601, 164289, + 168947, 173332, 177653, 182030, 186680, 191257, 196146, + 200982, 205472, 210052, 214090, 218495, 223659, 228486, + 233645, 238422, 243833, 248955, 254735, 260648, 266613, + 272024, 277464, 282948, 288364, 292981, 298197, 303092, + 308376, 312919, 317984, 322972, 327423, 332040, 336384, + 340917, 345605, 349713, 354152, 358436, 362974, 367405, + 371964, 376579, 380994, 385802, 390708, 395508, 400464, + 404739, 409332, 413834, 418421, 422658, 427282, 431993, + 436359, 441045, 445568, 450483, 455193, 459389, 463935, + 468621, 473312, 478024, 484018, 489925, 499847, 506195, + 510496, 514976, 519421, 523442, 527942, 532251, 536702, + 541001, 545452, 549912, 554201, 558596, 562898, 567326, + 571781, 575836, 580290, 584604, 589044, 593355, 597824, + 602298, 606618, 611066, 615384, 619871, 624312, 628475, + 632848, 637166, 641633, 645955, 650396, 654855, 659133, + 663626, 667895, 672364, 676770, 680773, 685462, 689903, + 694154, 698312, 702882, 707322, 711585, 716117, 720443, + 724876, 729367, 733391, 737876, 742106, 746494, 750878, + 755311, 759815 + ]; + let baseline = new Date('2011-01-01'); + let index = (dateTime.getYear() - baseline.getYear()) * 12 + dateTime.getMonth() + 1; + + height = heights[index - 1]; + } + return height; + } + Column { id: entryPage width: parent.width @@ -79,7 +117,7 @@ Page { property var typedData: Pay.identifyString(secretText.totalText) property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic || typedData === Wallet.ElectrumMnemonic - onFinishedChanged: importAccount.toNextPage(); + onFinishedChanged: root.toNextPage(); PageTitledBox { id: buttonsBox title: qsTr("Select import method") @@ -198,7 +236,7 @@ Page { text: qsTr("Next") visible: parent.finished - onClicked: importAccount.toNextPage(); + onClicked: root.toNextPage(); } Behavior on x { NumberAnimation { } } @@ -224,7 +262,7 @@ Page { // this shows the bitcoincash address matching the private key font.pixelSize: singleAddress.font.pixelSize * 0.9 text: { - if (importAccount.state !== "privKeyDetailsPage") + if (root.state !== "privKeyDetailsPage") return ""; return Pay.addressForPrivKey(secretText.text); } @@ -319,7 +357,8 @@ Page { if (privKeyImportHelper.resultCount > 0) bh = privKeyImportHelper.startHeight(0); else - bh = Pay.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); + bh = root.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); + checkBlockchainPopup.oldestBlock = bh; checkBlockchainPopup.whenDone = startImport; checkBlockchainPopup.open(); @@ -329,8 +368,8 @@ Page { if (privKeyImportHelper.resultCount > 0) { var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) } else { - var sh = oldestTransactionChooser.item.selectedDate; - options = Pay.createImportedWallet(secretText.text, accountName.text, sh) + var height = root.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); + options = Pay.createImportedWallet(secretText.text, accountName.text, height) options.forceSingleAddress = singleAddress.checked; } @@ -522,7 +561,7 @@ Page { property int oldestBlock: 0 property var whenDone: null - width: importAccount.width - 20 + width: root.width - 20 height: checkerLoader.height + 20 background: Rectangle { diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 293be78..1e7bbe7 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -723,7 +723,14 @@ QDateTime FloweePay::timeOfBlockHeight(int blockHeight) const int FloweePay::heightOfBlockAtTime(QDateTime time) const { - return m_downloadManager->blockchain().blockHeightAtTime(time.toSecsSinceEpoch()) - 1; + try { + return m_downloadManager->blockchain().blockHeightAtTime(time.toSecsSinceEpoch()) - 1; + } catch (...) { + // when there is no blockheader available at that time, the p2p lib throws, + // to avoid an uncaught exception shutting down the application we catch it and + // return an invalid number instead. + return -1; + } } QString FloweePay::formatBlockTime(int blockHeight) const -- 2.54.0 From a4d394aca13ee2d59357c266a341095007e10358 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 23 Jun 2024 16:41:20 +0200 Subject: [PATCH 0899/1428] Re-order and cleanup the import (seed) page. This makes the language simpler and makes clear that the password field is related to the import. Second, the name-field is now moved closer to the big 'start' button and has a more obvious title. --- guis/mobile/ImportWalletPage.qml | 267 +++++++++++++++---------------- 1 file changed, 129 insertions(+), 138 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 92fb1f7..42dd564 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -276,22 +276,6 @@ Page { checked: true } - PageTitledBox { - title: qsTr("Name") - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } - Flowee.TextField { - id: accountName - width: parent.width - } - } - PageTitledBox { title: qsTr("Oldest Transaction") Item { @@ -347,6 +331,22 @@ Page { visible: false } + PageTitledBox { + title: qsTr("New Wallet Name") + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + width: parent.width + } + } + Flowee.BigButton { id: privImportStartButton text: qsTr("Start") @@ -397,8 +397,91 @@ Page { function takeFocus() { seedCheckButton.forceActiveFocus(); } + PageTitledBox { - title: qsTr("Name") + title: qsTr("Password") + width: parent.width + Flowee.TextField { + id: passwordField + width: parent.width + placeholderText: qsTr("imported wallet password") + } + } + + Flowee.BigButton { + id: seedCheckButton + text: qsTr("Discover Details", "online check for wallet details") + enabled: !seedImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + seedImportHelper.secretType = entryPage.typedData + seedImportHelper.secret = secretText.text + seedImportHelper.password = passwordField.text + } + ImportHelper { + id: seedImportHelper + onCheckingChanged: { + if (checking) + return; + emptySeedWarningLabel.visible = false; + if (resultCount === 0) { // empty + seedCheckButton.isMainButton = false; + emptySeedWarningLabel.visible = true; + } + else if (resultCount >= 1) { + // TODO what to do if there are more then 1? + + let height = startHeight(0); + oldestTransactionChooser2.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser2.item.year.currentIndex + = yearOnHeight(height) - 2010; + oldestTransactionChooser2.item.enabled = false; + derivationPath.text = derivation(0); + derivationPath.enabled = false; + + seedCheckButton.isMainButton = false; + seedStartButton.isMainButton = true; + } + } + } + + // TODO add warning + } + + PageTitledBox { + title: qsTr("Oldest Transaction") + width: parent.width + Loader { + id: oldestTransactionChooser2 + width: parent.width + sourceComponent: oldestTransactionChooser_component + } + } + PageTitledBox { + id: derivationLabel + title: qsTr("Derivation Path") + width: parent.width + Flowee.TextField { + id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); + width: parent.width + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" + } + } + Flowee.Label { + id: emptySeedWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for seed") + visible: false + width: parent.width + } + + PageTitledBox { + title: qsTr("New Wallet Name") width: parent.width visible: { // don't ask for a name when the user imports a @@ -414,132 +497,40 @@ Page { } } - PageTitledBox { - title: qsTr("Password") - width: parent.width - Flowee.TextField { - id: passwordField - width: parent.width - placeholderText: qsTr("Optional") + Flowee.BigButton { + id: seedStartButton + text: qsTr("Start") + anchors.right: parent.right + onClicked: { + var bh = 0; + if (seedImportHelper.resultCount > 0) + bh = seedImportHelper.startHeight(0); + else + bh = root.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate); + checkBlockchainPopup.oldestBlock = bh; + checkBlockchainPopup.whenDone = startImport; + checkBlockchainPopup.open(); } - } - PageTitledBox { - title: qsTr("Details") - width: parent.width - Item { - implicitWidth: parent.width - implicitHeight: seedCheckButton.height - Flowee.BigButton { - id: seedCheckButton - text: qsTr("Lookup Details", "online check for wallet details") - enabled: !seedImportHelper.checking - isMainButton: true; + function startImport() { + if (seedImportHelper.resultCount > 0) { + var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), + seedImportHelper.isElectrumSeed(0)); + } else { + var height = root.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate); + options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, height); + } - onClicked: { - // setting new values here will start the check. - seedImportHelper.secretType = entryPage.typedData - seedImportHelper.secret = secretText.text - seedImportHelper.password = passwordField.text + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; } - ImportHelper { - id: seedImportHelper - onCheckingChanged: { - if (checking) - return; - emptySeedWarningLabel.visible = false; - if (resultCount === 0) { // empty - seedCheckButton.isMainButton = false; - emptySeedWarningLabel.visible = true; - } - else if (resultCount >= 1) { - // TODO what to do if there are more then 1? - - let height = startHeight(0); - oldestTransactionChooser2.item.month.currentIndex - = monthOnHeight(height) - 1; - oldestTransactionChooser2.item.year.currentIndex - = yearOnHeight(height) - 2010; - oldestTransactionChooser2.item.enabled = false; - derivationPath.text = derivation(0); - derivationPath.enabled = false; - - seedCheckButton.isMainButton = false; - seedStartButton.isMainButton = true; - } - } - } - - // TODO add warning - } - } - - PageTitledBox { - id: derivationLabel - title: qsTr("Derivation") - width: parent.width - Flowee.TextField { - id: derivationPath - property bool derivationOk: Pay.checkDerivation(text); - width: parent.width - text: "m/44'/0'/0'" // What most BCH wallets are created with - color: derivationOk ? palette.text : "red" - } - } - PageTitledBox { - title: qsTr("Oldest Transaction") - width: parent.width - Loader { - id: oldestTransactionChooser2 - width: parent.width - sourceComponent: oldestTransactionChooser_component - } - } - Flowee.Label { - id: emptySeedWarningLabel - color: mainWindow.errorRed - text: qsTr("Nothing found for seed") - visible: false - width: parent.width - } - - Flowee.BigButton { - id: seedStartButton - text: qsTr("Start") - anchors.right: parent.right - onClicked: { - var bh = 0; - console.log("a: " + oldestTransactionChooser2.item.selectedDate); - console.log("b: " + Pay.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate)); - if (seedImportHelper.resultCount > 0) - bh = seedImportHelper.startHeight(0); - else - bh = Pay.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate); - checkBlockchainPopup.oldestBlock = bh; - checkBlockchainPopup.whenDone = startImport; - checkBlockchainPopup.open(); - } - - function startImport() { - if (seedImportHelper.resultCount > 0) { - var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), - seedImportHelper.isElectrumSeed(0)); - } else { - var sh = oldestTransactionChooser2.item.selectedDate - options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, sh); - } - - for (let a of portfolio.accounts) { - if (a.id === options.accountId) { - portfolio.current = a; - break; - } - } - thePile.pop(); - thePile.pop(); } + thePile.pop(); + thePile.pop(); } } -- 2.54.0 From 7ee064dd59e772122190276410d9a47063a3b5a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 23 Jun 2024 16:41:34 +0200 Subject: [PATCH 0900/1428] Add assert detecting misuse. --- modules/blocks/BlockHeadersChecker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 8d0ba49..855ae75 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -49,6 +49,7 @@ void BlockHeadersChecker::setWantedHeight(int h) void BlockHeadersChecker::startChecking() { logInfo() << "starting" << m_wantedHeight; + assert(m_checkpoint >= 0); if (m_wantedHeight == 0 || FloweePay::instance()->p2pNet()->chain() != P2PNet::MainChain) { setStatus(NoDownloadNeeded); return; -- 2.54.0 From 40dce2a91b46a6be8dbfe937b94f8f9b1875d563 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 23 Jun 2024 16:43:46 +0200 Subject: [PATCH 0901/1428] Minor text improvement. --- modules/blocks/DownloadChecker.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml index cf87025..d878411 100644 --- a/modules/blocks/DownloadChecker.qml +++ b/modules/blocks/DownloadChecker.qml @@ -59,7 +59,7 @@ Item { Flowee.Label { id: titleLabel - text: qsTr("Fetching Headers") + text: qsTr("Downloading Headers") anchors.horizontalCenter: parent.horizontalCenter } @@ -124,7 +124,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter anchors.top: titleLabel.bottom anchors.topMargin: 6 - text: "Disk related Failure" // honestly so rare that it makes no sense to translate. + text: "Disk related failure" // honestly so rare that it makes no sense to translate. visible: testData.status == Blocks.Checker.DiskFailure } -- 2.54.0 From 074c79d0427c944ae7bf6391161e6ef18d1bbaa9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 23 Jun 2024 19:25:34 +0200 Subject: [PATCH 0902/1428] Change overwrite policy of static headers (Android) As we start to ship headers based on a checkpoint, the earlier code to overwrite an existing static-headers file when the new delivable is larger is no longer useful. This is because just being larger doesn't mean the checkpoint it extends will stay the same. We simply won't overwrite a static headers file anymore. --- src/main_utils_android.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 956bd33..5d95959 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -75,7 +75,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) QFileInfo orig("assets:/blockheaders"); const QString infoFilePath = target.absoluteFilePath() + ".info"; - bool install = orig.exists() && (!target.exists() || orig.size() > target.size()); + bool install = orig.exists() && !target.exists(); if (install) { // make sure we have a local copy QFile::remove(infoFilePath); -- 2.54.0 From 92be995fddfa8a77e10e7d05a57dc05e78a61412 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 23 Jun 2024 19:55:11 +0200 Subject: [PATCH 0903/1428] Hide 'blocks' module from user for now There isn't yet any user interface they can see, so no point in showing it to the user in the 'explore' part of the app. --- guis/mobile/ExploreModules.qml | 3 ++- modules/blocks/BlocksModuleInfo.cpp | 2 ++ src/ModuleInfo.cpp | 13 ++++++++++++- src/ModuleInfo.h | 15 ++++++++++----- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 16a6563..c558dbb 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,6 +48,7 @@ Page { color: palette.alternateBase border.width: 1 border.color: palette.midlight + visible: modelData.hasUISections // has user-visible parts. Flowee.Label { id: titleLabel diff --git a/modules/blocks/BlocksModuleInfo.cpp b/modules/blocks/BlocksModuleInfo.cpp index ec460a6..0fc5698 100644 --- a/modules/blocks/BlocksModuleInfo.cpp +++ b/modules/blocks/BlocksModuleInfo.cpp @@ -28,11 +28,13 @@ ModuleInfo * BlocksModuleInfo::build() info->setDescription(tr("Everything about blocks")); info->setIconSource("qrc:/blocks/blocks.svg"); +/* Not usable yet. auto menuSection = new ModuleSection(ModuleSection::MainMenuItem, info); menuSection->setText(tr("Blocks")); menuSection->setSubtext(tr("History of our chain")); menuSection->setStartQMLFile("qrc:/blocks/Configuration.qml"); info->addSection(menuSection); +*/ // The custom section that is used by the main app to check if // the blockchain currently available is should be expanded. diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 10ddd6d..2ff1957 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -84,6 +84,17 @@ QString ModuleInfo::iconSource() const return m_iconSource; } +bool ModuleInfo::hasUISections() const +{ + for (auto section : m_sections) { + if (section->isMainMenuMethod()) + return true; + if (section->isSendMethod()) + return true; + } + return false; +} + void ModuleInfo::setIconSource(const QString &newIconSource) { m_iconSource = newIconSource; diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 922b52a..5423474 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -33,11 +33,13 @@ class ModuleInfo : public QObject { Q_OBJECT - Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) - Q_PROPERTY(QString title READ title CONSTANT) - Q_PROPERTY(QString description READ description CONSTANT) - Q_PROPERTY(QList sections READ sections CONSTANT) - Q_PROPERTY(QString iconSource READ iconSource CONSTANT) + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged FINAL) + /// returns true if there is at least one section that is user-visible + Q_PROPERTY(bool hasUISections READ hasUISections CONSTANT FINAL) + Q_PROPERTY(QString title READ title CONSTANT FINAL) + Q_PROPERTY(QString description READ description CONSTANT FINAL) + Q_PROPERTY(QList sections READ sections CONSTANT FINAL) + Q_PROPERTY(QString iconSource READ iconSource CONSTANT FINAL) public: explicit ModuleInfo(QObject *parent = nullptr); @@ -75,6 +77,9 @@ public: void setIconSource(const QString &newIconSource); QString iconSource() const; + /// returns true if there is at least one section that is user-visible + bool hasUISections() const; + signals: void enabledChanged(); -- 2.54.0 From 7ba275fd535c89080517127e48cd5883801e2ef3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Jun 2024 22:33:53 +0200 Subject: [PATCH 0904/1428] On resolve start-date, show it in the UI We use the same header-heights lookup table to show the actual month to the user on resolve. The resolve is the fetch of first-use of an address or seed, this returns a blockheight from elecrum servers only and that isn't very user-friendly. As such this now fills the comboboxes with the proper month/year for better understanding. --- guis/mobile/ImportWalletPage.qml | 96 ++++++++++++++++++++------------ src/QMLImportHelper.cpp | 5 +- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 42dd564..0b97866 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -52,8 +52,8 @@ Page { } } backHandler: function handler() { - // we practically have two pages inside this on Page object, we should make the 'back' - // button (header of Page) be aware of this. + // we practically have two or 3 pages inside this on Page object, we should make the 'back' + // button be aware of this. if (state !== "entryPage") state = "entryPage"; else @@ -71,6 +71,32 @@ Page { } } + function historicalHeights() { + return [ + 100406, 105564, 111133, 115516, 120731, 127374, 133753, + 138539, 142965, 147309, 150955, 155150, 159601, 164289, + 168947, 173332, 177653, 182030, 186680, 191257, 196146, + 200982, 205472, 210052, 214090, 218495, 223659, 228486, + 233645, 238422, 243833, 248955, 254735, 260648, 266613, + 272024, 277464, 282948, 288364, 292981, 298197, 303092, + 308376, 312919, 317984, 322972, 327423, 332040, 336384, + 340917, 345605, 349713, 354152, 358436, 362974, 367405, + 371964, 376579, 380994, 385802, 390708, 395508, 400464, + 404739, 409332, 413834, 418421, 422658, 427282, 431993, + 436359, 441045, 445568, 450483, 455193, 459389, 463935, + 468621, 473312, 478024, 484018, 489925, 499847, 506195, + 510496, 514976, 519421, 523442, 527942, 532251, 536702, + 541001, 545452, 549912, 554201, 558596, 562898, 567326, + 571781, 575836, 580290, 584604, 589044, 593355, 597824, + 602298, 606618, 611066, 615384, 619871, 624312, 628475, + 632848, 637166, 641633, 645955, 650396, 654855, 659133, + 663626, 667895, 672364, 676770, 680773, 685462, 689903, + 694154, 698312, 702882, 707322, 711585, 716117, 720443, + 724876, 729367, 733391, 737876, 742106, 746494, 750878, + 755311, 759815 + ]; + } + function heightOfBlockAtTime(dateTime) { let height = Pay.heightOfBlockAtTime(dateTime); // This is the import page, an import can start before the @@ -78,37 +104,39 @@ Page { // not actually resolve. if (height <= 0) { // then we need to resolve it ourselves. // block-heights is one entry per month, first entry is Jan 2011 - let heights = [ - 100406, 105564, 111133, 115516, 120731, 127374, 133753, - 138539, 142965, 147309, 150955, 155150, 159601, 164289, - 168947, 173332, 177653, 182030, 186680, 191257, 196146, - 200982, 205472, 210052, 214090, 218495, 223659, 228486, - 233645, 238422, 243833, 248955, 254735, 260648, 266613, - 272024, 277464, 282948, 288364, 292981, 298197, 303092, - 308376, 312919, 317984, 322972, 327423, 332040, 336384, - 340917, 345605, 349713, 354152, 358436, 362974, 367405, - 371964, 376579, 380994, 385802, 390708, 395508, 400464, - 404739, 409332, 413834, 418421, 422658, 427282, 431993, - 436359, 441045, 445568, 450483, 455193, 459389, 463935, - 468621, 473312, 478024, 484018, 489925, 499847, 506195, - 510496, 514976, 519421, 523442, 527942, 532251, 536702, - 541001, 545452, 549912, 554201, 558596, 562898, 567326, - 571781, 575836, 580290, 584604, 589044, 593355, 597824, - 602298, 606618, 611066, 615384, 619871, 624312, 628475, - 632848, 637166, 641633, 645955, 650396, 654855, 659133, - 663626, 667895, 672364, 676770, 680773, 685462, 689903, - 694154, 698312, 702882, 707322, 711585, 716117, 720443, - 724876, 729367, 733391, 737876, 742106, 746494, 750878, - 755311, 759815 - ]; let baseline = new Date('2011-01-01'); let index = (dateTime.getYear() - baseline.getYear()) * 12 + dateTime.getMonth() + 1; - height = heights[index - 1]; + height = historicalHeights()[index - 1]; } return height; } + + function dateOnHeight(height) { + // This class, in contrary to the similar method on the Pay class, returns -1 if + // the client doesn't have the headers available to do the lookup. + var m = seedImportHelper.monthOnHeight(height); + if (m === -1) { + // use our lookup table to select the historical month / year. + let heights = historicalHeights(); + for (var index = 0; index < heights.length; ++index) { + if (heights[index] > height) + break; + } + m = index % 12; + var y = 2011 + Math.floor(index / 12); + } + else { + y = seedImportHelper.yearOnHeight(height); + } + + return { + month: m, + year: y + }; + } + Column { id: entryPage width: parent.width @@ -303,11 +331,9 @@ Page { emptyPrivKeyWarningLabel.visible = true; } else if (resultCount === 1) { - let height = startHeight(0); - oldestTransactionChooser.item.month.currentIndex - = monthOnHeight(height) - 1; - oldestTransactionChooser.item.year.currentIndex - = yearOnHeight(height) - 2010; + let date = root.dateOnHeight(startHeight(0)); + oldestTransactionChooser.item.month.currentIndex = date.month - 1; + oldestTransactionChooser.item.year.currentIndex = date.year - 2010; oldestTransactionChooser.item.enabled = false; privImportStartButton.isMainButton = true; @@ -433,11 +459,9 @@ Page { else if (resultCount >= 1) { // TODO what to do if there are more then 1? - let height = startHeight(0); - oldestTransactionChooser2.item.month.currentIndex - = monthOnHeight(height) - 1; - oldestTransactionChooser2.item.year.currentIndex - = yearOnHeight(height) - 2010; + let date = root.dateOnHeight(startHeight(0)); + oldestTransactionChooser2.item.month.currentIndex = date.month - 1; + oldestTransactionChooser2.item.year.currentIndex = date.year - 2010; oldestTransactionChooser2.item.enabled = false; derivationPath.text = derivation(0); derivationPath.enabled = false; diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp index ee7c4d3..2e36539 100644 --- a/src/QMLImportHelper.cpp +++ b/src/QMLImportHelper.cpp @@ -45,6 +45,7 @@ void QMLImportHelper::checkFinished() assert(m_checking); if (m_importHandler->errored()) { logWarning() << "Import lookup failed, lets punish host and retry"; + logInfo() << " host to punish:" << m_importHandler->service(); FloweePay::instance()->indexerServices()->punish(m_importHandler->service(), 60); m_checking = false; // to lure startCheck() into a sense of normalcy startCheck(); @@ -214,7 +215,7 @@ int QMLImportHelper::monthOnHeight(int height) const QDateTime date = QDateTime::fromSecsSinceEpoch(block.nTime); return date.date().month(); } catch (...) { - return 0; + return -1; } } @@ -226,6 +227,6 @@ int QMLImportHelper::yearOnHeight(int height) const QDateTime date = QDateTime::fromSecsSinceEpoch(block.nTime); return date.date().year(); } catch (...) { - return 0; + return -1; } } -- 2.54.0 From 74b387e43e7590bb5e1d4577d5aa02534d80d058 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Jun 2024 10:05:36 +0200 Subject: [PATCH 0905/1428] install desktop file without x flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c03b31..030f4cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,7 +318,7 @@ if (build_pay_tools) install(TARGETS blockheaders-meta-extractor DESTINATION bin) endif() -install(PROGRAMS guis/desktop/org.flowee.pay.desktop DESTINATION share/applications) +install(FILES guis/desktop/org.flowee.pay.desktop DESTINATION share/applications) set (ICONIN guis/desktop/icons/hicolor/) set (ICONOUT share/icons/hicolor/) install(FILES ${ICONIN}16x16/apps/org.flowee.pay.png DESTINATION ${ICONOUT}16x16/apps) -- 2.54.0 From 9bbc82686b0314dfe7032f665e2af8b9cbd228bd Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 14 Jun 2024 20:36:29 +0200 Subject: [PATCH 0906/1428] Add helpful debug message --- src/main_utils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 245401e..989c2c0 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -131,6 +131,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) + (cld->chain == P2PNet::MainChain ? "/blockheaders" : "/blockheaders-testnet4"))); else blockheaders.reset(new QFile(info.absoluteFilePath())); + logCritical() << "Overriding static-headers, using:" << blockheaders->fileName(); } else { // do not load if pointing to invalid path. -- 2.54.0 From e2d1251fc35ee29c57f30c8e8b5732946957b797 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 17:54:19 +0200 Subject: [PATCH 0907/1428] Move the UI to allow imports only from 2011 --- guis/mobile/ImportWalletPage.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 0b97866..18a609e 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -124,7 +124,8 @@ Page { if (heights[index] > height) break; } - m = index % 12; + index = index - 1; + m = index % 12 + 1; var y = 2011 + Math.floor(index / 12); } else { @@ -333,7 +334,7 @@ Page { else if (resultCount === 1) { let date = root.dateOnHeight(startHeight(0)); oldestTransactionChooser.item.month.currentIndex = date.month - 1; - oldestTransactionChooser.item.year.currentIndex = date.year - 2010; + oldestTransactionChooser.item.year.currentIndex = date.year - 2011; oldestTransactionChooser.item.enabled = false; privImportStartButton.isMainButton = true; @@ -461,7 +462,7 @@ Page { let date = root.dateOnHeight(startHeight(0)); oldestTransactionChooser2.item.month.currentIndex = date.month - 1; - oldestTransactionChooser2.item.year.currentIndex = date.year - 2010; + oldestTransactionChooser2.item.year.currentIndex = date.year - 2011; oldestTransactionChooser2.item.enabled = false; derivationPath.text = derivation(0); derivationPath.enabled = false; @@ -639,7 +640,7 @@ Page { property alias month: inner_month property alias year: inner_year property date selectedDate: { - return new Date(inner_year.currentIndex + 2010, inner_month.currentIndex, 1); + return new Date(inner_year.currentIndex + 2011, inner_month.currentIndex, 1); } width: parent.width @@ -661,7 +662,7 @@ Page { model: { var list = []; let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { + for (let i = 2011; i <= last; ++i) { list.push(i); } return list; -- 2.54.0 From 1bcabee85eae10e1c43c5d023002d3f5bfb73480 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 16:45:01 +0200 Subject: [PATCH 0908/1428] Add a basic info screen to the 'blocks' module. --- modules/blocks/BlocksDataProvider.cpp | 62 +++++++++++++++++++++++++++ modules/blocks/BlocksDataProvider.h | 56 ++++++++++++++++++++++++ modules/blocks/BlocksModuleInfo.cpp | 2 + modules/blocks/CMakeLists.txt | 1 + modules/blocks/Configuration.qml | 33 ++++++++++++-- 5 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 modules/blocks/BlocksDataProvider.cpp create mode 100644 modules/blocks/BlocksDataProvider.h diff --git a/modules/blocks/BlocksDataProvider.cpp b/modules/blocks/BlocksDataProvider.cpp new file mode 100644 index 0000000..7398e9e --- /dev/null +++ b/modules/blocks/BlocksDataProvider.cpp @@ -0,0 +1,62 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "BlocksDataProvider.h" + +#include + + +DataSource::DataSource(const Blockchain::DataSource &ds, QObject *parent) + : QObject(parent), + m_fromStaticFile(ds.source == Blockchain::StaticHeadersSource), + m_startBlock(ds.startBlock), + m_endBlock(ds.endBlock) +{ +} + +bool DataSource::fromStaticFile() const +{ + return m_fromStaticFile; +} + +int DataSource::startBlock() const +{ + return m_startBlock; +} + +int DataSource::endBlock() const +{ + return m_endBlock; +} + + +// ----------------------------------------------------------- + +BlocksDataProvider::BlocksDataProvider(QObject *parent) + : QObject{parent} +{ + auto sources = FloweePay::instance()->p2pNet()->blockchain().dataSources(); + for (const auto &s : sources) { + m_dataSources.append(new DataSource(s, this)); + } +} + + +QList BlocksDataProvider::dataSources() const +{ + return m_dataSources; +} diff --git a/modules/blocks/BlocksDataProvider.h b/modules/blocks/BlocksDataProvider.h new file mode 100644 index 0000000..5e2bd9a --- /dev/null +++ b/modules/blocks/BlocksDataProvider.h @@ -0,0 +1,56 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef BLOCKSDATAPROVIDER_H +#define BLOCKSDATAPROVIDER_H + +#include +#include + +class DataSource : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool fromStaticFile READ fromStaticFile CONSTANT FINAL) + Q_PROPERTY(int startBlock READ startBlock CONSTANT FINAL) + Q_PROPERTY(int endBlock READ endBlock CONSTANT FINAL) +public: + DataSource(const Blockchain::DataSource &ds, QObject *parent = nullptr); + + bool fromStaticFile() const; + int startBlock() const; + int endBlock() const; + +private: + bool m_fromStaticFile; + int m_startBlock; + int m_endBlock; +}; + +class BlocksDataProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList dataSources READ dataSources CONSTANT FINAL) +public: + explicit BlocksDataProvider(QObject *parent = nullptr); + + QList dataSources() const; + +private: + QList m_dataSources; +}; + +#endif diff --git a/modules/blocks/BlocksModuleInfo.cpp b/modules/blocks/BlocksModuleInfo.cpp index 0fc5698..cf600a3 100644 --- a/modules/blocks/BlocksModuleInfo.cpp +++ b/modules/blocks/BlocksModuleInfo.cpp @@ -17,6 +17,7 @@ */ #include "BlocksModuleInfo.h" #include "BlockHeadersChecker.h" +#include "BlocksDataProvider.h" #include // for the qmlRegisterType @@ -44,6 +45,7 @@ ModuleInfo * BlocksModuleInfo::build() info->addSection(checker); qmlRegisterType("Flowee.org.pay.blocks", 1, 0, "Checker"); + qmlRegisterType("Flowee.org.pay.blocks", 1, 0, "BlocksData"); return info; } diff --git a/modules/blocks/CMakeLists.txt b/modules/blocks/CMakeLists.txt index 1872df8..4392f4c 100644 --- a/modules/blocks/CMakeLists.txt +++ b/modules/blocks/CMakeLists.txt @@ -19,6 +19,7 @@ project(blocks_module) set (SOURCES BlocksModuleInfo.cpp BlockHeadersChecker.cpp + BlocksDataProvider.cpp ) add_library (blocks_module_lib STATIC ${SOURCES}) target_link_libraries(blocks_module_lib pay_lib) diff --git a/modules/blocks/Configuration.qml b/modules/blocks/Configuration.qml index dfd9393..e0a7cfb 100644 --- a/modules/blocks/Configuration.qml +++ b/modules/blocks/Configuration.qml @@ -19,11 +19,38 @@ import QtQuick import QtQuick.Layouts import "../Flowee" as Flowee import "../mobile"; +import Flowee.org.pay.blocks as Blocks; Page { headerText: qsTr("Blocks") - Flowee.Label { - y: 10 - text: "Nothing here yet" + + Item { + Blocks.BlocksData { + id: blocksData + } + } + + Column { + spacing: 6 + + Flowee.Label { + text: "Headers on this device" + } + + Repeater { + model: blocksData.dataSources + Rectangle { + width: 50 + height: 50 + color: modelData.fromStaticFile ? "#66b0ff" : "#1231a3" + + Flowee.Label { + anchors.left: parent.right + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + text: "from: " + modelData.startBlock + " - " + modelData.endBlock + } + } + } } } -- 2.54.0 From c8023e5e1177512225abd3c641065b74aebc03eb Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 19:40:19 +0200 Subject: [PATCH 0909/1428] Start new version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 27afe76..b1352d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.05.0"); + qapp.setApplicationVersion("2024.07.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From c9d3e5ade41758f425fc738ecfcd3128c5f74e75 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 21:30:03 +0200 Subject: [PATCH 0910/1428] Fix replacement headers on Android seems renaming a file that is mapped is not possible on Android, but remove is. This code now removes the original static headers after we get an approval of the downloaded file. --- modules/blocks/BlockHeadersChecker.cpp | 42 +++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 855ae75..44cd820 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -26,6 +26,9 @@ static constexpr const char * HEADERFILE = "https://flowee.org/products/pay/blockheaders"; +#define TEMP_DOWNLOAD_FILENAME "module_blocks_newheaders" +#define PAY_STATIC_HEADERS "staticHeaders" + BlockHeadersChecker::BlockHeadersChecker(QObject *parent) : QObject(parent) { @@ -118,7 +121,7 @@ void BlockHeadersChecker::headerReturned() assert(m_checkpoint > 0); const int from = m_checkpoint * 80; const int to = m_downloadTo * 80; - if (to > length) { + if (to > static_cast(length)) { logCritical() << "Remote file does not have the data we want"; setStatus(NetworkFailure); return; @@ -130,8 +133,7 @@ void BlockHeadersChecker::headerReturned() range = range.arg(to - 1); // -1 because ranges are inclusive. downloadRequest.setRawHeader("Range", range.toLatin1()); - QString filename("module_blocks_newheaders"); - m_newHeaders = new QFile(filename, this); + m_newHeaders = new QFile(TEMP_DOWNLOAD_FILENAME, this); if (!m_newHeaders->open(QIODevice::WriteOnly)) { logFatal() << "Can not open file..."; setStatus(DiskFailure); @@ -161,7 +163,7 @@ void BlockHeadersChecker::downloadFinished() // the file we put this in has to have an application-long lifetime. // so lets give ownership to the main app. - QFile oldStaticHeaders("staticHeaders"); + QFile oldStaticHeaders(PAY_STATIC_HEADERS); if (!oldStaticHeaders.open(QIODevice::ReadOnly)) { logFatal() << "Failed to find old static headers"; setStatus(DiskFailure); @@ -180,38 +182,42 @@ void BlockHeadersChecker::downloadFinished() m_newHeaders->close(); // the currenty in use static headers are in the location I want the new ones to be - // because when we restart, we open by path. So the new ones shoud have the main + // because when we restart, we open by path. + // + // After a successful attempt to replace staticChain we'll thus remove the old + // staticHeaders file and move the one on top of that. + // "staticHeaders_new" -> finished, // static headers path... // Knowing that this will never run on Windowz, lets just rename the open file and // place the newly created file at the known path. - QFile::rename("staticHeaders", "staticHeaders_old"); - QFile::rename("staticHeaders.info", "staticHeaders_old.info"); - - bool ok = m_newHeaders->rename("staticHeaders"); - if (!ok) { - logFatal() << "Failed to rename to staticHeaders"; - setStatus(DiskFailure); - return; - } if (!m_newHeaders->open(QIODevice::ReadOnly)) { - logFatal() << "Failed to re-open staticHeaders"; + logFatal() << "Failed to re-open replacement static headers"; setStatus(DiskFailure); return; } try { + logCritical() << "Prepared a new static headers chain. Size:" << m_newHeaders->size() + << "Replacing the current one with this new file"; auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); blockchain.replaceStaticChain(m_newHeaders->map(0, m_newHeaders->size()), - m_newHeaders->size(), "staticHeaders.info"); + m_newHeaders->size(), TEMP_DOWNLOAD_FILENAME ".info"); // keep the file object from getting garbage collected. // the map() requires the QFile to stay alive. m_newHeaders->setParent(FloweePay::instance()); + m_newHeaders->close(); + + // make sure we use the new ones after restart. + QFile::remove(PAY_STATIC_HEADERS); + QFile::remove(PAY_STATIC_HEADERS ".info"); + QFile::rename(TEMP_DOWNLOAD_FILENAME ".info", PAY_STATIC_HEADERS ".info"); + m_newHeaders->rename(PAY_STATIC_HEADERS); } catch (const std::exception &e) { - logCritical() << e; + logCritical() << "Replace static chain failed. Reason:" << e; setStatus(NetworkFailure); + m_newHeaders->close(); m_newHeaders->remove(); } - m_newHeaders->close(); setStatus(Success); } -- 2.54.0 From c371d3b47a4fd65f21a43a24de26dd75030634ab Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 22:06:15 +0200 Subject: [PATCH 0911/1428] Import translations from Crowdin --- translations/floweepay-common_de.ts | 136 ++-- translations/floweepay-desktop_de.ts | 730 +++++++++++--------- translations/floweepay-mobile_de.ts | 422 ++++++----- translations/module-build-transaction_de.ts | 23 +- 4 files changed, 719 insertions(+), 592 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 8d40fda..90debd3 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Geldbörse: Aktuell - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Hinterher: %1 Tag @@ -31,17 +31,17 @@ - + Up to date Aktuell - + Updating Aktualisierung - + Still %1 hours behind Noch %1 Stunde hinterher @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity Coin wurde für eine erhöhte Anonymität fusioniert + + FiatTxInfo + + + Value now + Wert jetzt + + + + Value then + Wert zuvor + + FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -286,12 +299,12 @@ Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. - + Request received over insecure channel. Anyone could have altered it! Anfrage über unsicheren Kanal empfangen. Jeder hätte sie verändern können! - + Download of payment request failed. Download der Zahlungsanfrage fehlgeschlagen. @@ -310,6 +323,29 @@ Zahlungsanfrage abgelaufen + + QRScanner + + + Paste + Einfügen + + + + Failed + Fehlgeschlagen + + + + Instant Pay limit is %1 + Sofortzahlungslimit ist %1 + + + + Selected wallet: '%1' + Ausgewählte Geldbörse: '%1' + + QRWidget @@ -318,17 +354,25 @@ In die Zwischenablage kopiert + + TextPasteDecorator + + + Paste + Einfügen + + Wallet - - + + Change #%1 Wechselgeld #%1 - - + + Main #%1 Haupt #%1 @@ -336,12 +380,12 @@ WalletCoinsModel - + Unconfirmed Unbestätigt - + %1 hours age, like: hours old @@ -350,7 +394,7 @@ - + %1 days age, like: days old @@ -359,7 +403,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +412,7 @@ - + %1 months age, like: months old @@ -377,7 +421,7 @@ - + Change #%1 Wechselgeld #%1 @@ -385,27 +429,27 @@ WalletHistoryModel - + Today Heute - + Yesterday Gestern - + Earlier this week Früher in dieser Woche - + This week Diese Woche - + Earlier this month Früher in diesem Monat @@ -413,12 +457,12 @@ WalletSecretsView - + Explanation Erklärung - + Coins a / b a) active coin-count. b) historical coin-count. @@ -427,23 +471,33 @@ b) historische Coin-Anzahl. - - + + Copy Address Adresse kopieren - + + QR of Address + QR der Adresse + + + Copy Private Key Privaten Schlüssel kopieren - + + QR of Private Key + QR des privaten Schlüssels + + + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signiert mit Schnorr-Signaturen in der Vergangenheit diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index a86d45d..00a9685 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -19,28 +19,28 @@ Geldbörse archivieren - + Make Primary Primär setzen - + ★ Primary ★ Primär - + Protect With Pin... Mit Pin schützen... - + Open Open encrypted wallet Öffnen - + Close Close encrypted wallet Schließen @@ -64,92 +64,92 @@ Sync Status - + Encryption Verschlüsselung - + Password Passwort - + Open Öffnen - + Invalid PIN Ungültige PIN - + Include balance in total Inkludiere Guthaben in Gesamtsumme - + Hide in private mode Im privaten Modus ausblenden - + Address List Adressliste - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen - + Backup details Backup-Details - + Seed-phrase Seed-Phrase - + Seed format Seed-Format - + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -162,10 +162,58 @@ Letzte Transaktion + + AddressDbStats + + + IP Addresses + IP-Adressen + + + + Total found + Gesamt gefunden + + + + Tried + Versuchte + + + + Punished count + Abgestrafte Anzahl + + + + Banned count + Gebannte Anzahl + + + + IP-v4 count + IPv4-Anzahl + + + + IP-v6 count + IPv6-Anzahl + + + + Pardon the Banned + Gesperrte Knoten entsperren + + + + Close + Schließen + + NetView - + Peers (%1) Peers (%1) @@ -173,48 +221,38 @@ - + Address network address (IP) Adresse - + Start-height: %1 Starthöhe: %1 - + ban-score: %1 ban-score: %1 - - initializing connection - Initialisiere Verbindung - - - - Verifying peer - Überprüfe Peer - - - - Assigning... - Zuweisen... - - - + Peer for wallet: %1 Peer für Geldbörse: %1 - - Peer for initial wallet - Peer für die erste Geldbörse + + Disconnect Peer + Trenne Knoten - + + Ban Peer + Sperre Knoten + + + Close Schließen @@ -222,32 +260,27 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - Dies erzeugt eine neue leere Geldbörse mit einfacher Mehradressen-Fähigkeit + + Create a new empty wallet with simple multi-address capability + Dies erzeugt eine neue leere Geldbörse mit einfacher Mehradressen-Fähigkeit - + Name Name - + Go Los - - Advanced Options - Erweiterte Optionen - - - + Force Single Address Erzwinge einzelne Adresse - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wenn aktiviert, wird diese Geldbörse auf eine Adresse beschränkt. @@ -257,27 +290,27 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - Dies erzeugt eine neue leere Geldbörse mit intelligenter Erstellung von Adressen aus einer einzigen Seed-Phrase + + Creates a new wallet with smart creation of addresses from a single seed-phrase + Erstellt eine neue Geldbörse mit intelligenter Erstellung von Adressen aus einer einzigen Seed-Phrase - + Name Name - + Go Los - + Advanced Options Erweiterte Optionen - + Derivation Ableitung @@ -285,188 +318,172 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. - - - - Secret - The seed-phrase or private key - Geheimnis - - - - Example: %1 - placeholder text - Beispiel: %1 - - - + + Name Name - - Private key - description of type - Privater Schlüssel - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 Seed-Phrase (interpretiert als Electrum Format) - - - - BIP 39 seed-phrase - description of type - BIP 39 Seed-Phrase + + Select import method + Importmethode auswählen - Electrum seed-phrase - description of type - Electrum Seed-Phrase + Camera + Kamera - - Unrecognized word - Word from the seed-phrases lexicon - Unerkanntes Wort + + OR + OR - - Import wallet - Geldbörse importieren + + Secret as text + The seed-phrase or private key + Geheimnis als Text - - Advanced Options - Erweiterte Optionen + + Unknown word(s) found + Unbekannte(s) Wort(e) gefunden - + + Address to import + Zu importierende Adresse + + + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - Old Electrum Phrase - Alte Electrum Phrase + + + Oldest Transaction + Älteste Transaktion - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Wenn die Electrum Erkennung fehlschlägt, und Sie sind sicher, dass sie in dieser Geldbörse erstellt wurde, aktivieren Sie diese Option. + + Check Age + online check for wallet age + Überprüfe Alter - - Start Height - Starthöhe + + Nothing found for wallet + Nichts gefunden für Geldbörse - + + + Start + Start + + + + Password + Passwort + + + + Optional + Optional + + + + Details + Details + + + + Lookup Details + online check for wallet details + Suchdetails + + + + Nothing found for seed + Nichts gefunden für Seed + + + Derivation Ableitung - - - Alternate phrase - Alternative Phrase - NewAccountPane - - New Bitcoin Cash Wallet - Neue Bitcoin Cash Geldbörse + + New HD wallet + Neue HD-Geldbörse - - Basic - Einfach + + Import Existing Wallet + Import einer vorhandenen Geldbörse - + + New Basic Wallet + Neue Basis-Geldbörse + + + Private keys based Property of a wallet Private Schlüssel basiert - + Difficult to backup Context: wallet type Schwierig zu sichern - + Great for brief usage Context: wallet type Ideal für kurze Nutzung - - HD wallet - HD Geldbörse - - - + Seed-phrase based Context: wallet type Seed-Phrase basiert - + Easy to backup Context: wallet type Einfach zu sichern - + Most compatible The most compatible wallet type Am kompatibelsten - - Import - Import - - - + Imports seed-phrase Importiert Seed-Phrase - + Imports private key Importiert privaten Schlüssel - - - Basic wallet with private keys - Einfache Geldbörse mit privaten Schlüsseln - - - - Seed-phrase wallet - Seed-Phrase Geldbörse - - - - Import of an existing wallet - Import einer vorhandenen Geldbörse - PaymentTweakingPanel @@ -662,6 +679,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. + Copy Address Adresse kopieren @@ -764,60 +782,251 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Einstellungen - + Unit Einheit - + Show Bitcoin Cash value on Activity page Bitcoin Cash Wert auf der Aktivitätsseite anzeigen - + Show Block Notifications Zeige Blockbenachrichtigungen - + When a new block is mined, Flowee Pay shows a desktop notification Wenn ein neuer Block erzeugt wird, zeigt Flowee Pay eine Desktop-Benachrichtigung - + Night Mode Nachtmodus - + Private Mode Privater Modus - + Hides private wallets while enabled Versteckt private Geldbörsen, wenn aktiviert - + + Font sizing + Schriftgröße + + + Version Version - + Library Version Version der Bibliothek - + Synchronization Synchronisation - + Network Status Netzwerkstatus + + + Address Stats + Adressstatistik + + + + Transaction + + + Miner Reward + Miner Belohnung + + + + Fused + Fusioniert + + + + Received + Erhalten + + + + Moved + Verschoben + + + + Sent + Gesendet + + + + rejected + abgelehnt + + + + TransactionDetails + + + Transaction Details + Transaktionsdetails + + + + First Seen + Erstsichtung + + + + Rejected + Abgelehnt + + + + Unconfirmed + Unbestätigt + + + + Mined at + Abgebaut am + + + + %1 blocks ago + + %1 Block her + %1 Blöcke her + + + + + Comment + Kommentar + + + + Fees paid + Bezahlte Gebühren + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 Bytes + + + + Size + Größe + + + + %1 bytes + %1 Bytes + + + + Is Coinbase + Ist Coinbase + + + + Copy transaction-ID + Kopiere Transaktions-ID + + + + Fused from my addresses + Fusioniert von meinen Adressen + + + + Sent from my addresses + Gesendet von meinen Adressen + + + + Sent from addresses + Gesendet von den Adressen + + + + Fused into my addresses + Fusioniert in meine Adressen + + + + Received at addresses + Empfangen auf den Adressen + + + + Received at my addresses + Empfangen auf meinen Adressen + + + + TransactionInfoSmall + + + Transaction is rejected + Transaktion wurde abgelehnt + + + + Processing + Verarbeite + + + + Mined + Gemined + + + + %1 blocks ago + Confirmations + + %1 Block her + %1 Blöcke her + + + + + Fees + Gebühren + + + + Copy transaction-ID + Kopiere Transaktions-ID + + + + Holds a token + Hält ein Token + + + + Opening Website + Öffne Website + WalletEncryption @@ -946,16 +1155,16 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. WalletEncryptionStatus - - - Pin to Pay - Pin um zu bezahlen - Pin to Open Pin zum Öffnen + + + Pin to Pay + Pin um zu bezahlen + (Opened) @@ -963,206 +1172,98 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. (Geöffnet) - - WalletTransaction - - - Miner Reward - Miner Belohnung - - - - Fused - Fusioniert - - - - Received - Empfangen - - - - Moved - Verschoben - - - - Sent - Gesendet - - - - rejected - abgelehnt - - - - unconfirmed - unbestätigt - - - - WalletTransactionDetails - - - Copy transaction-ID - Transaktions-ID kopieren - - - - Status - Status - - - - rejected - Abgelehnt - - - - unconfirmed - Unbestätigt - - - - %1 confirmations (mined in block %2) - - %1 Bestätigungen (im Block %2) - %1 Bestätigungen (im Block %2) - - - - - Copy block height - Kopiere Blockhöhe - - - - Fees - Gebühren - - - - Size - Größe - - - - %1 bytes - - %1 Bytes - %1 Bytes - - - - - Inputs - Eingaben - - - - - Copy Address - Adresse kopieren - - - - Outputs - Ausgaben - - main - + Activity Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1171,9 +1272,14 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... + + + QR-Scan + QR-Scan + diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index f93fcda..9ab2fcb 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - ©️ 2020-2023 Tom Zander und Mitwirkende + © 2020-2024 Tom Zander and contributors + ©️ 2020-2024 Tom Zander und Mitwirkende - + Project Home Projekt-Startseite - + With git repository and issues tracker Mit git Repository und Issue Tracker - + Telegram Telegram @@ -63,42 +63,32 @@ Empfange - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Empfangen - + Moved Verschoben - + Sent Gesendet - - - Sending - Sende - - Seen - Gesehen - - - Rejected Abgelehnt @@ -126,99 +116,100 @@ Backup-Details - + Wallet seed-phrase Geldbörsen-Seed-Phrase - + Seed format Seed-Format - + + Starting Height height refers to block-height Starthöhe - + Derivation Path Ableitungspfad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + Wallet keys Geldbörsen-Schlüssel - + Show Index toggle to show numbers Index anzeigen - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between still in use addresses and formerly used, new empty, addresses Wechselt zwischen noch benutzten Adressen und früher genutzten, neuen leeren Adressen - + Addresses and keys Adressen und Schlüssel - + Sync Status Synchronisierungsstatus - + Hide balance in overviews Guthaben in Übersichten ausblenden - + Hide in private mode Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren @@ -241,7 +232,7 @@ Benötigt PIN zum Öffnen - + Balance Total Gesamtguthaben @@ -342,22 +333,22 @@ Dunkles Design - + Unit Einheit - + Change Currency (%1) Währung ändern (%1) - + Main View Hauptansicht - + Show Bitcoin Cash value Zeige Bitcoin Cash Wert @@ -370,93 +361,113 @@ Geldbörse importieren - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Bitte geben Sie die Geheimnisse der zu importierenden Geldbörse ein. Dies kann eine Seed-Phrase oder ein privater Schlüssel sein. - - - - Secret - The seed-phrase or private key - Geheimnis - - - - Private key - description of type - Privater Schlüssel - - - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 Seed-Phrase (interpretiert als Electrum Format) - - - - BIP 39 seed-phrase - description of type - BIP 39 Seed-Phrase - - - - Electrum seed-phrase - description of type - Electrum Seed-Phrase - - - - Unrecognized word - Word from the seed-phrases lexicon - Unerkanntes Wort - - - + + Name Name - + Force Single Address Erzwinge einzelne Adresse - + + Select import method + Importmethode auswählen + + + + Camera + Kamera + + + + OR + OR + + + + Secret as text + The seed-phrase or private key + Geheimnis als Text + + + + Unknown word(s) found + Unbekannte(s) Wort(e) gefunden + + + + Next + Weiter + + + + Address to import + Zu importierende Adresse + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - Old Electrum Phrase - Alte Electrum Phrase + + Nothing found for seed + Nichts gefunden für Seed - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Wenn die Electrum Erkennung fehlschlägt, und Sie sind sicher, dass sie in dieser Geldbörse erstellt wurde, aktivieren Sie diese Option. - - - + + Oldest Transaction Älteste Transaktion - + + Check Age + online check for wallet age + Überprüfe Alter + + + + Nothing found for wallet + Nichts gefunden für Geldbörse + + + + + Start + Start + + + + Password + Passwort + + + + Optional + Optional + + + + Details + Details + + + + Lookup Details + online check for wallet details + Suchdetails + + + Derivation Ableitung - - - Alternate phrase - Alternative Phrase - - - - Create - Erstelle - InstaPayConfigButton @@ -495,21 +506,21 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - Requests for payment can be approved automatically using Instant Pay. - Zahlungsanfragen können automatisch mit Sofortzahlung bewilligt werden. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Das Scannen des QR-Codes mit aktivierter Sofortzahlung macht die Überweisung ohne Bestätigung. Solange er das festgelegte Limit nicht überschreitet. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Geschützte Geldbörsen können nicht für Sofortzahlung verwendet werden, da sie vor der Nutzung eine PIN benötigen - + Enable Instant Pay for this wallet Sofortzahlung für diese Geldbörse aktivieren - + Maximum Amount Höchstbetrag @@ -583,122 +594,112 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Neue Bitcoin Cash Geldbörse - - Create a New Wallet - Erstelle eine neue Geldbörse - - - - HD wallet - HD Geldbörse - - - + Seed-phrase based Context: wallet type Seed-Phrase basiert - + Easy to backup Context: wallet type Einfach zu sichern - + Most compatible The most compatible wallet type Am kompatibelsten - - Basic - Einfach - - - + Private keys based Property of a wallet Private Schlüssel basiert - + Difficult to backup Context: wallet type Schwierig zu sichern - + Great for brief usage Context: wallet type Ideal für kurze Nutzung - + Import Existing Wallet Import einer vorhandenen Geldbörse - - Import - Import + + New HD Wallet + Neue HD-Geldbörse - + Imports seed-phrase Importiert Seed-Phrase - + Imports private key Importiert privaten Schlüssel - + + New Basic Wallet + Neue Basis-Geldbörse + + + New Wallet Neue Geldbörse - + This creates a new empty wallet with simple multi-address capability Dies erzeugt eine neue leere Geldbörse mit einfacher Mehradressen-Fähigkeit - - + + Name Name - + Force Single Address Erzwinge einzelne Adresse - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Wenn aktiviert, wird diese Geldbörse auf eine Adresse beschränkt. Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - - + + Create Erstelle - + New HD-Wallet Neue HD-Geldbörse - + This creates a new wallet that can be backed up with a seed-phrase Dies erzeugt eine neue Geldbörse, die mit einer Seed-Phrase gesichert werden kann - + Derivation Ableitung @@ -759,7 +760,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussZieladresse - + Unlock Wallet Geldbörse entsperren @@ -799,29 +800,6 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussAlle Währungen - - QRScannerOverlay - - - Paste - Einfügen - - - - Failed - Fehlgeschlagen - - - - Instant Pay limit is %1 - Sofortzahlungslimit ist %1 - - - - Selected wallet: '%1' - Ausgewählte Geldbörse: '%1' - - ReceiveTab @@ -999,27 +977,37 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussTransaktionsdetails - + + Open in Explorer + In Explorer öffnen + + + Transaction Hash Transaktions-Hash - + + First Seen + Erstsichtung + + + Rejected Abgelehnt - + Unconfirmed Unbestätigt - + Mined at Abgebaut am - + %1 blocks ago %1 Block her @@ -1027,86 +1015,80 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - + Transaction comment Transaktionskommentar - + Size: %1 bytes Größe: %1 Bytes - + Coinbase Coinbase - + Fees paid Bezahlte Gebühren - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 Bytes - + Fused from my addresses Fusioniert von meinen Adressen - + Sent from my addresses Gesendet von meinen Adressen - + Sent from addresses Gesendet von den Adressen - - - Copy Address - Adresse kopieren - - - + Fused into my addresses Fusioniert in meine Adressen - + Received at addresses Empfangen auf den Adressen - + Received at my addresses Empfangen auf meinen Adressen - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected - Transaktion ist abgelehnt + Transaktion wurde abgelehnt - + Processing Verarbeite - + Mined - Abgebaut + Gemined - + %1 blocks ago Confirmations @@ -1115,52 +1097,42 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - + Miner Reward Miner Belohnung - + Fees - Bezahlte Gebühren + Gebühren - + Received - Empfangen + Erhalten - + Payment to self Zahlung an sich selbst - + Sent Gesendet - + Holds a token Hält ein Token - + Sent to Gesendet an - - Value now - Wert jetzt - - - - Value then - Wert zuvor - - - + Transaction Details Transaktionsdetails @@ -1173,7 +1145,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussGeben Sie Ihren Geldbörsen-Zugangscode ein - + Open open wallet with PIN Öffnen diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index d1d943c..04c0120 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -40,7 +40,7 @@ - + Add Destination Ziel hinzufügen @@ -110,7 +110,7 @@ - + Copy Address Adresse kopieren @@ -131,42 +131,37 @@ Bitcoin Cash Adresse - - Paste - Einfügen - - - + Warning Warnung - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + I am certain Ich bin mir sicher - + Drag to Edit Ziehen zum Bearbeiten - + Drag to Delete Ziehen zum Löschen - + Unlock Wallet Geldbörse entsperren - + Prepare Payment... Zahlung wird vorbereitet... -- 2.54.0 From bfa373dc9947d12bfafe55fe0e9389a1ff71f6ff Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 23:39:30 +0200 Subject: [PATCH 0912/1428] Update basic description of the Blocks module This also adds an icon. --- guis/mobile/ExploreModules.qml | 4 +- modules/blocks/BlocksModuleInfo.cpp | 4 +- modules/blocks/blocks.svg | 66 +++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index c558dbb..e1193b6 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -59,7 +59,7 @@ Page { } Image { x: 10 - width: 42 + width: 64 anchors.top: descriptionFrame.top source: modelData.iconSource visible: modelData.iconSource !== "" @@ -71,7 +71,7 @@ Page { id: descriptionFrame anchors.top: titleLabel.bottom anchors.topMargin: 10 - width: parent.width - (modelData.iconSource === "" ? 0 : 52) + width: parent.width - (modelData.iconSource === "" ? 0 : 76) clip: true anchors.bottom: statusField.top anchors.bottomMargin: 10 diff --git a/modules/blocks/BlocksModuleInfo.cpp b/modules/blocks/BlocksModuleInfo.cpp index cf600a3..45bec4f 100644 --- a/modules/blocks/BlocksModuleInfo.cpp +++ b/modules/blocks/BlocksModuleInfo.cpp @@ -25,8 +25,8 @@ ModuleInfo * BlocksModuleInfo::build() { ModuleInfo *info = new ModuleInfo(); info->setId("blocks"); - info->setTitle(tr("History Details")); - info->setDescription(tr("Everything about blocks")); + info->setTitle(tr("Blockchain History")); + info->setDescription(tr("Investigate the historical blockchain. See blocks as they get mined, or download more history.")); info->setIconSource("qrc:/blocks/blocks.svg"); /* Not usable yet. diff --git a/modules/blocks/blocks.svg b/modules/blocks/blocks.svg index d8efb34..bdc1f27 100644 --- a/modules/blocks/blocks.svg +++ b/modules/blocks/blocks.svg @@ -1,13 +1,55 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + -- 2.54.0 From 19efc6575fa12fc5b0309cf8db4e293799b3951e Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 28 Jun 2024 19:02:18 +0200 Subject: [PATCH 0913/1428] Split processing and update UI We set the UI to change from 'downloading' state to 'processing' state, and now we give the UI the processor time to actually show this new state on the screen before we start that processing step. --- modules/blocks/BlockHeadersChecker.cpp | 9 +++++++++ modules/blocks/BlockHeadersChecker.h | 1 + 2 files changed, 10 insertions(+) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 44cd820..128c6d5 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -181,6 +181,15 @@ void BlockHeadersChecker::downloadFinished() oldStaticHeaders.close(); m_newHeaders->close(); + // make sure that the UI can update the changes we pushed here + // before we start the process new headers which is going to take + // some actual processing time, depending on how big our headers + // are now. + QTimer::singleShot(130, this, SLOT(processNewHeaders())); +} + +void BlockHeadersChecker::processNewHeaders() +{ // the currenty in use static headers are in the location I want the new ones to be // because when we restart, we open by path. // diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index ed4f014..2778fb3 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -70,6 +70,7 @@ private slots: void startChecking(); void headerReturned(); void downloadFinished(); + void processNewHeaders(); void onBytesDownloaded(); private: -- 2.54.0 From 7cab7672834da415a1c1227d96eb370a65e6811c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 28 Jun 2024 19:23:56 +0200 Subject: [PATCH 0914/1428] Allow the download to be restarted. We check the first block to figure out if the download is actually the same one we are about to start, and if it is then Pay will not re- download the part that is already on disk. --- modules/blocks/BlockHeadersChecker.cpp | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 128c6d5..1f513c0 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -111,15 +111,36 @@ void BlockHeadersChecker::headerReturned() return; } + // Check if this is a continuation download. + assert(m_downloadTo > m_checkpoint); + assert(m_checkpoint > 0); + m_newHeaders = new QFile(TEMP_DOWNLOAD_FILENAME, this); + bool isContinuation = false; + if (m_newHeaders->open(QIODevice::ReadOnly)) { + char bytes[80]; + auto read = m_newHeaders->read(bytes, 80); + if (read == 80) { + BlockHeader *bh = reinterpret_cast(bytes); + auto hash = bh->createHash(); + auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); + bool ok = false; + for (const auto &cp : blockchain.checkpoints()) { + if (cp.height == m_checkpoint) { + isContinuation = cp.hash == hash; + break; + } + } + } + m_newHeaders->close(); + } + // We're going to download the headers. // On the remote server there is one file that has the genesis // all the way up to a recent height=((length / 80) - 1) // We use the 'range' feature of the webserver to only download // what we need, so lets calculate the offsets here. - assert(m_downloadTo > m_checkpoint); - assert(m_checkpoint > 0); - const int from = m_checkpoint * 80; + const int from = m_checkpoint * 80 + (isContinuation ? m_newHeaders->size() : 0); const int to = m_downloadTo * 80; if (to > static_cast(length)) { logCritical() << "Remote file does not have the data we want"; @@ -133,8 +154,8 @@ void BlockHeadersChecker::headerReturned() range = range.arg(to - 1); // -1 because ranges are inclusive. downloadRequest.setRawHeader("Range", range.toLatin1()); - m_newHeaders = new QFile(TEMP_DOWNLOAD_FILENAME, this); - if (!m_newHeaders->open(QIODevice::WriteOnly)) { + if (!m_newHeaders->open(QIODevice::WriteOnly + | (isContinuation ? QIODevice::Append : QIODevice::Truncate))) { logFatal() << "Can not open file..."; setStatus(DiskFailure); return; -- 2.54.0 From 05732c1998e63945a670160d40e85ced99e77c5d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 28 Jun 2024 22:24:44 +0200 Subject: [PATCH 0915/1428] Set timeouts on network Make requests fail on no network activity for some time. We should then allow the user to try again. --- modules/blocks/BlockHeadersChecker.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 1f513c0..55faec4 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -86,8 +86,8 @@ void BlockHeadersChecker::startChecking() // do a HEAD if (m_headerReply == nullptr) { QNetworkRequest headRequest((QUrl(HEADERFILE))); + headRequest.setTransferTimeout(6000); m_headerReply = m_network.head(headRequest); - connect(m_headerReply, SIGNAL(finished()), this, SLOT(headerReturned())); } } @@ -100,6 +100,11 @@ void BlockHeadersChecker::headerReturned() logFatal() << "Already downloading..."; return; } + if (m_headerReply->error() != QNetworkReply::NoError) { + logDebug() << m_headerReply->errorString(); + setStatus(NetworkFailure); + return; + } uint64_t length = m_headerReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); auto allowedRange = m_headerReply->rawHeader("Accept-Ranges"); @@ -127,6 +132,8 @@ void BlockHeadersChecker::headerReturned() for (const auto &cp : blockchain.checkpoints()) { if (cp.height == m_checkpoint) { isContinuation = cp.hash == hash; + logCritical() << "Partial file found" << m_newHeaders->size() + << "Continuing!"; break; } } @@ -149,6 +156,7 @@ void BlockHeadersChecker::headerReturned() } QNetworkRequest downloadRequest((QUrl(HEADERFILE))); + downloadRequest.setTransferTimeout(30000); QString range("bytes=%1-%2"); range = range.arg(from); range = range.arg(to - 1); // -1 because ranges are inclusive. @@ -174,6 +182,14 @@ void BlockHeadersChecker::downloadFinished() assert(m_downloadReply); m_downloadReply->deleteLater(); m_downloadReply = nullptr; + if (m_headerReply->error() != QNetworkReply::NoError) { + logDebug() << m_headerReply->errorString(); + setStatus(NetworkFailure); + m_newHeaders->close(); + m_newHeaders->deleteLater(); + m_newHeaders = 0; + return; + } assert(m_newHeaders->isOpen()); logDebug() << "Wrote out:" << m_newHeaders->size() << "bytes" << (m_newHeaders->size() / 80); @@ -247,6 +263,8 @@ void BlockHeadersChecker::processNewHeaders() setStatus(NetworkFailure); m_newHeaders->close(); m_newHeaders->remove(); + m_newHeaders->deleteLater(); + m_newHeaders = nullptr; } setStatus(Success); } -- 2.54.0 From f34bd4e52a31257cf084da8356f2b3e032e767b0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 29 Jun 2024 11:53:33 +0200 Subject: [PATCH 0916/1428] Redo out-of-bounds check This changes us to no longer use the exception to find out the time is before the checkpoint, but instead checks directly. This was needed since upstream changed to no longer throw since that's still a bad practice in C++ --- src/FloweePay.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1e7bbe7..6bde26b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -723,14 +723,19 @@ QDateTime FloweePay::timeOfBlockHeight(int blockHeight) const int FloweePay::heightOfBlockAtTime(QDateTime time) const { - try { - return m_downloadManager->blockchain().blockHeightAtTime(time.toSecsSinceEpoch()) - 1; - } catch (...) { - // when there is no blockheader available at that time, the p2p lib throws, - // to avoid an uncaught exception shutting down the application we catch it and - // return an invalid number instead. - return -1; + const auto &chain = m_downloadManager->blockchain(); + int h = chain.blockHeightAtTime(time.toSecsSinceEpoch()); + if (h == chain.oldestKnownBlockHeight()) { + // The chain will limit the response to the known heights and thus + // will fail to detect if the timestamp is earlier than the checkpoint + // it starts at. + // The contract on this method promises to return -1 for that. So lets detect that here. + auto t = chain.block(h).nTime; + if (time.toSecsSinceEpoch() + 14400 < t) + return -1; } + + return h - 1; } QString FloweePay::formatBlockTime(int blockHeight) const -- 2.54.0 From b073a6ab64ccff9277217daec5479fffe6091e2c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 29 Jun 2024 11:56:51 +0200 Subject: [PATCH 0917/1428] Fix regression, handle the checkbox in all cases. --- guis/desktop/NewAccountImportAccount.qml | 2 +- guis/mobile/ImportWalletPage.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 7ec6cd9..27bcfc6 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -308,8 +308,8 @@ Item { } else { var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2010, oldestTransactionChooser.item.month.currentIndex, 1); options = Pay.createImportedWallet(secretText.text, accountName.text, sh) - options.forceSingleAddress = singleAddress.checked; } + options.forceSingleAddress = singleAddress.checked; for (let a of portfolio.accounts) { if (a.id === options.accountId) { diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 18a609e..f64ac75 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -397,8 +397,8 @@ Page { } else { var height = root.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); options = Pay.createImportedWallet(secretText.text, accountName.text, height) - options.forceSingleAddress = singleAddress.checked; } + options.forceSingleAddress = singleAddress.checked; for (let a of portfolio.accounts) { if (a.id === options.accountId) { -- 2.54.0 From 59949ebe47e950b75305d07ea3968868751294ac Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 29 Jun 2024 11:57:47 +0200 Subject: [PATCH 0918/1428] Add the ability to restart download This adds a label showing the download failed and adds a retry button to start it again. --- modules/blocks/BlockHeadersChecker.cpp | 32 ++++++++++++++++++-------- modules/blocks/BlockHeadersChecker.h | 2 ++ modules/blocks/DownloadChecker.qml | 26 +++++++++++++++++---- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 55faec4..08bc9b7 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -95,20 +95,22 @@ void BlockHeadersChecker::startChecking() void BlockHeadersChecker::headerReturned() { assert(m_headerReply); + auto reply = m_headerReply; m_headerReply->deleteLater(); + m_headerReply = nullptr; + if (m_newHeaders) { logFatal() << "Already downloading..."; return; } - if (m_headerReply->error() != QNetworkReply::NoError) { - logDebug() << m_headerReply->errorString(); + if (reply->error() != QNetworkReply::NoError) { + logDebug() << reply->errorString(); setStatus(NetworkFailure); return; } - uint64_t length = m_headerReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); - auto allowedRange = m_headerReply->rawHeader("Accept-Ranges"); - m_headerReply = nullptr; + uint64_t length = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); + auto allowedRange = reply->rawHeader("Accept-Ranges"); if (allowedRange != "bytes") { setStatus(NetworkFailure); @@ -132,8 +134,8 @@ void BlockHeadersChecker::headerReturned() for (const auto &cp : blockchain.checkpoints()) { if (cp.height == m_checkpoint) { isContinuation = cp.hash == hash; - logCritical() << "Partial file found" << m_newHeaders->size() - << "Continuing!"; + logCritical() << "Partial file found of" << m_newHeaders->size() + << "bytes. Continuing!"; break; } } @@ -180,14 +182,16 @@ void BlockHeadersChecker::headerReturned() void BlockHeadersChecker::downloadFinished() { assert(m_downloadReply); + auto reply = m_downloadReply; m_downloadReply->deleteLater(); m_downloadReply = nullptr; - if (m_headerReply->error() != QNetworkReply::NoError) { - logDebug() << m_headerReply->errorString(); + + if (reply->error() != QNetworkReply::NoError) { + logCritical() << "headers download gave:" << reply->errorString(); setStatus(NetworkFailure); m_newHeaders->close(); m_newHeaders->deleteLater(); - m_newHeaders = 0; + m_newHeaders = nullptr; return; } @@ -300,6 +304,14 @@ void BlockHeadersChecker::setStatus(Status newStatus) emit statusChanged(); } +void BlockHeadersChecker::restart() +{ + if (m_headerReply == nullptr + && m_downloadReply == nullptr + && m_newHeaders == nullptr) + startChecking(); +} + int BlockHeadersChecker::totalDownload() const { return m_totalDownload; diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index 2778fb3..f2df336 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -59,6 +59,8 @@ public: Status status() const; void setStatus(Status newStatus); + Q_INVOKABLE void restart(); + signals: void wantedHeightChanged(); void bytesDownloadedChanged(); diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml index d878411..f61b060 100644 --- a/modules/blocks/DownloadChecker.qml +++ b/modules/blocks/DownloadChecker.qml @@ -40,6 +40,8 @@ Item { h += errorLabel.height + 6; if (downloadProgress.visible) h += downloadProgress.height + 6; + if (netErrorLabel.visible) + h += netErrorLabel.height + 6 + retryButton.height + 6 return h; } @@ -52,7 +54,7 @@ Item { id: testData onStatusChanged: { - if (status == Blocks.Checker.NoDownloadNeeded || status == Blocks.Checker.Success) + if (status === Blocks.Checker.NoDownloadNeeded || status === Blocks.Checker.Success) root.visible = false; } } @@ -147,8 +149,22 @@ Item { Behavior on opacity { OpacityAnimator { } } } - /* - // TODO handle network error and offer a 'retry' button. - NetworkFailure - */ + Flowee.Label { + id: netErrorLabel + text: qsTr("Download Unsuccessful") + visible: testData.status === Blocks.Checker.NetworkFailure + anchors.top: titleLabel.bottom + anchors.topMargin: 6 + color: mainWindow.errorRed + } + + Flowee.Button { + id: retryButton + text: qsTr("Retry") + visible: testData.status === Blocks.Checker.NetworkFailure + anchors.top: netErrorLabel.bottom + anchors.topMargin: 6 + anchors.right: parent.right + onClicked: testData.restart(); + } } -- 2.54.0 From 8776e5763a1e178ca889a6b3c36171ef6953fcf2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 29 Jun 2024 12:11:39 +0200 Subject: [PATCH 0919/1428] Make 'back' button do what we'd expect. --- guis/mobile/ImportWalletPage.qml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index f64ac75..7f1f581 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -45,20 +45,24 @@ Page { ] state: "entryPage" - Keys.onPressed: (event)=> { - if (state !== "entryPage" && (event.key === Qt.Key_Escape || event.key === Qt.Key_Back)) { - event.accepted = true; - state = "entryPage"; - } - } backHandler: function handler() { - // we practically have two or 3 pages inside this on Page object, we should make the 'back' - // button be aware of this. - if (state !== "entryPage") + // we practically have two or 3 pages inside this on Page object, plus a popup! + // we should make the 'back' button be aware of this. + if (checkBlockchainPopup.visible) { + checkBlockchainPopup.whenDone = undefined; // cancel import + checkBlockchainPopup.visible = false; + } + else if (state !== "entryPage") state = "entryPage"; else thePile.pop(); } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { + event.accepted = true; + backHandler(); + } + } function toNextPage() { var type = entryPage.typedData; -- 2.54.0 From c7f1fec3224f3d094c3ec77097a22d20cfd6a726 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Jun 2024 23:44:50 +0200 Subject: [PATCH 0920/1428] Import translations from crowdin --- translations/floweepay-common_pl.ts | 164 +++-- translations/floweepay-desktop_pl.ts | 738 +++++++++++--------- translations/floweepay-mobile_pl.ts | 484 ++++++------- translations/module-build-transaction_pl.ts | 23 +- translations/module-peers-view_pl.ts | 96 ++- 5 files changed, 850 insertions(+), 655 deletions(-) diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index bab2e65..a1cc959 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Rozłączony - + Wallet: Up to date Portfel: Aktualny - + Behind: %1 weeks, %2 days counter on weeks @@ -25,7 +25,7 @@ - + Behind: %1 days W tyle: %1 dzień @@ -35,17 +35,17 @@ - + Up to date Na bieżąco - + Updating Aktualizowanie - + Still %1 hours behind Nadal %1 godzinę w tyle @@ -133,61 +133,74 @@ CFIcon - + Coin has been fused for increased anonymity Moneta poddana fuzji dla podniesienia anonimowości + + FiatTxInfo + + + Value now + Wartość teraz + + + + Value then + Wartość wtedy + + FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp - %1 minutę temu - %1 minut temu + %1 minuta temu %1 minuty temu %1 minut temu + %1 minuty temu - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp - %1 godzinę temu + %1 godzina temu + %1 godziny temu %1 godzin temu - %1 godziy temu %1 godziny temu @@ -250,10 +263,10 @@ New Transactions dialog-title - Nowa Transakcja - Nowe Transakcje - Nowych transakcji - Nowych transakcji + nowa transakcja + Nowe transakcje + Nowe transakcje + Nowe transakcje @@ -287,7 +300,7 @@ Not enough funds selected for fees - Brak środków, by pokryć koszt transakcji + Nie wybrano wystarczającej ilości środków, by pokryć koszt transakcji @@ -300,12 +313,12 @@ Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. - + Request received over insecure channel. Anyone could have altered it! - Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł go zmodyfikować! + Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł je zmodyfikować! - + Download of payment request failed. Pobieranie żądania płatności nie powiodło się. @@ -324,6 +337,29 @@ Żądanie płatności wygasło + + QRScanner + + + Paste + Wklej + + + + Failed + Niepowodzenie + + + + Instant Pay limit is %1 + Limit szybkiej płatności wynosi %1 + + + + Selected wallet: '%1' + Wybrany portfel: '%1' + + QRWidget @@ -332,17 +368,25 @@ Skopiowano do schowka + + TextPasteDecorator + + + Paste + Wklej + + Wallet - - + + Change #%1 Reszta #%1 - - + + Main #%1 Główny #%1 @@ -350,23 +394,23 @@ WalletCoinsModel - + Unconfirmed Niepotwierdzona - + %1 hours age, like: hours old %1 godzinę %1 godziny %1 godzin - %1 godzinami + %1 godziny - + %1 days age, like: days old @@ -377,7 +421,7 @@ - + %1 weeks age, like: weeks old @@ -388,7 +432,7 @@ - + %1 months age, like: months old @@ -399,7 +443,7 @@ - + Change #%1 Reszta #%1 @@ -407,27 +451,27 @@ WalletHistoryModel - + Today Dzisiaj - + Yesterday Wczoraj - + Earlier this week Wcześniej w tym tygodniu - + This week W tym tygodniu - + Earlier this month Wcześniej w tym miesiącu @@ -435,12 +479,12 @@ WalletSecretsView - + Explanation Wyjaśnienie - + Coins a / b a) active coin-count. b) historical coin-count. @@ -449,25 +493,35 @@ b) liczba monet w przeszłości. - - + + Copy Address Skopiuj adres - - Copy Private Key - Skopiuj link prywatny + + QR of Address + Kod QR adresu - + + Copy Private Key + Skopiuj klucz prywatny + + + + QR of Private Key + Kod QR klucza prywatnego + + + Coins: %1 / %2 Monety: %1 / %2 - + Signed with Schnorr signatures in the past - Podpisywano sygnatury Schborra + Podpisano w przeszłości podpisem Schnorr diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index 648bacf..acc0462 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -19,28 +19,28 @@ Zarchiwizuj portfel - + Make Primary Ustaw jako główny - + ★ Primary ★ Główny - + Protect With Pin... Chroń za pomocą pinu... - + Open Open encrypted wallet Otwórz - + Close Close encrypted wallet Zamknij @@ -64,92 +64,92 @@ Status synchronizacji - + Encryption Szyfrowanie - + Password Hasło - + Open Otwórz - + Invalid PIN Nieprawidłowy PIN - + Include balance in total Uwzględnij saldo ogółem - + Hide in private mode Ukryj w trybie prywatnym - + Address List Lista adresów - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Backup details Szczegóły kopii zapasowej - + Seed-phrase Seed - + Seed format Seed format - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -162,10 +162,58 @@ Ostatnia transakcja + + AddressDbStats + + + IP Addresses + Adresy IP + + + + Total found + Łącznie znaleziono + + + + Tried + Tried + + + + Punished count + Punished count + + + + Banned count + Banned count + + + + IP-v4 count + IP-v4 count + + + + IP-v6 count + IP-v6 count + + + + Pardon the Banned + Ułaskaw Zbanowanych + + + + Close + Zamknij + + NetView - + Peers (%1) Peer (%1) @@ -175,48 +223,38 @@ - + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - - initializing connection - Inicjowanie połączenia... - - - - Verifying peer - Weryfikowanie peera - - - - Assigning... - Przypisywanie... - - - + Peer for wallet: %1 Peer dla portfela: %1 - - Peer for initial wallet - Peer dla pierwszego portfela + + Disconnect Peer + Disconnect Peer - + + Ban Peer + Zbanuj peera + + + Close Zamknij @@ -224,32 +262,27 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - Tworzy nowy, pusty portfel jedno- lub wieloadresowy + + Create a new empty wallet with simple multi-address capability + Create a new empty wallet with simple multi-address capability - + Name Nazwa - + Go Dalej - - Advanced Options - Opcje zaawansowane - - - + Force Single Address Wymuś jeden adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Gdy włączone, ten portfel będzie ograniczony do jednego adresu. @@ -259,27 +292,27 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase - Tworzy nowy, pusty portfel z możliwością kreowania adresów z losowego ciągu wyrazów (seeda) + + Creates a new wallet with smart creation of addresses from a single seed-phrase + Creates a new wallet with smart creation of addresses from a single seed-phrase - + Name Nazwa - + Go Dalej - + Advanced Options Opcje zaawansowane - + Derivation Derywacja @@ -287,187 +320,171 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Wprowadź sekrety portfela, który chcesz importować. Może to być seed lub klucz prywatny. - - - - Secret - The seed-phrase or private key - Sekret - - - - Example: %1 - placeholder text - Przykład: %1 - - - + + Name Nazwa - - Private key - description of type - Klucz prywatny - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 seed-phrase (interpreted as Electrum format) - - - - BIP 39 seed-phrase - description of type - Seed BIP 39 + + Select import method + Wybierz metodę importu - Electrum seed-phrase - description of type - Electrum seed-phrase + Camera + Aparat - - Unrecognized word - Word from the seed-phrases lexicon - Nierozpoznawalne słowo + + OR + LUB - - Import wallet - Importuj portfel + + Secret as text + The seed-phrase or private key + Secret as text - - Advanced Options - Opcje zaawansowane + + Unknown word(s) found + Znaleziono nieznane słowa - + + Address to import + Adres do zaimportowania + + + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - Old Electrum Phrase - Old Electrum Phrase + + + Oldest Transaction + Najstarsza transakcja - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. + + Check Age + online check for wallet age + Check Age - - Start Height - Wysokość początkowa + + Nothing found for wallet + Nothing found for wallet - + + + Start + Rozpocznij + + + + Password + Hasło + + + + Optional + Opcjonalne + + + + Details + Szczegóły + + + + Lookup Details + online check for wallet details + Szczegóły wyszukiwania + + + + Nothing found for seed + Nothing found for seed + + + Derivation Derywacja - - - Alternate phrase - Alternatywna fraza - NewAccountPane - - New Bitcoin Cash Wallet - Nowy portfel Bitcoin Cash + + New HD wallet + Nowy portfel HD - - Basic - Podstawowy + + Import Existing Wallet + Importuj istniejący portfel - + + New Basic Wallet + Nowy portfel podstawowy + + + Private keys based Property of a wallet Bazuje na kluczach prywatnych - + Difficult to backup Context: wallet type Trudna kopia zapasowa - + Great for brief usage Context: wallet type Świetny do krótkotrwałego użytku - - HD wallet - Portfel HD - - - + Seed-phrase based Context: wallet type Bazuje na seedzie - + Easy to backup Context: wallet type Łatwa kopia zapasowa - + Most compatible The most compatible wallet type Najbardziej kompatybilny - - Import - Import - - - + Imports seed-phrase Importuje seeda - + Imports private key Importuje klucz prywatny - - - Basic wallet with private keys - Podstawowy portfel z kluczami prywatnymi - - - - Seed-phrase wallet - Portfel bazujący na ciągu losowych słów (seedzie) - - - - Import of an existing wallet - Import istniejącego portfela - PaymentTweakingPanel @@ -663,6 +680,7 @@ Change will come back to the imported key. + Copy Address Skopiuj Adres @@ -767,60 +785,255 @@ Change will come back to the imported key. Ustawienia - + Unit Jednostka - + Show Bitcoin Cash value on Activity page Pokaż wartość Bitcoin Cash na stronie Aktywności - + Show Block Notifications Pokaż powiadomienia o blokach - + When a new block is mined, Flowee Pay shows a desktop notification Gdy nowy blok zostanie wykopany, Flowee Pay pokazuje powiadomienia na pulpicie - + Night Mode Tryb nocny - + Private Mode Tryb prywatny - + Hides private wallets while enabled Ukrywa prywatne portfele - + + Font sizing + Rozmiar czcionki + + + Version Wersja - + Library Version Wersja biblioteki - + Synchronization Synchronizacja - + Network Status Status sieci + + + Address Stats + Statystyki adresu + + + + Transaction + + + Miner Reward + Nagroda dla górnika + + + + Fused + Fused + + + + Received + Otrzymano + + + + Moved + Przeniesiono + + + + Sent + Wysłano + + + + rejected + odrzucona + + + + TransactionDetails + + + Transaction Details + Szczegóły transakcji + + + + First Seen + First Seen + + + + Rejected + Odrzucona + + + + Unconfirmed + Niepotwierdzona + + + + Mined at + Wykopano + + + + %1 blocks ago + + %1 blok temu + %1 bloki temu + %1 bloków temu + %1 bloku temu + + + + + Comment + Komentarz + + + + Fees paid + Koszt transakcji + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bajtów + + + + Size + Rozmiar + + + + %1 bytes + %1 B + + + + Is Coinbase + Czy Coinbase + + + + Copy transaction-ID + Skopiuj ID transakcji + + + + Fused from my addresses + Fuzja z moich adresów + + + + Sent from my addresses + Wysłano z moich adresów + + + + Sent from addresses + Wysłano z adresów + + + + Fused into my addresses + Fuzja na mój adres + + + + Received at addresses + Wpłynęło na adresy + + + + Received at my addresses + Wpłynęło na moje adresy + + + + TransactionInfoSmall + + + Transaction is rejected + Transakcja została odrzucona + + + + Processing + Przetwarzanie + + + + Mined + Wykopano + + + + %1 blocks ago + Confirmations + + %1 blok temu + %1 bloki temu + %1 bloków temu + %1 bloku temu + + + + + Fees + Opłaty + + + + Copy transaction-ID + Skopiuj ID transakcji + + + + Holds a token + Przechowuje token + + + + Opening Website + Otwieranie Strony + WalletEncryption @@ -949,16 +1162,16 @@ Change will come back to the imported key. WalletEncryptionStatus - - - Pin to Pay - PIN by płacić - Pin to Open PIN by otworzyć + + + Pin to Pay + PIN by płacić + (Opened) @@ -966,210 +1179,98 @@ Change will come back to the imported key. (otwarty) - - WalletTransaction - - - Miner Reward - Nagroda dla górnika - - - - Fused - Fused - - - - Received - Otrzymane - - - - Moved - Przeniesione - - - - Sent - Wysłane - - - - rejected - odrzucono - - - - unconfirmed - niepotwierdzone - - - - WalletTransactionDetails - - - Copy transaction-ID - Kopiuj ID transakcji - - - - Status - Status - - - - rejected - odrzucono - - - - unconfirmed - niepotwierdzone - - - - %1 confirmations (mined in block %2) - - %1 potwierdzenie (wykopane w bloku %2) - %1 potwierdzenia (wykopane w bloku %2) - %1 potwierdzeń (wykopane w bloku %2) - %1 potwierdzeń (wykopane w bloku %2) - - - - - Copy block height - Kopiuj wysokość bloku - - - - Fees - Koszt - - - - Size - Rozmiar - - - - %1 bytes - - %1 bajt - %1 bajty - %1 bajtów - %1 bajtów - - - - - Inputs - Wejścia - - - - - Copy Address - Kopiuj adres - - - - Outputs - Wyjścia - - main - + Activity Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1180,9 +1281,14 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… + + + QR-Scan + Skanowanie QR + diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 394cfad..42890bb 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - ©️ 2020-2023 Tom Zander i współtwórcy + © 2020-2024 Tom Zander and contributors + ©️ 2020-2024 Tom Zander i współtwórcy - + Project Home Strona projektu - + With git repository and issues tracker Z repozytorium git i trackerem problemów - + Telegram Telegram @@ -63,42 +63,32 @@ Otrzymaj - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymane - + Moved Przeniesione - + Sent Wysłane - - - Sending - Wysyłam - - Seen - Wyświetlono - - - Rejected Odrzucona @@ -118,7 +108,7 @@ Backup information - Kopia zapasowa informacji + Dane kopii zapasowej @@ -126,99 +116,100 @@ Szczegóły kopii zapasowej - + Wallet seed-phrase Fraza seedowa portfela - + Seed format Seed format - + + Starting Height height refers to block-height - Wysokość przy utworzeniu + Wysokość początkowa - + Derivation Path Ścieżka derywacji - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + Wallet keys Klucze portfela - + Show Index toggle to show numbers Pokaż indeks - + Change Addresses - Adres Reszty + Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between still in use addresses and formerly used, new empty, addresses Przełącza się pomiędzy nadal używanymi adresami i wcześniej używanymi, nowymi pustymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Ukryj saldo w podsumowaniach - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet - Anuluj archiwizację portfela + Przywróć portfel - + Archive Wallet Zarchiwizuj portfel @@ -241,7 +232,7 @@ Do otwarcia wymagany jest PIN - + Balance Total Saldo Całkowite @@ -284,22 +275,22 @@ Exit Private Mode - Wyjdź z trybu edycji + Wyjdź z trybu prywatnego Enter Private Mode - Włącz Tryb Prywatny + Włącz tryb prywatny Reveals wallets marked private - Pokaż portfele oznaczone jako prywatne + Ujawnia portfele oznaczone jako prywatne Hides wallets marked private - Ukryj portfele oznaczone jako prywatne + Ukrywa portfele oznaczone jako prywatne @@ -307,7 +298,7 @@ Select Currency - Wybierz Walutę + Wybierz walutę @@ -321,7 +312,7 @@ ON Enabled. SHORT TEXT! - WŁĄCZONE + @@ -342,22 +333,22 @@ Dark Theme - + Unit Jednostka - + Change Currency (%1) - Zmień Walutę (%1) + Zmień walutę (%1) - + Main View - Ekran Główny + Ekran główny - + Show Bitcoin Cash value Pokazuj wartość Bitcoin Cash @@ -367,95 +358,115 @@ Import Wallet - Importuj Portfel + Importuj portfel - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Proszę wprowadzić sekret portfela, aby zaimportować. Może to być albo fraza-ziarno, albo klucz prywatny. - - - - Secret - The seed-phrase or private key - Sekret - - - - Private key - description of type - Klucz prywatny - - - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 seed-phrase (interpreted as Electrum) - - - - BIP 39 seed-phrase - description of type - Fraza-ziarno BIP 39 - - - - Electrum seed-phrase - description of type - Electrum seed-phrase - - - - Unrecognized word - Word from the seed-phrases lexicon - Nierozpoznane słowo - - - + + Name Nazwa - + Force Single Address Wymuś pojedynczy adres - + + Select import method + Wybierz metodę importu + + + + Camera + Aparat + + + + OR + LUB + + + + Secret as text + The seed-phrase or private key + Secret as text + + + + Unknown word(s) found + Znaleziono nieznane słowa + + + + Next + Dalej + + + + Address to import + Adres do zaimportowania + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - - Old Electrum Phrase - Old Electrum Phrase + + Nothing found for seed + Nothing found for seed - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - - - + + Oldest Transaction Najstarsza transakcja - + + Check Age + online check for wallet age + Check Age + + + + Nothing found for wallet + Nothing found for wallet + + + + + Start + Rozpocznij + + + + Password + Hasło + + + + Optional + Opcjonalne + + + + Details + Szczegóły + + + + Lookup Details + online check for wallet details + Szczegóły wyszukiwania + + + Derivation Derywacja - - - Alternate phrase - Alternatywna fraza - - - - Create - Utwórz - InstaPayConfigButton @@ -482,7 +493,7 @@ Change will come back to the imported key. Limit set to: %1 - Limit ustawiony do: %1 + Limit ustawiony na: %1 @@ -490,27 +501,27 @@ Change will come back to the imported key. Instant Pay - Szybkie płatności + Szybka Płatność - Requests for payment can be approved automatically using Instant Pay. - Przy Szybkich Płatnościach żądania będą automatycznie zatwierdzane. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Zabezpieczone portfele nie mogą być używane do Szybkich Płatności, ponieważ wymagają PIN-u przed użyciem - + Enable Instant Pay for this wallet - Włącz Szybkie Płatności w tym portfelu + Włącz Szybką Płatność w tym portfelu - + Maximum Amount - Maksymalna Kwota + Maksymalna kwota @@ -563,7 +574,7 @@ Change will come back to the imported key. Ok - Ok + OK @@ -571,7 +582,7 @@ Change will come back to the imported key. Add Wallet - Dodaj Portfel + Dodaj portfel @@ -579,125 +590,115 @@ Change will come back to the imported key. New Bitcoin Cash Wallet - Nowy Portfel Bitcoin Cash + Nowy portfel Bitcoin Cash - - Create a New Wallet - Utwórz nowy portfel - - - - HD wallet - Portfel HD - - - + Seed-phrase based Context: wallet type Portfel bazujący na ciągu losowych słów (seedzie) - + Easy to backup Context: wallet type Łatwa kopia zapasowa - + Most compatible The most compatible wallet type Najbardziej kompatybilny - - Basic - Podstawowy - - - + Private keys based Property of a wallet Portfel bazujący na kluczach prywatnych - + Difficult to backup Context: wallet type Trudna kopia zapasowa - + Great for brief usage Context: wallet type Świetny do krótkotrwałego użytku - + Import Existing Wallet Importuj istniejący portfel - - Import - Importuj + + New HD Wallet + Nowy portfel HD - + Imports seed-phrase Importuje seeda - + Imports private key - Importuj klucz prywatny + Importuje klucz prywatny - + + New Basic Wallet + Nowy portfel podstawowy + + + New Wallet - Nowy Portfel + Nowy portfel - + This creates a new empty wallet with simple multi-address capability Tworzy nowy, pusty portfel jedno- lub wieloadresowy - - + + Name Nazwa - + Force Single Address Wymuś jeden adres - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Gdy włączone, ten portfel będzie ograniczony do jednego adresu. Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - - + + Create Utwórz - + New HD-Wallet Nowy portfel HD - + This creates a new wallet that can be backed up with a seed-phrase Tworzy to nowy portfel, który może być zachowany przy pomocy frazy seeda - + Derivation Derywacja @@ -713,7 +714,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Send All all money in wallet - Wyślij Wszystko + Wyślij wszystko @@ -758,7 +759,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoAdres docelowy - + Unlock Wallet Odblokuj portfel @@ -769,7 +770,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego 1 BCH is: %1 Price of a whole bitcoin cash - 1 BCH jest wart: %1 + 1 BCH to: %1 @@ -795,30 +796,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego All Currencies - Wszystkie Waluty - - - - QRScannerOverlay - - - Paste - Wklej - - - - Failed - Niepowodzenie - - - - Instant Pay limit is %1 - Limit szybkiej płatności wynosi %1 - - - - Selected wallet: '%1' - Wybrany portfel: '%1' + Wszystkie waluty @@ -831,12 +809,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Share this QR to receive - Pokaż ten QR kod, aby otrzymać + Udostępnij ten QR kod, aby otrzymać Encrypted Wallet - Zaszyfrowany Portfel + Zaszyfrowany portfel @@ -998,172 +976,166 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSzczegóły transakcji - + + Open in Explorer + Otwórz w Eksploratorze + + + Transaction Hash Hasz Transakcji - + + First Seen + First Seen + + + Rejected Odrzucona - + Unconfirmed Niepotwierdzona - + Mined at Wykopano - + %1 blocks ago %1 blok temu %1 bloki temu %1 bloków temu - %1 bloki temu + %1 bloku temu - + Transaction comment Komentarz do transakcji - + Size: %1 bytes Rozmiar: %1 bajtów - + Coinbase Coinbase - + Fees paid Koszt transakcji - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bajtów - + Fused from my addresses Fuzja z moich adresów - + Sent from my addresses Wysłano z moich adresów - + Sent from addresses Wysłano z adresów - - - Copy Address - Skopiuj Adres - - - + Fused into my addresses Fuzja na mój adres - + Received at addresses - Adresy otrzymujące + Wpłynęło na adresy - + Received at my addresses - Moje adresy otrzymujące + Wpłynęło na moje adresy - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected Transakcja została odrzucona - + Processing Przetwarzanie - + Mined Wykopano - + %1 blocks ago Confirmations %1 blok temu %1 bloki temu %1 bloków temu - %1 bloki temu + %1 bloku temu - + Miner Reward - Nagroda Górnicza + Nagroda dla górnika - + Fees - Koszt + Opłaty - + Received Otrzymano - + Payment to self - Płatność do siebie + Płatność dla siebie - + Sent Wysłano - + Holds a token Przechowuje token - + Sent to - Wysłano na + Wysłano do - - Value now - Wartość teraz - - - - Value then - Wartość wtedy - - - + Transaction Details Szczegóły transakcji @@ -1176,7 +1148,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoWprowadź hasło portfela - + Open open wallet with PIN Otwórz diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 38eff14..8bc3ea6 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -40,7 +40,7 @@ - + Add Destination Dodaj Odbiorcę @@ -110,7 +110,7 @@ - + Copy Address Skopiuj Adres @@ -131,42 +131,37 @@ Adres Bitcoin Cash - - Paste - Wklej - - - + Warning Uwaga - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + I am certain Na Pewno - + Drag to Edit Przeciągnij, aby edytować - + Drag to Delete Przeciągnij, aby usunąć - + Unlock Wallet Odblokuj portfel - + Prepare Payment... Przygotuj płatność... diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts index 9f58bfc..f17a4f7 100644 --- a/translations/module-peers-view_pl.ts +++ b/translations/module-peers-view_pl.ts @@ -4,45 +4,65 @@ NetView - + Peers Parowie - + + Statistics + Statistics + + + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - - initializing connection - Inicjowanie połączenia + + Opening Connection + Opening Connection - - Verifying peer - Weryfikowanie peera + + Validating peer + Validating peer - + + Validated + Validated + + + + Good Peer + Good Peer + + + Peer for wallet: %1 Peer dla portfela: %1 - - Peer for wallet - Peer dla portfela + + Disconnect Peer + Disconnect Peer + + + + Ban Peer + Ban Peer @@ -63,4 +83,52 @@ Szczegóły sieci + + StatsPage + + + IP-Address Statistics + IP-Address Statistics + + + + Counts + Counts + + + + Total found + Total found + + + + Tried + Tried + + + + Misbehaving IPs + Misbehaving IPs + + + + Bad + Bad + + + + Banned + Banned + + + + Network + Network + + + + Pardon the Banned + Pardon the Banned + + -- 2.54.0 From c6a4b200c7cc801ae6ad946c8f4beb590ddc06de Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 15:45:38 +0200 Subject: [PATCH 0921/1428] Update to newer blockheaders This moves the blockheaders we ship to actually be for 2024 :-) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 030f4cd..d894e52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -231,7 +231,7 @@ if (ANDROID AND build_mobile_pay) # blockheaders to be included in the APK set (ASSETS_DIR ${CMAKE_BINARY_DIR}/android-build/assets/) if (NOT quick_deploy) - download_file(https://flowee.org/products/pay/blockheaders_760000-810000 + download_file(https://flowee.org/products/pay/blockheaders_850000-852000 ${ASSETS_DIR}/blockheaders) endif() file(COPY ${CMAKE_SOURCE_DIR}/data/bip39-english -- 2.54.0 From 845bdbab892637e720287d50208a7869f1639129 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 16:47:08 +0200 Subject: [PATCH 0922/1428] Improve for smaller screens --- modules/blocks/blocks.svg | 64 +++++++-------------------------------- 1 file changed, 11 insertions(+), 53 deletions(-) diff --git a/modules/blocks/blocks.svg b/modules/blocks/blocks.svg index bdc1f27..f71000d 100644 --- a/modules/blocks/blocks.svg +++ b/modules/blocks/blocks.svg @@ -1,55 +1,13 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + -- 2.54.0 From a291826f8b2c600ca81b5edc483b558413a09f51 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 17:16:28 +0200 Subject: [PATCH 0923/1428] New Android version --- android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 2ea6e0d..3132883 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="27" android:versionName="2024.07.0"> -- 2.54.0 From 3a6e0470a58ce550a083b985fca1d308c01cb74f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 22:27:32 +0200 Subject: [PATCH 0924/1428] Move processing out of UI thread Processing can take several seconds on a mobile, so we should do that in its own thread in order to make the progress icon continue rotating. --- modules/blocks/BlockHeadersChecker.cpp | 27 ++++++++++++++++++-------- modules/blocks/BlockHeadersChecker.h | 10 ++++++---- modules/blocks/DownloadChecker.qml | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 08bc9b7..d244c9c 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -32,6 +32,9 @@ static constexpr const char * HEADERFILE = "https://flowee.org/products/pay/bloc BlockHeadersChecker::BlockHeadersChecker(QObject *parent) : QObject(parent) { + connect (this, &BlockHeadersChecker::finishUp, this, [=](Status status) { + this->setStatus(status); + }, Qt::QueuedConnection); } int BlockHeadersChecker::wantedHeight() const @@ -130,7 +133,6 @@ void BlockHeadersChecker::headerReturned() BlockHeader *bh = reinterpret_cast(bytes); auto hash = bh->createHash(); auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); - bool ok = false; for (const auto &cp : blockchain.checkpoints()) { if (cp.height == m_checkpoint) { isContinuation = cp.hash == hash; @@ -226,7 +228,10 @@ void BlockHeadersChecker::downloadFinished() // before we start the process new headers which is going to take // some actual processing time, depending on how big our headers // are now. - QTimer::singleShot(130, this, SLOT(processNewHeaders())); + // keep the file object from getting garbage collected. + // the map() requires the QFile to stay alive. + m_newHeaders->setParent(FloweePay::instance()); + FloweePay::instance()->ioService().post(std::bind(&BlockHeadersChecker::processNewHeaders, this)); } void BlockHeadersChecker::processNewHeaders() @@ -241,9 +246,19 @@ void BlockHeadersChecker::processNewHeaders() // Knowing that this will never run on Windowz, lets just rename the open file and // place the newly created file at the known path. + struct RAII { + explicit RAII(BlockHeadersChecker *dd) : d(dd) {} + ~RAII() { + emit d->finishUp(status); + } + BlockHeadersChecker *d; + Status status = Success; + }; + RAII raii(this); + if (!m_newHeaders->open(QIODevice::ReadOnly)) { logFatal() << "Failed to re-open replacement static headers"; - setStatus(DiskFailure); + raii.status = DiskFailure; return; } try { @@ -252,9 +267,6 @@ void BlockHeadersChecker::processNewHeaders() auto &blockchain = FloweePay::instance()->p2pNet()->blockchain(); blockchain.replaceStaticChain(m_newHeaders->map(0, m_newHeaders->size()), m_newHeaders->size(), TEMP_DOWNLOAD_FILENAME ".info"); - // keep the file object from getting garbage collected. - // the map() requires the QFile to stay alive. - m_newHeaders->setParent(FloweePay::instance()); m_newHeaders->close(); // make sure we use the new ones after restart. @@ -264,13 +276,12 @@ void BlockHeadersChecker::processNewHeaders() m_newHeaders->rename(PAY_STATIC_HEADERS); } catch (const std::exception &e) { logCritical() << "Replace static chain failed. Reason:" << e; - setStatus(NetworkFailure); + raii.status = NetworkFailure; m_newHeaders->close(); m_newHeaders->remove(); m_newHeaders->deleteLater(); m_newHeaders = nullptr; } - setStatus(Success); } void BlockHeadersChecker::onBytesDownloaded() diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index f2df336..5e2d907 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -28,7 +28,7 @@ class BlockHeadersChecker : public QObject Q_PROPERTY(int wantedHeight READ wantedHeight WRITE setWantedHeight NOTIFY wantedHeightChanged FINAL) Q_PROPERTY(int totalDownload READ totalDownload NOTIFY totalDownloadChanged FINAL) Q_PROPERTY(int bytesDownloaded READ bytesDownloaded NOTIFY bytesDownloadedChanged FINAL) - Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged FINAL) + Q_PROPERTY(Status status READ status NOTIFY statusChanged FINAL) public: BlockHeadersChecker(QObject *parent = nullptr); @@ -56,8 +56,8 @@ public: int totalDownload() const; void setTotalDownload(int newTotalDownload); - Status status() const; void setStatus(Status newStatus); + Status status() const; Q_INVOKABLE void restart(); @@ -65,17 +65,19 @@ signals: void wantedHeightChanged(); void bytesDownloadedChanged(); void totalDownloadChanged(); - void statusChanged(); + // helper signal for threading. + void finishUp(Status status); private slots: void startChecking(); void headerReturned(); void downloadFinished(); - void processNewHeaders(); void onBytesDownloaded(); private: + void processNewHeaders(); + int m_wantedHeight = 0; int m_checkpoint = 0; // start download at checkpoint-height int m_downloadTo = 0; // download to this point (we have the rest) diff --git a/modules/blocks/DownloadChecker.qml b/modules/blocks/DownloadChecker.qml index f61b060..6e05d41 100644 --- a/modules/blocks/DownloadChecker.qml +++ b/modules/blocks/DownloadChecker.qml @@ -61,7 +61,7 @@ Item { Flowee.Label { id: titleLabel - text: qsTr("Downloading Headers") + text: testData.status === Blocks.Checker.Verifying ? qsTr("Processing") : qsTr("Downloading Headers") anchors.horizontalCenter: parent.horizontalCenter } -- 2.54.0 From 0c315370dd25a54a3d364220ec01da4609e02677 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 22:50:16 +0200 Subject: [PATCH 0925/1428] Fixes in CoinSelector This (desktop only) component now works properly with: - Showing the CF-logo for approriate coins. - Clicking on a locked coin no longer selects it. --- guis/desktop/SendTransactionPane.qml | 14 +++++++++----- src/WalletCoinsModel.cpp | 9 ++++----- src/WalletCoinsModel.h | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 3cb02a4..99186d3 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -548,7 +548,7 @@ Item { QQC2.ToolTip { delay: 600 text: qsTr("Locked coins will never be used for payments. Right-click for menu.") - visible: locked && rowMouseArea.containsMouse + visible: model.locked && rowMouseArea.containsMouse } } @@ -557,6 +557,7 @@ Item { id: selectedBox checked: model.selected visible: !lockedRect.visible + enabled: visible } Flowee.Label { id: mainText @@ -577,7 +578,7 @@ Item { id: amountLabel value: model.value anchors.baseline: mainText.baseline - anchors.right: parent.right + anchors.right: fusedIcon.visible ? fusedIcon.left : parent.right // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 } @@ -602,9 +603,11 @@ Item { return; } if (mouse.button === Qt.LeftButton) { - var willCheck = !selectedBox.checked - selectedBox.checked = willCheck - inputsPane.paymentDetail.setRowIncluded(index, willCheck) + if (!model.locked) { + var willCheck = !selectedBox.checked + selectedBox.checked = willCheck + inputsPane.paymentDetail.setRowIncluded(index, willCheck) + } } else { coinsListView.menuIsOpen = true @@ -648,6 +651,7 @@ Item { id: fusedIcon anchors.right: parent.right anchors.verticalCenter: mainText.verticalCenter + visible: model.fused } } } diff --git a/src/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp index 2310f51..a8badb9 100644 --- a/src/WalletCoinsModel.cpp +++ b/src/WalletCoinsModel.cpp @@ -117,11 +117,10 @@ QVariant WalletCoinsModel::data(const QModelIndex &index, int role) const return QVariant(tx->second.minedBlockHeight); break; } - case FusedCount: { + case isFused: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); - if (tx != m_wallet->m_walletTransactions.end()) - return QVariant(tx->second.isCashFusionTx ? 1 : 0); - break; + bool isFused = tx != m_wallet->m_walletTransactions.end() && tx->second.isCashFusionTx; + return QVariant(isFused); } case Address: { const auto tx = m_wallet->m_walletTransactions.find(outRef.txIndex()); @@ -170,7 +169,7 @@ QHash WalletCoinsModel::roleNames() const answer[Value] = "value"; answer[Blockheight] = "blockheight"; answer[Age] = "age"; - answer[FusedCount] = "fusedCount"; + answer[isFused] = "fused"; answer[Address] = "address"; answer[CloakedAddress] = "cloakedAddress"; answer[Locked] = "locked"; diff --git a/src/WalletCoinsModel.h b/src/WalletCoinsModel.h index 49c602d..d9a6ddf 100644 --- a/src/WalletCoinsModel.h +++ b/src/WalletCoinsModel.h @@ -36,7 +36,7 @@ public: Value = Qt::UserRole, // in sats Age, Blockheight, - FusedCount, + isFused, Locked, // bool Address, CloakedAddress, -- 2.54.0 From f31fcbac4dbb05b3f8687945b650df45b784ca1e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 22:59:17 +0200 Subject: [PATCH 0926/1428] hide options irrelevant for a single-wallet user. These two features are only relevant when there are multiple wallets in the users app, so if that isn't the case we simply hide them. --- guis/desktop/AccountDetails.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 31efe0c..6d2c670 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -221,21 +221,25 @@ Item { id: balanceSetting checked: root.account.countBalance onCheckedChanged: root.account.countBalance = checked + visible: !portfolio.singleAccountSetup } Flowee.CheckBoxLabel { Layout.fillWidth: true buddy: balanceSetting text: qsTr("Include balance in total") + visible: balanceSetting.visible } Flowee.CheckBox { id: privateCB checked: root.account.isPrivate onClicked: root.account.isPrivate = checked + visible: balanceSetting.visible } Flowee.CheckBoxLabel { Layout.fillWidth: true buddy: privateCB text: qsTr("Hide in private mode") + visible: balanceSetting.visible } } -- 2.54.0 From 1960bb6260bfd2817e51f39694f8cb323b3653ae Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 23:26:25 +0200 Subject: [PATCH 0927/1428] Improve the import screens a little. This applies the knowledge learned from mobile to desktop too. Also set the initially selected import year to 2023 and avoid long imports for most people. --- guis/desktop/NewAccountImportAccount.qml | 260 +++++++++++------------ guis/mobile/ImportWalletPage.qml | 2 +- 2 files changed, 126 insertions(+), 136 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 27bcfc6..00117b8 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -222,23 +222,6 @@ Item { checked: true } - Flowee.GroupBox { - title: qsTr("Name") - collapsable: false - Layout.fillWidth: true - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - } - } Flowee.GroupBox { title: qsTr("Oldest Transaction") @@ -254,6 +237,7 @@ Item { isMainButton: true; onClicked: { +console.log("start it"); // setting new values here will start the check. privKeyImportHelper.secretType = Wallet.PrivateKey privKeyImportHelper.secret = secretText.text @@ -261,6 +245,7 @@ Item { ImportHelper { id: privKeyImportHelper onCheckingChanged: { +console.log("checking: " + checking); if (checking) return; emptyPrivKeyWarningLabel.visible = false; @@ -273,7 +258,7 @@ Item { oldestTransactionChooser.item.month.currentIndex = monthOnHeight(height) - 1; oldestTransactionChooser.item.year.currentIndex - = yearOnHeight(height) - 2010; + = yearOnHeight(height) - 2011; oldestTransactionChooser.item.enabled = false; privImportStartButton.isMainButton = true; @@ -297,6 +282,25 @@ Item { visible: false } + Flowee.GroupBox { + title: qsTr("New Wallet Name") + collapsable: false + Layout.fillWidth: true + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + } + + Flowee.BigButton { id: privImportStartButton text: qsTr("Start") @@ -306,7 +310,7 @@ Item { if (privKeyImportHelper.resultCount > 0) { var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) } else { - var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2010, oldestTransactionChooser.item.month.currentIndex, 1); + var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2011, oldestTransactionChooser.item.month.currentIndex, 1); options = Pay.createImportedWallet(secretText.text, accountName.text, sh) } options.forceSingleAddress = singleAddress.checked; @@ -335,7 +339,88 @@ Item { enabled: visible Flowee.GroupBox { - title: qsTr("Name") + title: qsTr("Password") + width: parent.width + collapsable: false + Flowee.TextField { + id: passwordField + Layout.fillWidth: true + placeholderText: qsTr("imported wallet password") + } + } + Flowee.BigButton { + id: seedCheckButton + text: qsTr("Discover Details", "online check for wallet details") + enabled: !seedImportHelper.checking + isMainButton: true; + + onClicked: { + // setting new values here will start the check. + seedImportHelper.secretType = inputColumn.typedData + seedImportHelper.secret = secretText.text + seedImportHelper.password = passwordField.text + } + ImportHelper { + id: seedImportHelper + onCheckingChanged: { + if (checking) + return; + emptySeedWarningLabel.visible = false; + if (resultCount === 0) { // empty + seedCheckButton.isMainButton = false; + emptySeedWarningLabel.visible = true; + } + else if (resultCount >= 1) { + // TODO what to do if there are more then 1? + + let height = startHeight(0); + oldestTransactionChooser2.item.month.currentIndex + = monthOnHeight(height) - 1; + oldestTransactionChooser2.item.year.currentIndex + = yearOnHeight(height) - 2011; + oldestTransactionChooser2.item.enabled = false; + derivationPath.text = derivation(0); + derivationPath.enabled = false; + + seedCheckButton.isMainButton = false; + seedStartButton.isMainButton = true; + } + } + } + } + Flowee.GroupBox { + title: qsTr("Oldest Transaction") + width: parent.width + collapsable: false + Loader { + id: oldestTransactionChooser2 + Layout.fillWidth: true + sourceComponent: oldestTransactionChooser_component + } + } + Flowee.GroupBox { + id: derivationLabel + title: qsTr("Derivation") + width: parent.width + collapsable: false + Flowee.TextField { + id: derivationPath + property bool derivationOk: Pay.checkDerivation(text); + Layout.fillWidth: true + text: "m/44'/0'/0'" // What most BCH wallets are created with + color: derivationOk ? palette.text : "red" + } + } + Flowee.Label { + id: emptySeedWarningLabel + color: mainWindow.errorRed + text: qsTr("Nothing found for seed") + visible: false + width: parent.width + } + + Flowee.GroupBox { + title: qsTr("New Wallet Name") width: parent.width collapsable: false visible: { @@ -352,122 +437,28 @@ Item { } } - Flowee.GroupBox { - title: qsTr("Password") - width: parent.width - collapsable: false - Flowee.TextField { - id: passwordField - Layout.fillWidth: true - placeholderText: qsTr("Optional") - } - } - Flowee.GroupBox { - title: qsTr("Details") - width: parent.width - collapsable: false - - Item { - implicitWidth: parent.width - implicitHeight: seedCheckButton.height - Flowee.BigButton { - id: seedCheckButton - text: qsTr("Lookup Details", "online check for wallet details") - enabled: !seedImportHelper.checking - isMainButton: true; - - onClicked: { - // setting new values here will start the check. - seedImportHelper.secretType = inputColumn.typedData - seedImportHelper.secret = secretText.text - seedImportHelper.password = passwordField.text - } - ImportHelper { - id: seedImportHelper - onCheckingChanged: { - if (checking) - return; - emptySeedWarningLabel.visible = false; - if (resultCount === 0) { // empty - seedCheckButton.isMainButton = false; - emptySeedWarningLabel.visible = true; - } - else if (resultCount >= 1) { - // TODO what to do if there are more then 1? - - let height = startHeight(0); - oldestTransactionChooser2.item.month.currentIndex - = monthOnHeight(height) - 1; - oldestTransactionChooser2.item.year.currentIndex - = yearOnHeight(height) - 2010; - oldestTransactionChooser2.item.enabled = false; - derivationPath.text = derivation(0); - derivationPath.enabled = false; - - seedCheckButton.isMainButton = false; - seedStartButton.isMainButton = true; - } - } - } - - // TODO add warning + Flowee.BigButton { + id: seedStartButton + text: qsTr("Start") + anchors.right: parent.right + onClicked: { + if (seedImportHelper.resultCount > 0) { + var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), + seedImportHelper.isElectrumSeed(0)); + } else { + var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2011, oldestTransactionChooser2.item.month.currentIndex, 1); + options = Pay.createImportedHDWallet(secretText.text, passwordField.text, + derivationPath.text, accountName2.text, sh); } - } - Flowee.GroupBox { - id: derivationLabel - title: qsTr("Derivation") - width: parent.width - collapsable: false - Flowee.TextField { - id: derivationPath - property bool derivationOk: Pay.checkDerivation(text); - Layout.fillWidth: true - text: "m/44'/0'/0'" // What most BCH wallets are created with - color: derivationOk ? palette.text : "red" - } - } - Flowee.GroupBox { - title: qsTr("Oldest Transaction") - width: parent.width - collapsable: false - Loader { - id: oldestTransactionChooser2 - Layout.fillWidth: true - sourceComponent: oldestTransactionChooser_component - } - } - Flowee.Label { - id: emptySeedWarningLabel - color: mainWindow.errorRed - text: qsTr("Nothing found for seed") - visible: false - width: parent.width - } - - Flowee.BigButton { - id: seedStartButton - text: qsTr("Start") - Layout.alignment: Qt.AlignRight - onClicked: { - if (seedImportHelper.resultCount > 0) { - var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), - seedImportHelper.isElectrumSeed(0)); - } else { - var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2010, oldestTransactionChooser2.item.month.currentIndex, 1); - options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, sh); + for (let a of portfolio.accounts) { + if (a.id === options.accountId) { + portfolio.current = a; + break; } - - for (let a of portfolio.accounts) { - if (a.id === options.accountId) { - portfolio.current = a; - break; - } - } - newAccountsPane.visible = false; } + newAccountsPane.visible = false; } } @@ -501,15 +492,14 @@ Item { model: { var list = []; let last = new Date().getFullYear(); - for (let i = 2010; i <= last; ++i) { + for (let i = 2011; i <= last; ++i) { list.push(i); } return list; } - currentIndex: 10; + currentIndex: 12; } } } } - } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 7f1f581..8db1c28 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -671,7 +671,7 @@ Page { } return list; } - currentIndex: 10; + currentIndex: 12; } } } -- 2.54.0 From 6950f641f23b298b0a3c61c8baf9b824190a4f60 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 23:33:55 +0200 Subject: [PATCH 0928/1428] Small clarification to docs --- src/PortfolioDataProvider.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PortfolioDataProvider.h b/src/PortfolioDataProvider.h index 46deda4..8f2ecfa 100644 --- a/src/PortfolioDataProvider.h +++ b/src/PortfolioDataProvider.h @@ -46,7 +46,7 @@ class PortfolioDataProvider : public QObject * This returns true when the UI can avoid showing elements that allows the user to * select between different wallets. * - * Notice that when the limitedArchiveView is set to true, we are a single-account-setup + * Notice that when the limitedArchiveView is set to true (default), we are a single-account-setup * simply by having exactly one non-archived wallet. * In the case of the limitedArchiveView being set to false, implying the UI has full * view of the archived wallets, the only way to have a single-account setup is by having -- 2.54.0 From 92e7da561e8dc113d0eae7ec8d27bfb95a562953 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 30 Jun 2024 23:35:12 +0200 Subject: [PATCH 0929/1428] Improve logging for the indexer-service component --- src/IndexerServices.cpp | 6 +++--- src/QMLImportHelper.cpp | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index 76f289a..fda2a07 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -151,7 +151,7 @@ void IndexerServices::save() out.close(); std::filesystem::rename(filebaseStr + "~", filebaseStr); } catch (const std::exception &e) { - logFatal() << "Failed to create electrum.dat file. Permissions issue?" << e; + logFatal(10006) << "Failed to create electrum.dat file. Permissions issue?" << e; } } @@ -317,12 +317,12 @@ void FetchIndexerServicePeers::connectionEstablished() void FetchIndexerServicePeers::disconnected() { - logFatal(); + logFatal(10006) << "disconnected"; } void FetchIndexerServicePeers::socketError(QAbstractSocket::SocketError error) { - logFatal() << error; + logFatal(10006) << "Peer gave error:" << error; } void FetchIndexerServicePeers::socketDataAvailable() diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp index 2e36539..e68b912 100644 --- a/src/QMLImportHelper.cpp +++ b/src/QMLImportHelper.cpp @@ -44,8 +44,8 @@ void QMLImportHelper::checkFinished() { assert(m_checking); if (m_importHandler->errored()) { - logWarning() << "Import lookup failed, lets punish host and retry"; - logInfo() << " host to punish:" << m_importHandler->service(); + logWarning(10006) << "Import lookup failed, lets punish host and retry"; + logInfo(10006) << " host to punish:" << m_importHandler->service(); FloweePay::instance()->indexerServices()->punish(m_importHandler->service(), 60); m_checking = false; // to lure startCheck() into a sense of normalcy startCheck(); @@ -86,6 +86,7 @@ void QMLImportHelper::setError(const QString &newError) if (m_error == newError) return; m_error = newError; + logWarning(10006) << m_error; emit errorChanged(); } -- 2.54.0 From e671d41a105c6097d21673f8e253e5b1124ddbae Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 3 Jul 2024 18:03:17 +0200 Subject: [PATCH 0930/1428] Skip fees when unknown We can't calculate fees when we don't know all inputs. In that case we simply return negative fees, which the UI then does not show. --- guis/desktop/TransactionDetails.qml | 2 +- guis/mobile/TransactionDetails.qml | 2 +- src/TransactionInfo.cpp | 35 ++++++++++++++--------------- src/Wallet_support.cpp | 2 +- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 19e0f33..60d505f 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -158,7 +158,7 @@ QQC2.ApplicationWindow { Flowee.Label { id: feesSection text: qsTr("Fees paid") + ":" - visible: txInfo.createdByUs + visible: txInfo.fees >= 0 Layout.alignment: Qt.AlignRight } Flowee.Label { diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index d7510b6..26b98f8 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -168,7 +168,7 @@ Page { // this account created. PageTitledBox { title: qsTr("Fees paid") - visible: root.infoObject != null && infoObject.createdByUs + visible: infoObject != null && infoObject.fees >= 0 Flowee.Label { text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index 31abb74..b060d52 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -31,25 +31,24 @@ int TransactionInfo::txSize() const double TransactionInfo::fees() const { + if (m_inputs.empty()) + return -1; // aka unknown. qint64 fees = 0; - if (m_createdByUs) { - for (const auto i : m_inputs) { - /* - * We may assume that if a transaction is created by us that this - * means we have all inputs. An assumption that makes sense. - * It does hit the snag that if a user started an import at a later date - * than the input was created that we don't actually have it. We should, but - * we don't. - * - * So, make sure to check our pointers here. - */ - if (i) - fees += i->value(); - } - for (const auto o : m_outputs) { - if (o) - fees -= o->value(); - } + for (const auto i : m_inputs) { + /* + * We can only deduce the fees if we have all inputs. + * The m_inputs gets filled when at least one input of ours is in there, but that + * doesn't mean all of them are there. For instance with cashfusion or other such + * co-operatirvely created transactions. + * So at first one we don't have that we can give up and just return the magic 'unknown' value. + */ + if (i == nullptr) + return -1; + fees += i->value(); + } + for (const auto o : m_outputs) { + if (o) + fees -= o->value(); } return static_cast(fees); } diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index e5e1a26..8230426 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -217,7 +217,7 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) } #ifndef NDEBUG // we created inputs that should ALL be there. - // if they are not, we have an internal data inconsistency. + // if they are not, we have an internal data inconsistency. (unless its a cooperatively created tx. Fusion/Flipstarter etc). if (info->m_createdByUs) { for (int i = 0; i < info->m_inputs.length(); ++i) { if (info->m_inputs.at(i) == nullptr) { -- 2.54.0 From 58de1827e0a43f500251999e1ee4bfdec8ba44b9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 3 Jul 2024 23:06:15 +0200 Subject: [PATCH 0931/1428] Improve display of tx-details The popup showing details of a certain list-item had the down-side that the list item we selected was made darker with the rest of the screen, making it harder to understand the whole info. This change repeats the clicked item inside the popup in a way that makes it immediately clear which transaction we are showing details of. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 139 ++----------------------- guis/mobile/PopupOverlay.qml | 53 ++++++++-- guis/mobile/TransactionListItem.qml | 156 ++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+), 136 deletions(-) create mode 100644 guis/mobile/TransactionListItem.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index dc69369..cff7083 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -82,5 +82,6 @@ mobile/UnlockWalletPanel.qml mobile/UnlockApplication.qml mobile/LockApplication.qml + mobile/TransactionListItem.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 898848c..3aa9c57 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -189,9 +189,6 @@ ListView { delegate: Item { id: transactionDelegate property var placementInGroup: model.placementInGroup - - property double amountBch: isMoved ? model.fundsIn - : (model.fundsOut - model.fundsIn) // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. property bool isMoved: { @@ -200,7 +197,6 @@ ListView { var amount = model.fundsOut - model.fundsIn return amount < 0 && amount > -2500 // then the diff is likely just fees. } - property bool isRejected: model.height === -2 // special height as defined by the wallet width: root.width height: 80 @@ -233,130 +229,8 @@ ListView { border.color: palette.midlight } - Item { - id: ruler - width: parent.width - height: 6 - anchors.verticalCenter: parent.verticalCenter - } - // icon - Image { - source: { - if (model.isFused) - return "qrc:/cf.svg"; - if (model.fundsIn === 0) - var base = "receiving"; - else if (isMoved) - base = "move"; - else - base = "send"; - return "qrc:/tx-"+ base + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - } - width: 45 - height: 45 - x: 20 - smooth: true - anchors.verticalCenter: ruler.verticalCenter - opacity: isRejected ? 0.6 : 1 - } - - Flowee.Label { - id: commentLabel - anchors.bottom: ruler.top - anchors.right: price.visible ? price.left : price.right - anchors.left: parent.left - anchors.leftMargin: 80 - clip: true // future, maybe wordwrap? - font.strikeout: isRejected - text: { - var comment = model.comment - if (comment !== "") - return comment; - - if (model.isCoinbase) - return qsTr("Miner Reward"); - if (model.isFused) - return qsTr("Fused"); - if (model.fundsIn === 0) - return qsTr("Received"); - if (isMoved) - return qsTr("Moved"); - return qsTr("Sent"); - } - } - Flowee.Label { - id: dateLine - anchors.top: ruler.bottom - anchors.left: commentLabel.left - color: isRejected ? mainWindow.errorRed : palette.text; - text: { - var minedHeight = model.height; - if (minedHeight === -2) - return qsTr("Rejected") - var date = model.date; - var today = new Date(); - if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) { - let day = today.getDate(); - if (date.getDate() === day || date.getDate() === day - 1) { - // Then this is an item in the 'today' or the 'yesterday' group. - // specify more specific date/time - return Qt.formatTime(date); - } - } - - return Pay.formatDate(model.date); - } - } - - // price in a green rectangle - Rectangle { - id: price - width: amount.width + 10 - height: amount.height + 8 - anchors.right: parent.right - y: { - if (Pay.activityShowsBch || !Pay.isMainChain) // my bottom at parent verticalCenter - return parent.height / 2 - height - return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter - } - anchors.rightMargin: 20 - radius: 6 - baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: - visible: !model.isFused - - color: (isMoved || amountBch < 0) ? "#00000000" - : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background - Flowee.Label { - id: amount - text: { - var dat = model.date; - if (typeof dat === "undefined") // unconfirmed transactions have no date - var fiatPrice = Fiat.price; - else - fiatPrice = Fiat.historicalPrice(dat); - return Fiat.formattedPrice(Math.abs(amountBch), fiatPrice); - } - anchors.centerIn: parent - opacity: Math.abs(amountBch) < 2000 ? 0.5 : 1 - font.strikeout: isRejected - } - } - Flowee.Label { // plus or minus in front of the price - visible: price.visible && !isMoved - text: amountBch >= 0 ? "+" : "-" - anchors.baseline: price.baseline - anchors.right: price.left - opacity: price.opacity - } - Flowee.BitcoinAmountLabel { - visible: price.visible && (Pay.activityShowsBch || !Pay.isMainChain) - anchors.right: parent.right - anchors.rightMargin: 25 - anchors.baseline: dateLine.baseline - value: amountBch - showFiat: false - fontPixelSize: amount.font.pixelSize * 0.9 - colorize: isMoved === false + TransactionListItem { + anchors.fill: parent } // horizontal separator @@ -374,7 +248,7 @@ ListView { anchors.fill: parent onClicked: { root.currentIndex = index; - var newItem = popupOverlay.open(selectedItem, transactionDelegate); + var newItem = popupOverlay.open(selectedItem, transactionDelegate, overlayTxListItem); newItem.infoObject = portfolio.current.txInfo(model.walletIndex, parent); root.model.freezeModel = true; } @@ -391,6 +265,13 @@ ListView { id: selectedItem TransactionInfoSmall { } } + Component { + id: overlayTxListItem + TransactionListItem { + implicitHeight: 60 + } + + } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 868fbf1..a6d90ac 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,13 +29,16 @@ FocusScope { /** * @param sourceComponent is a Component we set on the loader. * @param target is the visual item we position next to (vertically). + * @param overlayComponent is a component that we place on top of the + * \a 'target' item, but inside of the popup to avoid dimming. * @returns the item instance of the sourceComponent template */ - function open(sourceComponent, target) { + function open(sourceComponent, target, overlayComponent) { thePopup.palette = mainWindow.palette thePopup.width = root.width - 18 thePopup.x = (width - thePopup.width) / 2 thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); + overlayLoader.sourceComponent = overlayComponent; loader.sourceComponent = sourceComponent; // last, as it starts the loading return loader.item; } @@ -64,21 +67,57 @@ FocusScope { } Loader { id: loader - anchors.fill: parent + width: parent.width onLoaded: { - thePopup.height = item.implictHeight - var h = item.implicitHeight + // heightMonitor.target = item; + thePopup.visible = true; + root.forceActiveFocus(); + } + + onHeightChanged: { + if (item == null) + return; + var h = loader.item.implicitHeight + thePopup.height = h + 20; // 20 is the top and bottom inset of the popup var rect = thePopup.sourceRect; + if (overlayLoader.item) { // the overlay is supposed to be at the same position as the sourceRect + var h2 = overlayLoader.height + thePopup.height += h2 + 10; + + if (root.height - rect.bottom >= h) { // fits below + thePopup.y = rect.bottom - h2 - 22; + overlayLoader.y = 0 + loader.y = h2 + 10; + } + else if (h < rect.y) { // fits above + thePopup.y = rect.y - h; + overlayLoader.y = h + 10; + loader.y = 0; + } + else { + thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps + overlayLoader.y = 0 // item above + loader.y = h2 + 10; + } + + return; + } + if (root.height - rect.bottom >= h) // fits below thePopup.y = rect.bottom; else if (h < rect.y) // fits above thePopup.y = rect.y - h; else thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps - thePopup.visible = true; - root.forceActiveFocus(); + } } + Loader { + // use offsets to counter the popup insets + width: parent.width + 40 + x: -20 + id: overlayLoader + } } Keys.onPressed: (event)=> { diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml new file mode 100644 index 0000000..9ef12e9 --- /dev/null +++ b/guis/mobile/TransactionListItem.qml @@ -0,0 +1,156 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee + +Item { + property double amountBch: isMoved ? model.fundsIn + : (model.fundsOut - model.fundsIn) + property bool isRejected: model.height === -2 // special height as defined by the wallet + + implicitWidth: 360 + implicitHeight: 80 + + Item { + id: ruler + width: parent.width + height: 6 + anchors.verticalCenter: parent.verticalCenter + } + + // icon + Image { + source: { + if (model.isFused) + return "qrc:/cf.svg"; + if (model.fundsIn === 0) + var base = "receiving"; + else if (isMoved) + base = "move"; + else + base = "send"; + return "qrc:/tx-"+ base + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + } + width: 45 + height: 45 + x: 20 + smooth: true + anchors.verticalCenter: ruler.verticalCenter + opacity: isRejected ? 0.6 : 1 + } + + Flowee.Label { + id: commentLabel + anchors.bottom: ruler.top + anchors.right: price.visible ? price.left : price.right + anchors.left: parent.left + anchors.leftMargin: 80 + clip: true // future, maybe wordwrap? + font.strikeout: isRejected + text: { + var comment = model.comment + if (comment !== "") + return comment; + + if (model.isCoinbase) + return qsTr("Miner Reward"); + if (model.isFused) + return qsTr("Fused"); + if (model.fundsIn === 0) + return qsTr("Received"); + if (isMoved) + return qsTr("Moved"); + return qsTr("Sent"); + } + } + Flowee.Label { + id: dateLine + anchors.top: ruler.bottom + anchors.left: commentLabel.left + color: isRejected ? mainWindow.errorRed : palette.text; + text: { + var minedHeight = model.height; + if (minedHeight === -2) + return qsTr("Rejected") + var date = model.date; + var today = new Date(); + if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) { + let day = today.getDate(); + if (date.getDate() === day || date.getDate() === day - 1) { + // Then this is an item in the 'today' or the 'yesterday' group. + // specify more specific date/time + return Qt.formatTime(date); + } + } + + return Pay.formatDate(model.date); + } + } + + // price in a green rectangle + Rectangle { + id: price + width: amount.width + 10 + height: amount.height + 8 + anchors.right: parent.right + y: { + if (Pay.activityShowsBch || !Pay.isMainChain) // my bottom at parent verticalCenter + return parent.height / 2 - height + return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter + } + anchors.rightMargin: 20 + radius: 6 + baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: + visible: !model.isFused + + color: (isMoved || amountBch < 0) ? "#00000000" + : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background + Flowee.Label { + id: amount + text: { + var dat = model.date; + if (typeof dat === "undefined") // unconfirmed transactions have no date + var fiatPrice = Fiat.price; + else + fiatPrice = Fiat.historicalPrice(dat); + return Fiat.formattedPrice(Math.abs(amountBch), fiatPrice); + } + anchors.centerIn: parent + opacity: Math.abs(amountBch) < 2000 ? 0.5 : 1 + font.strikeout: isRejected + } + } + Flowee.Label { // plus or minus in front of the price + visible: price.visible && !isMoved + text: amountBch >= 0 ? "+" : "-" + anchors.baseline: price.baseline + anchors.right: price.left + opacity: price.opacity + } + Flowee.BitcoinAmountLabel { + visible: price.visible && (Pay.activityShowsBch || !Pay.isMainChain) + anchors.right: parent.right + anchors.rightMargin: 25 + anchors.baseline: dateLine.baseline + value: amountBch + showFiat: false + fontPixelSize: amount.font.pixelSize * 0.9 + colorize: isMoved === false + } + +} -- 2.54.0 From d89597d89aca90c45c7a3327558557af123fa11c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 3 Jul 2024 23:06:45 +0200 Subject: [PATCH 0932/1428] Remove unneeded bottom padding. This is no longer needed after a typo fix in the popup ovelay class. --- guis/mobile/TransactionInfoSmall.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 4f3af0f..997890b 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -143,5 +143,4 @@ ColumnLayout { newItem.infoObject = root.infoObject; } } - Item { width: 10; height: 4 } // spacing } -- 2.54.0 From 238a5d3f40b670fc067f9233ae71b7d1003507e2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 5 Jul 2024 12:40:16 +0200 Subject: [PATCH 0933/1428] Add indicator for price data being old. When we start up we use the last known price information and also fetch new data. When the user shows the price details popup before the fetch has completed, they may be mislead into thinking that they are looking at current data while they are not. So this shows a bouncy while we are fetching. --- guis/mobile/PriceDetails.qml | 27 ++++++++++++++++++++++++++- src/PriceDataProvider.cpp | 6 ++++++ src/PriceDataProvider.h | 5 ++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/guis/mobile/PriceDetails.qml b/guis/mobile/PriceDetails.qml index 4b751f3..9271a78 100644 --- a/guis/mobile/PriceDetails.qml +++ b/guis/mobile/PriceDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ Item { property int currentPrice: Fiat.price Image { + id: configIcon anchors.right: parent.right width: 16 height: 16 @@ -100,4 +101,28 @@ Item { delegate: historyLabel } } + + // the indicator that the price info is actually old. Typically because we're waiting on the network. + Rectangle { + id: bouncy + color: palette.highlight + width: 16 + height: 16 + anchors.right: configIcon.left + anchors.rightMargin: 6 + visible: opacity > 0 + opacity: Fiat.oldData ? 1 : 0 + property bool up: false + y: up ? 0 : 40 + radius: 8 + + Behavior on y { NumberAnimation { easing.type: Easing.OutQuad } } + Behavior on opacity { NumberAnimation { } } + Timer { + running: parent.visible + interval: 300 + repeat: true + onTriggered: bouncy.up = !bouncy.up + } + } } diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index fce9c9b..bbbb5b6 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -54,6 +54,12 @@ void PriceDataProvider::start() fetch(); } +bool PriceDataProvider::oldData() const +{ + const auto now = time(nullptr); + return m_currentPrice.timestamp == 0 || now - m_currentPrice.timestamp > 3600; +} + void PriceDataProvider::setCurrency(const QLocale &countryLocale) { auto newCurrency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index a6d0d07..32d5f94 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ class PriceDataProvider : public QObject { Q_OBJECT Q_PROPERTY(int price READ price NOTIFY priceChanged) + Q_PROPERTY(bool oldData READ oldData NOTIFY priceChanged) Q_PROPERTY(QString currencyName READ currencyName NOTIFY currencySymbolChanged) Q_PROPERTY(bool displayCents READ displayCents NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) @@ -45,6 +46,8 @@ public: int price() const { return m_currentPrice.price; } + // returns true if the data is more than an hour old. + bool oldData() const; void setCurrency(const QLocale &countryLocale); void setCountry(const QString &countrycode); -- 2.54.0 From 98906ac3e11c3543d561fc87dc8087c106f1cc43 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 5 Jul 2024 13:05:40 +0200 Subject: [PATCH 0934/1428] Make history return timestamp with price this changes the call for historicalPrice() to now take the (int based) timestamp as a ref and we change that to the actually found timestamp upon success. --- src/PriceDataProvider.cpp | 17 +++++++----- src/PriceHistoryDataProvider.cpp | 4 ++- src/PriceHistoryDataProvider.h | 8 +++++- testing/priceHistory/TestPriceHistory.cpp | 33 +++++++++++++++-------- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index bbbb5b6..cc35c21 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -178,16 +178,16 @@ int PriceDataProvider::historicalPrice(const QDateTime ×tamp) const if (m_priceHistory.get() == nullptr) return m_currentPrice.price; - return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch(), - PriceHistoryDataProvider::Nearest); + uint32_t epoch = timestamp.toSecsSinceEpoch(); + return m_priceHistory->historicalPrice(epoch, PriceHistoryDataProvider::Nearest); } int PriceDataProvider::historicalPriceAccurate(const QDateTime ×tamp) const { if (m_priceHistory.get() == nullptr) return 0; - return m_priceHistory->historicalPrice(timestamp.toSecsSinceEpoch(), - PriceHistoryDataProvider::Accurate); + uint32_t epoch = timestamp.toSecsSinceEpoch(); + return m_priceHistory->historicalPrice(epoch, PriceHistoryDataProvider::Accurate); } int PriceDataProvider::historicalPriceAccurate(int days) const @@ -198,8 +198,8 @@ int PriceDataProvider::historicalPriceAccurate(int days) const if (m_priceHistory.get() == nullptr) return 0; - return m_priceHistory->historicalPrice(now.addDays(days * -1).toSecsSinceEpoch(), - PriceHistoryDataProvider::Accurate); + uint32_t timestamp = now.addDays(days * -1).toSecsSinceEpoch(); + return m_priceHistory->historicalPrice(timestamp, PriceHistoryDataProvider::Accurate); } QString PriceDataProvider::priceToStringSimple(int64_t cents) const @@ -353,9 +353,12 @@ void PriceDataProvider::loadPriceHistory(const QString &basedir) m_priceHistory.reset(new PriceHistoryDataProvider(basedir, m_currency)); // take the last known price from our historical module to have something // mostly useful until we manage to fetch the data from the life feeds. - auto lastKnownPrice = historicalPrice(QDateTime::currentDateTimeUtc()); + uint32_t timestamp = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + auto lastKnownPrice = m_priceHistory->historicalPrice(timestamp); if (lastKnownPrice == 0) lastKnownPrice = 10000; // if we never fetched, set to 100,- + else + m_currentPrice.timestamp = timestamp; m_currentPrice.price = lastKnownPrice; connect (this, &PriceDataProvider::priceChanged, m_priceHistory.get(), [=](int price) { diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index 6136e50..f3a4167 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -91,7 +91,7 @@ void PriceHistoryDataProvider::addPrice(const QString ¤cy, uint32_t timest } } -int PriceHistoryDataProvider::historicalPrice(const uint32_t timestamp, HistoricalPriceAccuracy hpa) const +int PriceHistoryDataProvider::historicalPrice(uint32_t ×tamp, HistoricalPriceAccuracy hpa) const { const Currency *data = currencyData(m_currency); int answer = 0; @@ -137,6 +137,7 @@ int PriceHistoryDataProvider::historicalPrice(const uint32_t timestamp, Historic // 12 hours old. if (hpa == Accurate && timestamp - prevTimestamp > 3600 * 12) return 0; + timestamp = prevTimestamp; return answer; } } @@ -174,6 +175,7 @@ int PriceHistoryDataProvider::historicalPrice(const uint32_t timestamp, Historic && hpa == Accurate && prevTimestamp - timestamp > 3600 * 48) return 0; + timestamp = prevTimestamp; return answer; } diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index f83cca4..0853140 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -41,7 +41,13 @@ public: Nearest ///< Return the nearest known price. }; - int historicalPrice(uint32_t timestamp, HistoricalPriceAccuracy = Nearest) const; + /** + * Fetch the historical price and timestamp. + * This fetches the price at a certain timestamp, when found it will return the price and + * update the timestamp referenced value to the real one. + * This will return zero when no price is found that qualifies. + */ + int historicalPrice(uint32_t ×tamp, HistoricalPriceAccuracy = Nearest) const; QString currencyName() const; diff --git a/testing/priceHistory/TestPriceHistory.cpp b/testing/priceHistory/TestPriceHistory.cpp index 409cb48..17f4b19 100644 --- a/testing/priceHistory/TestPriceHistory.cpp +++ b/testing/priceHistory/TestPriceHistory.cpp @@ -34,18 +34,27 @@ void TestPriceHistory::testLog() { const uint32_t TimeStamp = 1650000000; PriceHistoryDataProvider ph(basedir(), "euro"); - QCOMPARE(ph.historicalPrice(TimeStamp), 0); + uint32_t ts = TimeStamp; + QCOMPARE(ph.historicalPrice(ts), 0); ph.addPrice("euro", TimeStamp, 12345); - QCOMPARE(ph.historicalPrice(TimeStamp), 12345); - QCOMPARE(ph.historicalPrice(TimeStamp + 10000), 12345); - QCOMPARE(ph.historicalPrice(TimeStamp - 10000), 12345); + ts = TimeStamp; + QCOMPARE(ph.historicalPrice(ts), 12345); + ts = TimeStamp + 10000; + QCOMPARE(ph.historicalPrice(ts), 12345); + ts = TimeStamp - 10000; + QCOMPARE(ph.historicalPrice(ts), 12345); ph.addPrice("euro", TimeStamp + 10000, 22345); - QCOMPARE(ph.historicalPrice(TimeStamp), 12345); - QCOMPARE(ph.historicalPrice(TimeStamp - 10000), 12345); - QCOMPARE(ph.historicalPrice(TimeStamp + 4000), 12345); - QCOMPARE(ph.historicalPrice(TimeStamp + 5001), 22345); - QCOMPARE(ph.historicalPrice(TimeStamp + 99999), 22345); + ts = TimeStamp; + QCOMPARE(ph.historicalPrice(ts), 12345); + ts = TimeStamp - 10000; + QCOMPARE(ph.historicalPrice(ts), 12345); + ts = TimeStamp + 4000; + QCOMPARE(ph.historicalPrice(ts), 12345); + ts = TimeStamp + 5001; + QCOMPARE(ph.historicalPrice(ts), 22345); + ts = TimeStamp + 99999; + QCOMPARE(ph.historicalPrice(ts), 22345); } class MockPHDP : public PriceHistoryDataProvider @@ -66,13 +75,15 @@ void TestPriceHistory::testFromBlob() static_cast(&ph)->compressLog(); // hack to access protected method for (int i = 5; i < 10; ++i) { - QCOMPARE(ph.historicalPrice(TimeStampBase + Day * i), i + 1); + uint32_t ts = TimeStampBase + Day * i; + QCOMPARE(ph.historicalPrice(ts), i + 1); } } { PriceHistoryDataProvider ph(basedir(), "euro"); for (int i = 0; i < 10; ++i) { - QCOMPARE(ph.historicalPrice(TimeStampBase + Day * i), i + 1); + uint32_t ts = TimeStampBase + Day * i; + QCOMPARE(ph.historicalPrice(ts), i + 1); } } } -- 2.54.0 From 4550d3f512fcfc1ac12fdff7d6478612e28a0c65 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 28 Aug 2024 22:45:47 +0200 Subject: [PATCH 0935/1428] Hungarian currency display update --- src/PriceDataProvider.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index cc35c21..61233cf 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -71,6 +71,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) if (m_currency == QLatin1String("AZN") || m_currency == QLatin1String("ILS") || m_currency == QLatin1String("IRR") + || m_currency == QLatin1String("HUF") || m_currency == QLatin1String("KHR") || m_currency == QLatin1String("KZT") || m_currency == QLatin1String("PLN") @@ -84,6 +85,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) } // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") + || m_currency == QLatin1String("HUF") || m_currency == QLatin1String("NOK") || m_currency == QLatin1String("ARS")); -- 2.54.0 From f3c95766885021f0a976424df5b14ce6637a1314 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 31 Aug 2024 21:49:31 +0200 Subject: [PATCH 0936/1428] Add currency rules for Slovakia / Slovenia THese two countries use the Euro, but unline most Euro countries they put the currency indicator behind the numbers. With Slovenia even avoiding the euro-sign in most places. This adds the specialization based on the phone set country. --- src/FloweePay.cpp | 28 ++++++++++++++++++++++------ src/PriceDataProvider.cpp | 12 +++++++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 6bde26b..27e48fb 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1431,16 +1431,32 @@ QString FloweePay::nameOfUnit(FloweePay::UnitOfBitcoin unit) const } } -void FloweePay::setCountry(const QString &countrycode) +void FloweePay::setCountry(const QString &countrycode_) { - m_prices->setCountry(countrycode); + QString countryCode(countrycode_); + /* + * Some 'countries' are stand-in for a shared currency. Like a bunch of countries + * all sharing the Euro, but we can only have one country that the user selects + * in the list of currencies. + * + * If the user device is configured to be in a certain country, we should give that + * country preference if they share the currency. + */ + QLocale loc(countrycode_); + auto systemLoc = QLocale::system(); + if (loc.currencySymbol(QLocale::CurrencyIsoCode) == systemLoc.currencySymbol(QLocale::CurrencyIsoCode)) { + countryCode = systemLoc.name(); + loc = systemLoc; + } + + m_prices->setCurrency(loc); QSettings appConfig; - appConfig.setValue(CURRENCY_COUNTRY, countrycode); + appConfig.setValue(CURRENCY_COUNTRY, countryCode); auto list = recentCountries(); - if (!list.isEmpty() && list.first() == countrycode) + if (!list.isEmpty() && list.first() == countryCode) return; - list.removeAll(countrycode); // avoid duplicates - list.insert(0, countrycode); + list.removeAll(countryCode); // avoid duplicates + list.insert(0, countryCode); if (list.size() > 5) list.resize(5); appConfig.setValue(CURRENCY_COUNTRIES, list); diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 61233cf..b0e0099 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -68,7 +68,12 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) m_currency = newCurrency; m_currencySymbolPrefix = countryLocale.currencySymbol(QLocale::CurrencySymbol); m_currencySymbolPost.clear(); - if (m_currency == QLatin1String("AZN") + + if (countryLocale.territory() == QLocale::Slovenia) { + m_currencySymbolPrefix.clear(); + m_currencySymbolPost = QLatin1String(" eur"); + } + else if (m_currency == QLatin1String("AZN") || m_currency == QLatin1String("ILS") || m_currency == QLatin1String("IRR") || m_currency == QLatin1String("HUF") @@ -78,8 +83,9 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) || m_currency == QLatin1String("NOK") || m_currency == QLatin1String("PYG") || m_currency == QLatin1String("RUB") - || m_currency == QLatin1String("VND")) { - // these currencies format the name after the numbers part. + || m_currency == QLatin1String("VND") + || countryLocale.territory() == QLocale::Slovakia) { + // these currencies/territories format the name after the numbers part. m_currencySymbolPost = QString(" ") + m_currencySymbolPrefix; m_currencySymbolPrefix.clear(); } -- 2.54.0 From 0ed828c6e31a82e9a5f89eea9dbd94bacfa3cd65 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 7 Sep 2024 19:50:30 +0200 Subject: [PATCH 0937/1428] Simplify --- guis/desktop/main.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index b2dadf1..10bb3d9 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -26,8 +26,8 @@ import QtQuick.Controls.Basic ApplicationWindow { id: mainWindow visible: true - width: Pay.windowWidth === -1 ? 750 : Pay.windowWidth - height: Pay.windowHeight === -1 ? 500 : Pay.windowHeight + width: Pay.windowWidth + height: Pay.windowHeight minimumWidth: 400 minimumHeight: 300 title: "Flowee Pay" -- 2.54.0 From a9b4f60f0b850e9f2cf991b2878716004d87c9c5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 7 Sep 2024 19:53:27 +0200 Subject: [PATCH 0938/1428] Follow API change in Flowee Libs --- src/Payment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 62689b0..493b289 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -208,7 +208,7 @@ void Payment::prepare() ok = true; } else if (c.type == CashAddress::SCRIPT_TYPE) { - builder.pushOutputPay2Script(CScriptID(reinterpret_cast(c.hash.data()))); + builder.pushOutputPay2Script(c.hash); ok = true; } assert(ok); // mismatch between PaymentDetailOutput::setAddress and this method... -- 2.54.0 From ea4435f9d0ddc7535756ee56fad8c39838a97631 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 7 Sep 2024 20:47:05 +0200 Subject: [PATCH 0939/1428] Follow Tx::Iterator change in Flowee Libs The Tx::Iterator no longer returns the token data as part of the output-script, but instead sees them as separate tags. This removes most of the code here, and makes future deeper support of cashtokens much easier. --- src/Wallet.cpp | 41 ++++++----------------------------------- src/Wallet.h | 2 +- 2 files changed, 7 insertions(+), 36 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 748f1fd..ab47c64 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -179,8 +179,11 @@ Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, co ++outputIndex; output.value = iter.longData(); } + else if (iter.tag() == Tx::CashTokenCategory) { + output.holdsCashToken = true; + } else if (iter.tag() == Tx::OutputScript) { - output.walletSecretId = findSecretFor(iter.byteData(), output.holdsCashToken); + output.walletSecretId = findSecretFor(iter.byteData()); if (output.walletSecretId > 0) { logDebug(LOG_WALLET) << " output"<< outputIndex << "pays to wallet id" << output.walletSecretId; wtx.outputs.insert(std::make_pair(outputIndex, output)); @@ -939,43 +942,11 @@ void Wallet::performUpgrades() } } -int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const +int Wallet::findSecretFor(const Streaming::ConstBuffer &outputScript) const { - auto theScript(outputScript); - isCashToken = false; - constexpr int MinTokenSize = /* prefix */ 1 + /* category */ + 32 + /* flags */ 1; - if (outputScript.size() > MinTokenSize - && static_cast(outputScript.begin()[0]) == 0xef) { - /* - * This output holds a cash-token. - * We eat the bytes that hold the cashtoken data and continue identifying the outputScript - */ - const uint8_t* tokenData = reinterpret_cast(outputScript.begin()); - uint8_t flags = tokenData[33]; - int skipAmount = MinTokenSize; - if ((flags & 0x40) == 0x40) // has-commitment-length bit is set. - skipAmount += tokenData[skipAmount++]; - if ((flags & 0x10) == 0x10) { // has-amount bit is set. - const uint8_t amount = tokenData[skipAmount++]; - if (amount == 253) // compact-size design. - skipAmount += 2; - else if (amount == 254) - skipAmount += 4; - else if (amount == 255) - skipAmount += 8; - } - - isCashToken = true; - if (outputScript.size() <= skipAmount) { - logWarning() << "Invalid token data"; - return -1; - } - theScript = outputScript.mid(skipAmount); - } - std::vector > vSolutions; Script::TxnOutType whichType; - if (!Script::solver(theScript, whichType, vSolutions)) + if (!Script::solver(outputScript, whichType, vSolutions)) return -1; KeyId keyID; diff --git a/src/Wallet.h b/src/Wallet.h index a572a4e..680f4e1 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -431,7 +431,7 @@ private: * * @return the wallet-secret-id or -1 if none match. */ - int findSecretFor(const Streaming::ConstBuffer &outputScript, bool &isCashToken) const; + int findSecretFor(const Streaming::ConstBuffer &outputScript) const; enum RebuildBloomOption { ForceBuild, -- 2.54.0 From a49f6ee0a1c22fa90092db843b3d3196767ecb8a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Sep 2024 22:51:10 +0200 Subject: [PATCH 0940/1428] Add basic support for bip21 op-return argument. This takes the concept added some 5 years ago to Electron-Cash and adds this to Flowee Pay as well. In a nutshell, it allows a bip21 style payment request to include a 'comment' with the argument op_return_raw This comment is expected to be a hex-encoded data payload that then will be added as a single output to the transaction we build to fullfill the payment request. As a natural consequence of how uri's work, adding the argument multiple times will cause multiple outputs to be generated. We check that this does not exceed the expected max sizing for op-return. --- src/CMakeLists.txt | 1 + src/Payment.cpp | 24 +++++++++ src/Payment.h | 4 +- src/PaymentDetailComment.cpp | 95 ++++++++++++++++++++++++++++++++++++ src/PaymentDetailComment_p.h | 62 +++++++++++++++++++++++ src/PaymentProtocol.cpp | 11 +++++ 6 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/PaymentDetailComment.cpp create mode 100644 src/PaymentDetailComment_p.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ef074f..2741c99 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ set (PAY_SOURCES PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp + PaymentDetailComment.cpp QRScanner.cpp PaymentProtocol.cpp diff --git a/src/Payment.cpp b/src/Payment.cpp index 493b289..fed12e1 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -25,6 +25,7 @@ #include "AccountConfig.h" #include "PaymentProtocol.h" #include "TxInfoObject.h" +#include "PaymentDetailComment_p.h" #include #include @@ -217,6 +218,22 @@ void Payment::prepare() else if (detail->type() == InputSelector) { inputs = detail->toInputs(); } + else if (detail->type() == CommentOutput) { + const auto co = dynamic_cast(detail); + builder.appendOutput(/* amount sats = */ 0); + CScript s; + s << OP_RETURN; + auto string = co->commentStr(); + if (!string.isEmpty()) { + auto qtbytes = string.toUtf8(); + std::vector bytes(qtbytes.begin(), qtbytes.end()); + s << bytes; + } else { + auto bytes = co->commentBytes(); + s << std::vector(bytes.begin(), bytes.end()); + } + builder.pushOutputScript(s); + } } auto tx = builder.createTransaction(); @@ -682,6 +699,13 @@ PaymentDetail *Payment::addInputSelector() return detail; } +PaymentDetail *Payment::addCommentOutput() +{ + auto detail = new PaymentDetailComment(this); + addDetail(detail); + return detail; +} + void Payment::remove(PaymentDetail *detail) { const auto count = m_paymentDetails.removeAll(detail); diff --git a/src/Payment.h b/src/Payment.h index 0d1a45a..e6725bf 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -77,7 +77,8 @@ class Payment : public QObject public: enum DetailType { InputSelector, - PayToAddress + PayToAddress, + CommentOutput // aka op-return }; /** @@ -178,6 +179,7 @@ public: Q_INVOKABLE void reset(); Q_INVOKABLE PaymentDetail* addExtraOutput(); Q_INVOKABLE PaymentDetail* addInputSelector(); + Q_INVOKABLE PaymentDetail* addCommentOutput(); Q_INVOKABLE void remove(PaymentDetail *detail); /** * Set a should-be-correct address and return true if valid. diff --git a/src/PaymentDetailComment.cpp b/src/PaymentDetailComment.cpp new file mode 100644 index 0000000..21a5ab0 --- /dev/null +++ b/src/PaymentDetailComment.cpp @@ -0,0 +1,95 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "PaymentDetailComment_p.h" +#include + +PaymentDetailComment::PaymentDetailComment(Payment *parent) + : PaymentDetail(parent, Payment::CommentOutput) +{ +} + +QString PaymentDetailComment::commentStr() const +{ + return m_commentStr; +} + +void PaymentDetailComment::setCommentStr(const QString &cs) +{ + assert(!m_editable); + if (!m_editable) + return; + if (m_commentStr == cs) + return; + m_commentStr = cs; + emit commentStrChanged(); + checkValid(); +} + +bool PaymentDetailComment::editable() const +{ + return m_editable; +} + +void PaymentDetailComment::setEditable(bool on) +{ + if (m_editable == on) + return; + m_editable = on; + emit editableChanged(); +} + +void PaymentDetailComment::setCommentBytes(const Streaming::ConstBuffer &comment) +{ + m_commentBytes = comment; + checkValid(); +} + +Streaming::ConstBuffer PaymentDetailComment::commentBytes() const +{ + return m_commentBytes; +} + +void PaymentDetailComment::checkValid() +{ + // the only validity check is that the total transaction doesn't exceed the number of + // op return max bytes. + + auto payment = qobject_cast(parent()); + assert(payment); + int size = 0; + const int MAX_COMMENT_SIZE = 223; + for (const auto detail_ : payment->paymentDetails()) { + const auto detail = qobject_cast(detail_); + if (detail) { + assert(detail->type() == Payment::CommentOutput); + // The actual comment is preceeded by the op-return and push opcodes which we have + // to count too. + int dataSize = detail->commentStr().toUtf8().size(); + if (dataSize == 0) + dataSize = detail->commentBytes().size(); + if (dataSize == 0) + size += 1; + else if (dataSize < OP_PUSHDATA1) + size += 2 + dataSize; + else + size += 3 + dataSize; + } + } + setValid(size <= MAX_COMMENT_SIZE); +} diff --git a/src/PaymentDetailComment_p.h b/src/PaymentDetailComment_p.h new file mode 100644 index 0000000..7601bd4 --- /dev/null +++ b/src/PaymentDetailComment_p.h @@ -0,0 +1,62 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef PAYMENT_DETAIL_COMMENT_H +#define PAYMENT_DETAIL_COMMENT_H + +#include "Payment.h" + +class PaymentDetailComment : public PaymentDetail +{ + Q_OBJECT + Q_PROPERTY(QString commentString READ commentStr WRITE setCommentStr NOTIFY commentStrChanged FINAL) + /** + * An output created by a payment protocol is not editable by users. + */ + Q_PROPERTY(bool editable READ editable WRITE setEditable NOTIFY editableChanged FINAL) +public: + explicit PaymentDetailComment(Payment *parent); + + QString commentStr() const; + void setCommentStr(const QString &newCommentStr); + + bool editable() const; + void setEditable(bool newEditable); + + void setCommentBytes(const Streaming::ConstBuffer &comment); + Streaming::ConstBuffer commentBytes() const; + +signals: + void commentStrChanged(); + void editableChanged(); + +private: + void checkValid(); + + bool m_editable = true; + QString m_commentStr; + Streaming::ConstBuffer m_commentBytes; +}; + +#if 0 +inline PaymentDetailComment* PaymentDetail::toOutput() { + assert(m_type == Payment::PayToAddress); + return static_cast(this); +} +#endif + +#endif diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 6cf850d..890397d 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -16,10 +16,12 @@ * along with this program. If not, see . */ #include "PaymentProtocol.h" +#include "PaymentDetailComment_p.h" #include "PaymentProtocol_p.h" #include "Payment.h" #include "PaymentDetailOutput_p.h" +#include #include #include #include @@ -106,6 +108,15 @@ void PaymentProtocolBip21::setUri(const QString &uri) // message goes on the main payment.. m_payment->setUserComment(item.second); } + else if (item.first == "op_return_raw") { + // the argument should be a hex string. + auto str = item.second.toStdString(); + auto pool =Streaming::pool(str.size()); + pool->writeHex(str.c_str()); + auto *commentOutput = dynamic_cast(m_payment->addCommentOutput()); + commentOutput->setCommentBytes(pool->commit()); + commentOutput->setEditable(false); + } } out->setAddress(addressOrURL.left(urlStart)); -- 2.54.0 From 0a8b44c56280de5090728d294ad4076f4a01db53 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Sep 2024 23:00:02 +0200 Subject: [PATCH 0941/1428] Fix costness issue. As found by KDE/clazy. --- src/PaymentProtocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 890397d..4f6dbaa 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -299,7 +299,7 @@ void PaymentProtocolBip70::fetchedRequest() m_payment->setUserComment(m_memo); assert(m_payment->paymentDetails().size() == 1); - auto *firstOut = dynamic_cast(m_payment->paymentDetails().first()); + auto *firstOut = dynamic_cast(m_payment->paymentDetails().constFirst()); assert(firstOut); // lets add all the outputs for (const auto &out : m_outputs) { -- 2.54.0 From 17e05d69a2a5c0b489c35e2efc3e80ce7347f26f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Sep 2024 21:22:52 +0200 Subject: [PATCH 0942/1428] Loosen reading from Payment methods The Payment specific methods paymentAmount and paymentAmountFiat are now made to return the data from the first output and no longer required there to be exactly one. This allows these methods to still give useful info in the case of payment protocol based creation of more complex payment objects. --- src/Payment.cpp | 36 ++++++++++++++++++++++++++---------- src/Payment.h | 30 +++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index fed12e1..052feb6 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -61,11 +61,26 @@ int Payment::feePerByte() PaymentDetailOutput *Payment::soleOut() const { - if (m_paymentDetails.size() != 1) // don't mix modes. - throw std::runtime_error("Don't mix payment modes, use the details"); - auto out = dynamic_cast(m_paymentDetails.first()); - assert(out); - return out; + PaymentDetailOutput *rc = nullptr; + for (auto *d : m_paymentDetails) { + if (d->isOutput()) { + if (rc) // found a second! + throw std::runtime_error("Don't mix payment modes, use the details"); + rc = d->toOutput(); + } + } + assert(rc); // internal state, there should always be at least one output + return rc; +} + +PaymentDetailOutput *Payment::firstOut() const +{ + for (auto *d : m_paymentDetails) { + if (d->isOutput()) + return d->toOutput(); + } + assert(false); // internal state, there should always be at least one output + return nullptr; } void Payment::setPaymentAmount(double amount) @@ -93,7 +108,8 @@ bool Payment::pasteTargetAddress(const QString &address) } } - auto out = soleOut(); + auto out = firstOut(); + assert(out); out->setAddress(address.trimmed()); return !out->formattedTarget().isEmpty(); } @@ -103,19 +119,19 @@ void Payment::setTargetAddress(const QString &address) pasteTargetAddress(address); } -QString Payment::targetAddress() +QString Payment::targetAddress() const { - return soleOut()->address(); + return firstOut()->address(); } QString Payment::formattedTargetAddress() const { - return soleOut()->formattedTarget(); + return firstOut()->formattedTarget(); } QString Payment::niceAddress() const { - return soleOut()->niceAddress(); + return firstOut()->niceAddress(); } bool Payment::walletNeedsPin() const diff --git a/src/Payment.h b/src/Payment.h index e6725bf..b84647d 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,26 @@ class PaymentDetailInputs; class AccountInfo; class TxInfoObject; - +/** + * This represents a single payment, possibly a complex one. + * + * Using the Payment object we allow building a single transaction that can vary in + * complexity from paying one single address. Using 'PaymentDetails' the payment can + * be enriched to excert more control over what funds are sent to what remote parties. + * + * To enable simple and direct usability of this class there is a small subset of features + * available directly on the Payment object. Avoiding the need to create or iterate the child + * details objects. + * + * For this we recognize two modes; either there is exactly one payment-destination (output) + * or there are additional details. + * In the case of the simple, 1-desination setup we provide the setPaymentAmount() and + * setPaymentAmountFiat() methods. Represented in the properties paymentAmount and paymentAmountFiat. + * + * When the UI allows adding more details, or scanning a payment protocol (which may do the same) + * you should avoid writing to those two properties and if you do so anyway we may throw an exception + * which will cause the application to abort. + */ class Payment : public QObject { Q_OBJECT @@ -152,7 +171,7 @@ public: * This method can no longer be used after addExtraOutput has been called. * This method assumes a single output, which is the default for this class. */ - QString targetAddress(); + QString targetAddress() const; /** * Returns the validated and formatted address to pay to. * @@ -292,9 +311,10 @@ private: void doAutoPrepare(); friend class PaymentDetailOutput; - /// Helper method to get the output, assuming that is the only detail. - /// Will throw if the Payment has more than one detail. + /// Helper methods to get the output, assuming that is the only output. + /// Will throw if the Payment has more than one. PaymentDetailOutput *soleOut() const; + PaymentDetailOutput *firstOut() const; void addDetail(PaymentDetail*); AccountInfo *m_account = nullptr; -- 2.54.0 From aa6c3b68ce7661d9df27da4dd2edda85fa742864 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Sep 2024 21:27:10 +0200 Subject: [PATCH 0943/1428] Help avoid mallocs; mark CoW containers const --- src/Payment.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 052feb6..39d42d0 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -164,7 +164,7 @@ void Payment::decrypt(const QString &password) bool Payment::validate() { int64_t output = 0; - for (auto detail : m_paymentDetails) { + for (auto detail : std::as_const(m_paymentDetails)) { if (!detail->valid()) return false; if (detail->isOutput()) @@ -200,7 +200,7 @@ void Payment::prepare() qint64 totalOut = 0; bool seenMaxAmount = false; // set to true if the last output detail was set to 'Max' PaymentDetailInputs *inputs = nullptr; - for (auto detail : m_paymentDetails) { + for (auto detail : std::as_const(m_paymentDetails)) { if (detail->type() == PayToAddress) { assert(!seenMaxAmount); // only allowed once. auto o = detail->toOutput(); @@ -431,7 +431,7 @@ void Payment::reset() m_userComment.clear(); m_wallet = nullptr; - for (auto d : m_paymentDetails) { + for (auto d : std::as_const(m_paymentDetails)) { d->deleteLater(); } m_paymentDetails.clear(); @@ -603,7 +603,7 @@ void Payment::setCurrentAccount(AccountInfo *account) emit walletPinChanged(); if (account) { - for (auto detail : m_paymentDetails) { + for (auto detail : std::as_const(m_paymentDetails)) { detail->setWallet(account->wallet()); } doAutoPrepare(); @@ -622,7 +622,7 @@ void Payment::setFiatPrice(int pricePerCoin) m_fiatPrice = pricePerCoin; emit fiatPriceChanged(); - for (auto *detail : m_paymentDetails) { + for (auto *detail : std::as_const(m_paymentDetails)) { if (detail->isOutput()) { auto out = detail->toOutput(); assert(out); @@ -691,7 +691,7 @@ Payment::BroadcastStatus Payment::broadcastStatus() const PaymentDetail *Payment::addExtraOutput() { // only the last in the sequence can have 'max' - for (auto d : m_paymentDetails) { + for (auto d : std::as_const(m_paymentDetails)) { if (d->isOutput()) d->toOutput()->setMaxAllowed(false); } @@ -703,7 +703,7 @@ PaymentDetail *Payment::addExtraOutput() PaymentDetail *Payment::addInputSelector() { // only one input selector allowed - for (auto d : m_paymentDetails) { + for (auto d : std::as_const(m_paymentDetails)) { if (d->isInputs()) { // un-collapse, but not add d->setCollapsed(false); -- 2.54.0 From 410763fc6fa75c5ca7cddc395b4ed607fef29e16 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Sep 2024 16:02:24 +0200 Subject: [PATCH 0944/1428] minor; consistent naming. --- guis/desktop/SendTransactionPane.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 99186d3..6df9d75 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -578,7 +578,7 @@ Item { id: amountLabel value: model.value anchors.baseline: mainText.baseline - anchors.right: fusedIcon.visible ? fusedIcon.left : parent.right + anchors.right: cfIcon.visible ? cfIcon.left : parent.right // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 } @@ -648,7 +648,7 @@ Item { } } Flowee.CFIcon { - id: fusedIcon + id: cfIcon anchors.right: parent.right anchors.verticalCenter: mainText.verticalCenter visible: model.fused -- 2.54.0 From f1f97f5347c3c7de1094853592dcb77f601f5963 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 1 Oct 2024 17:55:05 +0200 Subject: [PATCH 0945/1428] Add op-return creation UI for desktop --- guis/desktop/PaymentTweakingPanel.qml | 54 ++++++++++++++------------- guis/desktop/SendTransactionPane.qml | 51 +++++++++++++++++++++++-- guis/mobile/CurrencySelector.qml | 14 +++++++ src/Payment.h | 5 +++ src/PaymentDetailComment.cpp | 47 ++++++++++++++++------- src/PaymentDetailComment_p.h | 24 +++++++++--- 6 files changed, 148 insertions(+), 47 deletions(-) diff --git a/guis/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml index faaa86e..69128be 100644 --- a/guis/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee Item { @@ -70,9 +71,9 @@ Item { id: content y: 20 opacity: priv.collapsed ? 0 : 1 - visible: opacity != 0 + visible: opacity !== 0 width: parent.width - height: randomRect.height + title.height + 30 + height: detailsLayout.height + title.height + 30 Flowee.Label { id: title @@ -80,38 +81,41 @@ Item { font.pointSize: 20 anchors.horizontalCenter: parent.horizontalCenter } - Rectangle { - id: randomRect - width: inputSelectorButton.width + 30 - height: width + GridLayout { + id: detailsLayout + width: parent.width - 20 + x: 10 anchors.top: title.bottom anchors.topMargin: 10 - x: 10 - - color: palette.light - border.width: 2 - border.color: palette.shadow + columns: 2 + columnSpacing: 10 Flowee.Button { - id: inputSelectorButton text: qsTr("Coin Selector") - y: 15 - anchors.horizontalCenter: parent.horizontalCenter + Layout.alignment: Qt.AlignTop onClicked: { payment.addInputSelector(); priv.collapsed = true } } - } - Flowee.Label { - id: helpText - anchors.top: randomRect.top - anchors.topMargin: 20 - anchors.left: randomRect.right - anchors.leftMargin: 10 - anchors.right: parent.right - wrapMode: Flowee.Label.Wrap - text: qsTr("To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible.") + Flowee.Label { + Layout.fillWidth: true + wrapMode: Flowee.Label.Wrap + text: qsTr("To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible.") + } + Flowee.Button { + text: qsTr("Comment") + Layout.alignment: Qt.AlignTop + onClicked: { + payment.addCommentOutput(); + priv.collapsed = true + } + } + Flowee.Label { + Layout.fillWidth: true + wrapMode: Flowee.Label.Wrap + text: qsTr("This allows adding a public comment, that will be included in the transaction and seen by everyone.") + } } // line diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 6df9d75..992a33d 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -81,9 +81,11 @@ Item { height: status === Loader.Ready ? item.implicitHeight : 0 sourceComponent: { if (modelData.type === Payment.PayToAddress) - return destinationFields + return destinationFields; if (modelData.type === Payment.InputSelector) - return inputFields + return inputFields; + if (modelData.type === Payment.CommentOutput) + return opReturnInput; return null; // should never happen } onLoaded: item.paymentDetail = modelData @@ -657,4 +659,47 @@ Item { } } } + + /* + * Op-Return comment / data field. + */ + Component { + id: opReturnInput + Flowee.GroupBox { + property QtObject paymentDetail: null + + collapsable: paymentDetail.collapsable + onEffectiveCollapsedChanged: paymentDetail.collapsed = effectiveCollapsed + collapsed: paymentDetail.collapsed + title: qsTr("Public-comment") + columns: 2 + + Flowee.Label { + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.columnSpan: 2 + text: { + if (paymentDetail.editable) // user-created. + return qsTr("Add a comment you want to include in the transaction, visible for everyone.") + return qsTr("Custom message, to be included in the transaction.") + } + font.italic: true + } + Flowee.Label { + text: qsTr("Text") + ":" + } + Flowee.TextField { + id: textData + Layout.fillWidth: true + text: paymentDetail.editable ? paymentDetail.commentString : paymentDetail.preview + enabled: paymentDetail.editable + onTextChanged: if (paymentDetail.editable) paymentDetail.commentString = text + } + Flowee.Label { + text: qsTr("Size")+ ":" + } + Flowee.Label { + text: paymentDetail.size + } + } + } } diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index f942193..7cee8a0 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -91,6 +91,20 @@ Page { anchors.leftMargin: 10 anchors.right: parent.right text: { + /* + * On top of the Locale issues as QTBUG-116527 describes, + * Qt 65 misses several actual texts. + * Here are the work-arounds: + * + * hi + * ko + * si + * my + * ru + * th + * uk + * + */ var loc = Qt.locale(modelData); return loc.currencySymbol(2) + " [" + loc.currencySymbol(1) + "]"; // return loc.currencySymbol(Locale.CurrencyDisplayName) diff --git a/src/Payment.h b/src/Payment.h index b84647d..590eea9 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -29,6 +29,7 @@ class Wallet; class PaymentDetail; class PaymentDetailOutput; class PaymentDetailInputs; +class PaymentDetailComment; class AccountInfo; class TxInfoObject; @@ -364,8 +365,12 @@ public: inline bool isInputs() const { return m_type == Payment::InputSelector; } + inline bool isComment() const { + return m_type == Payment::CommentOutput; + } PaymentDetailOutput *toOutput(); PaymentDetailInputs *toInputs(); + PaymentDetailComment *toComment(); bool valid() const; diff --git a/src/PaymentDetailComment.cpp b/src/PaymentDetailComment.cpp index 21a5ab0..4dc74a1 100644 --- a/src/PaymentDetailComment.cpp +++ b/src/PaymentDetailComment.cpp @@ -31,14 +31,22 @@ QString PaymentDetailComment::commentStr() const void PaymentDetailComment::setCommentStr(const QString &cs) { - assert(!m_editable); + assert(m_editable); if (!m_editable) return; if (m_commentStr == cs) return; m_commentStr = cs; emit commentStrChanged(); + if (!m_commentBytes.isEmpty()) // the string one is now leading + setCommentBytes(Streaming::ConstBuffer()); checkValid(); + emit sizeChanged(); +} + +QString PaymentDetailComment::preview() const +{ + return QString::fromUtf8(m_commentBytes.toString()); } bool PaymentDetailComment::editable() const @@ -57,6 +65,11 @@ void PaymentDetailComment::setEditable(bool on) void PaymentDetailComment::setCommentBytes(const Streaming::ConstBuffer &comment) { m_commentBytes = comment; + if (!m_commentStr.isEmpty()) { // the bytes one is now leading. + m_commentStr.clear(); + emit commentStrChanged(); + } + emit sizeChanged(); checkValid(); } @@ -65,6 +78,21 @@ Streaming::ConstBuffer PaymentDetailComment::commentBytes() const return m_commentBytes; } +int PaymentDetailComment::size() const +{ + int payloadSize = m_commentBytes.size(); + if (payloadSize == 0 && !m_commentStr.isEmpty()) + payloadSize = m_commentStr.toUtf8().size(); + + // The actual comment is preceeded by the op-return and push opcodes which we have + // to count too. + if (payloadSize == 0) + return 1; + else if (payloadSize < OP_PUSHDATA1) + return 2 + payloadSize; + return 3 + payloadSize; +} + void PaymentDetailComment::checkValid() { // the only validity check is that the total transaction doesn't exceed the number of @@ -73,22 +101,13 @@ void PaymentDetailComment::checkValid() auto payment = qobject_cast(parent()); assert(payment); int size = 0; - const int MAX_COMMENT_SIZE = 223; - for (const auto detail_ : payment->paymentDetails()) { + const int MAX_COMMENT_SIZE = 223; // is-standard rule from 2021 + const auto details = payment->paymentDetails(); + for (const auto detail_ : details) { const auto detail = qobject_cast(detail_); if (detail) { assert(detail->type() == Payment::CommentOutput); - // The actual comment is preceeded by the op-return and push opcodes which we have - // to count too. - int dataSize = detail->commentStr().toUtf8().size(); - if (dataSize == 0) - dataSize = detail->commentBytes().size(); - if (dataSize == 0) - size += 1; - else if (dataSize < OP_PUSHDATA1) - size += 2 + dataSize; - else - size += 3 + dataSize; + size += detail->size(); } } setValid(size <= MAX_COMMENT_SIZE); diff --git a/src/PaymentDetailComment_p.h b/src/PaymentDetailComment_p.h index 7601bd4..cb5bca0 100644 --- a/src/PaymentDetailComment_p.h +++ b/src/PaymentDetailComment_p.h @@ -20,29 +20,45 @@ #include "Payment.h" +/* + * The comment-detail object represents a single op-return output. + * + * When the payload is set using setCommentBytes, the detail should be not editable + * since it makes no sense to provide the user the option to edit a byte-array. + * The preview() will try to find text content from it, in a lossy manner. + * + * User editable objects should use commentStr which is a normal property. + */ class PaymentDetailComment : public PaymentDetail { Q_OBJECT Q_PROPERTY(QString commentString READ commentStr WRITE setCommentStr NOTIFY commentStrChanged FINAL) + Q_PROPERTY(QString preview READ preview CONSTANT FINAL) /** * An output created by a payment protocol is not editable by users. */ Q_PROPERTY(bool editable READ editable WRITE setEditable NOTIFY editableChanged FINAL) + Q_PROPERTY(int size READ size NOTIFY sizeChanged FINAL) public: explicit PaymentDetailComment(Payment *parent); QString commentStr() const; void setCommentStr(const QString &newCommentStr); + QString preview() const; + bool editable() const; - void setEditable(bool newEditable); + void setEditable(bool edit); void setCommentBytes(const Streaming::ConstBuffer &comment); Streaming::ConstBuffer commentBytes() const; + int size() const; + signals: void commentStrChanged(); void editableChanged(); + void sizeChanged(); private: void checkValid(); @@ -52,11 +68,9 @@ private: Streaming::ConstBuffer m_commentBytes; }; -#if 0 -inline PaymentDetailComment* PaymentDetail::toOutput() { - assert(m_type == Payment::PayToAddress); +inline PaymentDetailComment* PaymentDetail::toComment() { + assert(m_type == Payment::CommentOutput); return static_cast(this); } -#endif #endif -- 2.54.0 From 4cdb393f20ec80a7267a5e9e4fc452ec8b12dd36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 1 Oct 2024 19:57:17 +0200 Subject: [PATCH 0946/1428] Add a recycle icon for the delete-swipe --- modules/build-transaction/PayToOthers.qml | 33 +++++++++---------- .../build-transactions-data.qrc | 2 ++ modules/build-transaction/recycle-light.svg | 5 +++ modules/build-transaction/recycle.svg | 4 +++ 4 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 modules/build-transaction/recycle-light.svg create mode 100644 modules/build-transaction/recycle.svg diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 0097d43..b3b1b77 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -409,7 +409,7 @@ Page { // check the comment at loaderForPayments to understand this one function pushToThePile(componentId, detail) { thePile.push(loaderForPayments, - {"paymentDetail": detail, + { "paymentDetail": detail, "sourceComponent": componentId } ); } @@ -463,8 +463,7 @@ Page { property bool editStarted: false onXChanged: { - /* When we move, we move the icons etc with us. - */ + /* When we move, we move the icons etc with us. */ if (x < 0) { leftArea.x = leftArea.width * -1 // out of screen // moving left, opening the edit option @@ -525,19 +524,19 @@ Page { opacity: 0 anchors.fill: parent color: mainWindow.errorRedBg - } - - Rectangle { - id: trashcan - color: "orange" // TODO replace this with an icon - width: 40 - height: 40 - y: 10 - x: { - let newX = parent.width - width; - let moved = parent.x + parent.width; - let additional = Math.max(0, Math.min(moved - width, 8)); - return newX - additional; + radius: 6 + Image { + id: trashcan + source: "./recycle" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + width: 40 + height: 40 + y: 10 + x: { + let newX = parent.width - width; + let moved = parent.x + parent.width; + let additional = Math.max(0, Math.min(moved - width, 8)); + return newX - additional; + } } } diff --git a/modules/build-transaction/build-transactions-data.qrc b/modules/build-transaction/build-transactions-data.qrc index cb06914..4606eb5 100644 --- a/modules/build-transaction/build-transactions-data.qrc +++ b/modules/build-transaction/build-transactions-data.qrc @@ -3,5 +3,7 @@ PayToOthers.qml edit.svg edit-light.svg + recycle.svg + recycle-light.svg diff --git a/modules/build-transaction/recycle-light.svg b/modules/build-transaction/recycle-light.svg new file mode 100644 index 0000000..22055ce --- /dev/null +++ b/modules/build-transaction/recycle-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/build-transaction/recycle.svg b/modules/build-transaction/recycle.svg new file mode 100644 index 0000000..ede4d59 --- /dev/null +++ b/modules/build-transaction/recycle.svg @@ -0,0 +1,4 @@ + + + + -- 2.54.0 From ad1e187c549523e15b976be957eb368c7efe0f0e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 1 Oct 2024 21:14:54 +0200 Subject: [PATCH 0947/1428] minor fix. More space for touch --- modules/build-transaction/PayToOthers.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index b3b1b77..cfc4188 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -424,7 +424,7 @@ Page { id: mainColumn y: 10 width: parent.width - spacing: 6 + spacing: 10 AccountSelectorWidget { visible: !portfolio.singleAccountSetup -- 2.54.0 From 9b7d6749e4bafda133d1f3aecd4cdba200951c95 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 1 Oct 2024 21:26:01 +0200 Subject: [PATCH 0948/1428] Start new send-sweep module --- modules/send-sweep/CMakeLists.txt | 24 +++++++++++++++ modules/send-sweep/SendPage.qml | 31 ++++++++++++++++++++ modules/send-sweep/SendSweepModuleInfo.cpp | 34 ++++++++++++++++++++++ modules/send-sweep/SendSweepModuleInfo.h | 31 ++++++++++++++++++++ modules/send-sweep/send-sweep-data.qrc | 5 ++++ 5 files changed, 125 insertions(+) create mode 100644 modules/send-sweep/CMakeLists.txt create mode 100644 modules/send-sweep/SendPage.qml create mode 100644 modules/send-sweep/SendSweepModuleInfo.cpp create mode 100644 modules/send-sweep/SendSweepModuleInfo.h create mode 100644 modules/send-sweep/send-sweep-data.qrc diff --git a/modules/send-sweep/CMakeLists.txt b/modules/send-sweep/CMakeLists.txt new file mode 100644 index 0000000..abc2964 --- /dev/null +++ b/modules/send-sweep/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2024 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(send-sweep) + +set (SOURCES + SendSweepModuleInfo.cpp +) +add_library (send-sweep_module_lib STATIC ${SOURCES}) +target_link_libraries(send-sweep_module_lib pay_lib) + diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml new file mode 100644 index 0000000..cec9b00 --- /dev/null +++ b/modules/send-sweep/SendPage.qml @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import "../mobile" as Mobile; +import Flowee.org.pay; + +Mobile.Page { + id: root + headerText: qsTr("Sweep a wallet") + + + +} diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp new file mode 100644 index 0000000..3e085ab --- /dev/null +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -0,0 +1,34 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "SendSweepModuleInfo.h" + +ModuleInfo * SendSweepModuleInfo::build() +{ + ModuleInfo *info = new ModuleInfo(); + info->setId("sendSweepModule"); + info->setTitle(tr("Sweep & Send")); + info->setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); + // info->setIconSource("qrc:/example/example.svg"); + + auto sendButton = new ModuleSection(ModuleSection::SendMethod, info); + sendButton->setText(tr("Sweep Paper Wallet")); + sendButton->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); + info->addSection(sendButton); + + return info; +} diff --git a/modules/send-sweep/SendSweepModuleInfo.h b/modules/send-sweep/SendSweepModuleInfo.h new file mode 100644 index 0000000..e6c13e7 --- /dev/null +++ b/modules/send-sweep/SendSweepModuleInfo.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class SendSweepModuleInfo : public QObject +{ + Q_OBJECT +public: + static ModuleInfo *build(); + + static const char *translationUnit() { + return "module-send-sweep"; + } +}; diff --git a/modules/send-sweep/send-sweep-data.qrc b/modules/send-sweep/send-sweep-data.qrc new file mode 100644 index 0000000..3eb99a7 --- /dev/null +++ b/modules/send-sweep/send-sweep-data.qrc @@ -0,0 +1,5 @@ + + + SendPage.qml + + -- 2.54.0 From 3aa28743f11a8c8e1a62d3a838970eb000fb4b48 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 2 Oct 2024 23:04:38 +0200 Subject: [PATCH 0949/1428] Start implementing the sweep module This adds scanning of private key and processing the data. --- modules/send-sweep/CMakeLists.txt | 1 + modules/send-sweep/QMLSweepHandler.cpp | 90 ++++++++++++++++++++++ modules/send-sweep/QMLSweepHandler.h | 56 ++++++++++++++ modules/send-sweep/SendPage.qml | 20 +++++ modules/send-sweep/SendSweepModuleInfo.cpp | 6 +- src/CameraController.cpp | 3 + src/QRScanner.cpp | 2 +- src/QRScanner.h | 7 +- 8 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 modules/send-sweep/QMLSweepHandler.cpp create mode 100644 modules/send-sweep/QMLSweepHandler.h diff --git a/modules/send-sweep/CMakeLists.txt b/modules/send-sweep/CMakeLists.txt index abc2964..78e683a 100644 --- a/modules/send-sweep/CMakeLists.txt +++ b/modules/send-sweep/CMakeLists.txt @@ -18,6 +18,7 @@ project(send-sweep) set (SOURCES SendSweepModuleInfo.cpp + QMLSweepHandler.cpp ) add_library (send-sweep_module_lib STATIC ${SOURCES}) target_link_libraries(send-sweep_module_lib pay_lib) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp new file mode 100644 index 0000000..b4839bb --- /dev/null +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -0,0 +1,90 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "QMLSweepHandler.h" + +#include + +#include +#include +#include +#include + + +QMLSweepHandler::QMLSweepHandler(QObject *parent) + : QObject(parent) +{ +} + +QString QMLSweepHandler::privKey() const +{ + return m_privKey; +} + +void QMLSweepHandler::setPrivKey(const QString &newPrivKey) +{ + if (m_privKey == newPrivKey) + return; + m_privKey = newPrivKey; + m_addressHash.clear(); + emit privKeyChanged(); + + // if the private key is valid, find the address and outputscript hash which + // we'll use to ask electrum-cash for content. + PrivateKey key; + CBase58Data string; + if (string.SetString(newPrivKey.toStdString())) { + auto chain = FloweePay::instance()->chain(); + if ((chain == P2PNet::MainChain && string.isMainnetPrivKey()) + || (chain == P2PNet::Testnet4Chain && string.isTestnetPrivKey())) { + key.set(string.data().begin(), string.data().begin() + 32, + string.data().size() > 32 && string.data().at(32) == 1); + } + } + if (!key.isValid()) { + setError(InvalidInput); + return; + } + const auto id = key.getPubKey().getKeyId(); + assert(id.size() == 20); + CashAddress::Content cashContent; + cashContent.type = CashAddress::PUBKEY_TYPE; + cashContent.hash = std::vector(id.begin(), id.end()); + + m_addressHash = CashAddress::createHashedOutputScript(cashContent); + + QTimer::singleShot(10, this, SLOT(start())); +} + +QMLSweepHandler::Error QMLSweepHandler::error() const +{ + return m_error; +} + +void QMLSweepHandler::setError(Error newError) +{ + if (m_error == newError) + return; + m_error = newError; + emit errorChanged(); +} + +void QMLSweepHandler::start() +{ + logFatal() << "start" << m_addressHash.toHex(); + // TODO +} diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h new file mode 100644 index 0000000..c4e45ae --- /dev/null +++ b/modules/send-sweep/QMLSweepHandler.h @@ -0,0 +1,56 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QMLSWEEPHANDLER_H +#define QMLSWEEPHANDLER_H + +#include +#include + +class QMLSweepHandler : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString privKey READ privKey WRITE setPrivKey NOTIFY privKeyChanged FINAL) + Q_PROPERTY(Error error READ error NOTIFY errorChanged FINAL) +public: + QMLSweepHandler(QObject *parent = nullptr); + + enum Error { + NoError, + InvalidInput, + }; + + QString privKey() const; + void setPrivKey(const QString &newPrivKey); + + Error error() const; + void setError(Error newError); + +signals: + void privKeyChanged(); + void errorChanged(); + +private slots: + void start(); + +private: + QString m_privKey; + Streaming::ConstBuffer m_addressHash; + Error m_error = NoError; +}; + +#endif diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index cec9b00..6a7884f 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -21,11 +21,31 @@ import QtQuick.Layouts import "../Flowee" as Flowee import "../mobile" as Mobile; import Flowee.org.pay; +import Flowee.org.pay.SendSweep; Mobile.Page { id: root headerText: qsTr("Sweep a wallet") + Item { // data + QRScanner { + id: scanner + scanType: QRScanner.PrivateKeyWIF + autostart: true + onFinished: { + var rc = scanResult + if (rc === "") { // scanning interrupted + thePile.pop(); + return; + } + sweeper.privKey = rc; + } + } + + SweepHandler { + id: sweeper + } + } } diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index 3e085ab..ffa9ef5 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -16,6 +16,9 @@ * along with this program. If not, see . */ #include "SendSweepModuleInfo.h" +#include "QMLSweepHandler.h" + +#include // for the qmlRegisterType ModuleInfo * SendSweepModuleInfo::build() { @@ -23,12 +26,13 @@ ModuleInfo * SendSweepModuleInfo::build() info->setId("sendSweepModule"); info->setTitle(tr("Sweep & Send")); info->setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); - // info->setIconSource("qrc:/example/example.svg"); auto sendButton = new ModuleSection(ModuleSection::SendMethod, info); sendButton->setText(tr("Sweep Paper Wallet")); sendButton->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); info->addSection(sendButton); + qmlRegisterType("Flowee.org.pay.SendSweep", 1, 0, "SweepHandler"); + return info; } diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 1fcd62c..6fdce51 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -299,6 +299,7 @@ void QRScanningThread::run() // logInfo() << "result:" << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); switch (m_scanType) { + case QRScanner::PrivateKeyWIF: case QRScanner::SeedOrPrivKey: { // we expect WIF encoded private keys heree if (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L')) { @@ -311,6 +312,8 @@ void QRScanningThread::run() return; } } + if (m_scanType == QRScanner::PrivateKeyWIF) + break; // no privkey, ok. Then check if its a seed :-) /* diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index 60cc673..b40b999 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/QRScanner.h b/src/QRScanner.h index 65166c2..17c2144 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,6 @@ #include #include -#include class QRScanner : public QObject { @@ -39,8 +38,8 @@ public: enum ScanType { SeedOrPrivKey, PaymentDetails, - PaymentDetailsTestnet - // others like private key or cashId + PaymentDetailsTestnet, + PrivateKeyWIF }; Q_ENUM(ScanType) -- 2.54.0 From 402ab8740ec3efcf05949ea0da3e14df01aa3602 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 2 Oct 2024 23:42:37 +0200 Subject: [PATCH 0950/1428] Split ElectronXClient class from ImportHandler This splits the latter class into an abstract baseclass that finds a server and handshakes with it called ElectronXClient and then as a subclass the import handler handling doing what it always did. --- src/CMakeLists.txt | 3 +- src/ElectronXClient.cpp | 127 ++++++++++++++++++++++++++++++++++++++++ src/ElectronXClient.h | 65 ++++++++++++++++++++ src/ImportHandler.cpp | 107 ++++----------------------------- src/ImportHandler.h | 25 ++------ 5 files changed, 212 insertions(+), 115 deletions(-) create mode 100644 src/ElectronXClient.cpp create mode 100644 src/ElectronXClient.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2741c99..7e683a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ # This file is part of the Flowee project -# Copyright (C) 2020-2022 Tom Zander +# Copyright (C) 2020-2024 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ set (PAY_SOURCES AccountInfo.cpp AddressInfo.cpp BitcoinValue.cpp + ElectronXClient.cpp FloweePay.cpp ImportHandler.cpp IndexerServices.cpp diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp new file mode 100644 index 0000000..6e4e0d5 --- /dev/null +++ b/src/ElectronXClient.cpp @@ -0,0 +1,127 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ElectronXClient.h" + +#include +#include +#include + +constexpr const char *USERAGENT = "net/useragent"; + +ElectronXClient::ElectronXClient(QObject *parent) + : QObject(parent), + m_electronServer(new QSslSocket(this)) +{ + connect (m_electronServer, SIGNAL(connected()), this, SLOT(connectionEstablished())); + connect (m_electronServer, SIGNAL(disconnected()), this, SLOT(disconnected())); + connect (m_electronServer, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); + connect (m_electronServer, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); + + connect (m_electronServer, &QSslSocket::sslErrors, [=](const QList &errors) { + logInfo(10005) << "sslErrors" << errors.size(); + for (const auto &e : errors) { + logInfo(10005) << " " << e.errorString(); + } + }); +} + +void ElectronXClient::setService(const EndPoint &ep) +{ + m_serviceAddress = ep; + assert(!m_serviceAddress.hostname.empty()); +} + +EndPoint ElectronXClient::service() const +{ + return m_serviceAddress; +} + +void ElectronXClient::connectionEstablished() +{ + // first version we need is 1.5.2 because that is the one that introduced the + // get_first_use method. + // The second version is the highest we've tested to work. + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"server.version\"," + "\"params\": [\"%1\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); + + QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); + QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); + call = call.arg(useragent); + + m_electronServer->write(call.toUtf8()); + m_electronServer->write("\n"); +} + +void ElectronXClient::disconnected() +{ + logDebug(10005); +} + +void ElectronXClient::socketError(QAbstractSocket::SocketError error) +{ + logCritical(10005) << error; + m_error = true; + emit failed(); +} + +void ElectronXClient::socketDataAvailable() +{ + auto data = m_electronServer->readAll(); + logDebug(10005) << QString::fromLatin1(data); + QJsonDocument doc; + doc = QJsonDocument::fromJson(data); + if (!doc.isObject()) { + m_electronServer->close(); + return; + } + const auto o = doc.object(); + const int id = o.value("id").toInt(); + if (id == 1) { + handleVersion(o); + return; + } + handleResponse(id, o); +} + +void ElectronXClient::connectToServer() +{ + logInfo(10005) << "ImportHandler connecting to" << m_serviceAddress; + m_electronServer->connectToHostEncrypted( + QString::fromStdString(m_serviceAddress.hostname), m_serviceAddress.announcePort); +} + + +void ElectronXClient::handleVersion(const QJsonObject &rootObject) +{ + auto result = rootObject.value("result"); + if (!result.isArray()) { + logFatal(10005) << "No viable protocol-version found for this server"; + m_error = true; + m_electronServer->close(); + emit failed(); + return; + } + + handshakeCompleted(); +} + +bool ElectronXClient::errored() const +{ + return m_error; +} diff --git a/src/ElectronXClient.h b/src/ElectronXClient.h new file mode 100644 index 0000000..9e1b140 --- /dev/null +++ b/src/ElectronXClient.h @@ -0,0 +1,65 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef ELECTRONXCLIENT_H +#define ELECTRONXCLIENT_H + +#include + +#include +#include +#include + +class QSslSocket; + + +class ElectronXClient : public QObject +{ + Q_OBJECT +public: + explicit ElectronXClient(QObject *parent = nullptr); + + void setService(const EndPoint &ep); + EndPoint service() const; + + /// returns true if an error occurred since startCheck() + bool errored() const; + +signals: + void failed(); + +private slots: + void connectionEstablished(); + void disconnected(); + void socketError(QAbstractSocket::SocketError error); + void socketDataAvailable(); + +protected: + virtual void handshakeCompleted() = 0; + virtual void handleResponse(int id, const QJsonObject &data) = 0; + void connectToServer(); + +private: + void handleVersion(const QJsonObject &rootObject); + +protected: + EndPoint m_serviceAddress; + QSslSocket *m_electronServer; + bool m_error = false; +}; + +#endif diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 2bad83a..05b4c06 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -31,23 +31,9 @@ #include #include -constexpr const char *USERAGENT = "net/useragent"; - ImportHandler::ImportHandler(QObject *parent) - : QObject{parent}, - m_electronServer(new QSslSocket(this)) + : ElectronXClient(parent) { - connect (m_electronServer, SIGNAL(connected()), this, SLOT(connectionEstablished())); - connect (m_electronServer, SIGNAL(disconnected()), this, SLOT(disconnected())); - connect (m_electronServer, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); - connect (m_electronServer, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); - - connect (m_electronServer, &QSslSocket::sslErrors, [=](const QList &errors) { - logInfo(10005) << "sslErrors" << errors.size(); - for (const auto &e : errors) { - logInfo(10005) << " " << e.errorString(); - } - }); } void ImportHandler::startCheck(WalletType type, const QString &text, const QString &password) @@ -65,83 +51,7 @@ void ImportHandler::startCheck(WalletType type, const QString &text, const QStri m_found.clear(); m_currentlyChecking = ImportData(); - logInfo(10005) << "ImportHandler connecting to" << m_serviceAddress; - m_electronServer->connectToHostEncrypted( - QString::fromStdString(m_serviceAddress.hostname), m_serviceAddress.announcePort); -} - -void ImportHandler::setService(const EndPoint &ep) -{ - m_serviceAddress = ep; - assert(!m_serviceAddress.hostname.empty()); -} - -EndPoint ImportHandler::service() const -{ - return m_serviceAddress; -} - -void ImportHandler::connectionEstablished() -{ - // first version we need is 1.5.2 because that is the one that introduced the - // get_first_use method. - // The second version is the highest we've tested to work. - QString call("{\"jsonrpc\":\"2.0\"," - "\"method\":\"server.version\"," - "\"params\": [\"%1\", [\"1.5.2\", \"1.5.3\"]], \"id\": 1}"); - - QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); - QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString(); - call = call.arg(useragent); - - m_electronServer->write(call.toUtf8()); - m_electronServer->write("\n"); -} - -void ImportHandler::disconnected() -{ - logDebug(10005); -} - -void ImportHandler::socketError(QAbstractSocket::SocketError error) -{ - logCritical(10005) << error; - m_error = true; - emit finished(); -} - -void ImportHandler::socketDataAvailable() -{ - auto data = m_electronServer->readAll(); - logDebug(10005) << QString::fromLatin1(data); - QJsonDocument doc; - doc = QJsonDocument::fromJson(data); - if (!doc.isObject()) { - m_electronServer->close(); - return; - } - auto o = doc.object(); - switch(o.value("id").toInt()) { - case 1: handleVersion(o); break; - case 2: handleFirstUse(o); break; - default: - return; - } -} - -void ImportHandler::handleVersion(const QJsonObject &rootObject) -{ - auto result = rootObject.value("result"); - if (!result.isArray()) { - logFatal(10005) << "No viable protocol-version found for this server"; - m_error = true; - m_electronServer->close(); - emit finished(); - return; - } - - assert(m_nextToCheck != Done); - checkNext(); + connectToServer(); } void ImportHandler::handleFirstUse(const QJsonObject &rootObject) @@ -235,9 +145,18 @@ void ImportHandler::checkNext() m_electronServer->write("\n"); } -bool ImportHandler::errored() const +void ImportHandler::handshakeCompleted() { - return m_error; + assert(m_nextToCheck != Done); + checkNext(); +} + +void ImportHandler::handleResponse(int id, const QJsonObject &data) +{ + if (id == 2) + handleFirstUse(data); + else + logCritical() << "Received unrecognized response, ignoring. ID:" << id; } QList ImportHandler::found() const diff --git a/src/ImportHandler.h b/src/ImportHandler.h index 2d5ba1b..8751a35 100644 --- a/src/ImportHandler.h +++ b/src/ImportHandler.h @@ -18,12 +18,9 @@ #ifndef IMPORTHANDLER_H #define IMPORTHANDLER_H -#include -#include +#include "ElectronXClient.h" -class QSslSocket; - -class ImportHandler : public QObject +class ImportHandler : public ElectronXClient { Q_OBJECT public: @@ -43,9 +40,6 @@ public: */ void startCheck(WalletType type, const QString &text, const QString &password = QString()); - void setService(const EndPoint &ep); - EndPoint service() const; - struct ImportData { std::vector derivation; bool electrum = false; @@ -54,25 +48,18 @@ public: QList found() const; - /// returns true if an error occurred since startCheck() - bool errored() const; - signals: void finished(); -private slots: - void connectionEstablished(); - void disconnected(); - void socketError(QAbstractSocket::SocketError error); - void socketDataAvailable(); +protected: + void handshakeCompleted() override; + void handleResponse(int id, const QJsonObject &data) override; private: - void handleVersion(const QJsonObject &rootObject); void handleFirstUse(const QJsonObject &rootObject); void checkNext(); private: - QSslSocket *m_electronServer; QString m_input; QString m_password; // seeds may have a password @@ -91,8 +78,6 @@ private: ImportData m_currentlyChecking; QList m_found; - EndPoint m_serviceAddress; - bool m_error = false; }; #endif -- 2.54.0 From f40910742df10a1908b2fe5885deb76d71ea3974 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 3 Oct 2024 14:27:46 +0200 Subject: [PATCH 0951/1428] Add retry on failure. We move to another server, if one fails us. --- src/QMLImportHelper.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp index e68b912..73cc967 100644 --- a/src/QMLImportHelper.cpp +++ b/src/QMLImportHelper.cpp @@ -33,6 +33,14 @@ QMLImportHelper::QMLImportHelper(QObject *parent) connect (m_importHandler, SIGNAL(finished()), this, SLOT(checkFinished()), Qt::QueuedConnection); + + connect (m_importHandler, &ImportHandler::failed, this, [=]() { + auto services = FloweePay::instance()->indexerServices(); + services->punish(m_importHandler->service(), 40); + m_checking = false; + // try again + this->startCheck(); + }, Qt::QueuedConnection); } bool QMLImportHelper::checking() const -- 2.54.0 From caea90b3cba7d8719c65c1941aa7d2fb80c88b2a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 3 Oct 2024 14:29:05 +0200 Subject: [PATCH 0952/1428] Add ElectronX interaction. This adds the fetching of utxos and the containing transactions from a compliant electronx server. Like fulcrum. --- modules/send-sweep/CMakeLists.txt | 3 +- modules/send-sweep/QMLSweepHandler.cpp | 33 ++++- modules/send-sweep/QMLSweepHandler.h | 5 + modules/send-sweep/TransactionsFetcher.cpp | 138 +++++++++++++++++++++ modules/send-sweep/TransactionsFetcher.h | 57 +++++++++ 5 files changed, 229 insertions(+), 7 deletions(-) create mode 100644 modules/send-sweep/TransactionsFetcher.cpp create mode 100644 modules/send-sweep/TransactionsFetcher.h diff --git a/modules/send-sweep/CMakeLists.txt b/modules/send-sweep/CMakeLists.txt index 78e683a..4f2c657 100644 --- a/modules/send-sweep/CMakeLists.txt +++ b/modules/send-sweep/CMakeLists.txt @@ -20,6 +20,7 @@ set (SOURCES SendSweepModuleInfo.cpp QMLSweepHandler.cpp ) -add_library (send-sweep_module_lib STATIC ${SOURCES}) +add_library (send-sweep_module_lib STATIC ${SOURCES} + TransactionsFetcher.h TransactionsFetcher.cpp) target_link_libraries(send-sweep_module_lib pay_lib) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index b4839bb..866bc7b 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -16,6 +16,8 @@ * along with this program. If not, see . */ #include "QMLSweepHandler.h" +#include "IndexerServices.h" +#include "TransactionsFetcher.h" #include @@ -26,8 +28,19 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) - : QObject(parent) + : QObject(parent), + m_fetcher(new TransactionsFetcher(this)) { + // make sure that we'll have a list of indexer services when we + // need them later. + FloweePay::instance()->indexerServices()->populate(); + + connect (m_fetcher, &TransactionsFetcher::failed, this, [=]() { + auto services = FloweePay::instance()->indexerServices(); + services->punish(m_fetcher->service(), 40); + // try again + this->start(); + }, Qt::QueuedConnection); } QString QMLSweepHandler::privKey() const @@ -75,16 +88,24 @@ QMLSweepHandler::Error QMLSweepHandler::error() const return m_error; } -void QMLSweepHandler::setError(Error newError) +void QMLSweepHandler::setError(Error err) { - if (m_error == newError) + if (m_error == err) return; - m_error = newError; + m_error = err; emit errorChanged(); } void QMLSweepHandler::start() { - logFatal() << "start" << m_addressHash.toHex(); - // TODO + auto service = FloweePay::instance()->indexerServices()->service(); + if (service.hostname.empty()) { + // lets not translate this, since this is likely an + // internal error (aka bug) or simply a lack of Internet. + setError(NoBackendFound); + return; + } + + m_fetcher->setService(service); + m_fetcher->start(m_addressHash); } diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index c4e45ae..4263f84 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -21,6 +21,8 @@ #include #include +class TransactionsFetcher; + class QMLSweepHandler : public QObject { Q_OBJECT @@ -32,6 +34,7 @@ public: enum Error { NoError, InvalidInput, + NoBackendFound, // no indexer services }; QString privKey() const; @@ -51,6 +54,8 @@ private: QString m_privKey; Streaming::ConstBuffer m_addressHash; Error m_error = NoError; + + TransactionsFetcher *m_fetcher; }; #endif diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp new file mode 100644 index 0000000..fb45a57 --- /dev/null +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -0,0 +1,138 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TransactionsFetcher.h" +#include +#include +#include +#include +#include +#include + +TransactionsFetcher::TransactionsFetcher(QObject *parent) + : ElectronXClient{parent} +{ +} + +void TransactionsFetcher::start(const Streaming::ConstBuffer &scripthash) +{ + m_scriptHash = scripthash; + connectToServer(); +} + +void TransactionsFetcher::handshakeCompleted() +{ + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"blockchain.scripthash.listunspent\"," + "\"params\": [\"%1\"], \"id\": 2}"); + auto param = m_scriptHash.toHex_reversed(); + call = call.arg(QString::fromLatin1(param)); + m_electronServer->write(call.toLatin1()); + m_electronServer->write("\n"); +} + +void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) +{ + if (id == 2) { // the list-unspent + auto result = data["result"].toArray(); + for (auto item = result.begin(); item != result.end(); item++) { + if (item->isObject()) { + auto result = item->toObject(); + auto token = result[QLatin1String("token_data")]; + if (!token.isNull()) { + ++m_tokensFound; + } + // auto height = result[QLatin1String("height")]; + auto txid = result[QLatin1String("tx_hash")]; + auto outIndex = result[QLatin1String("tx_pos")]; + auto amount = result[QLatin1String("value")]; + if (txid.isNull() || outIndex.isNull() || amount.isNull()) { + logCritical() << "Missing data in result row"; + continue; + } + m_balance += amount.toInteger(); + ++m_coinsFound; + + Output o; + o.txid = txid.toString(); + o.outIndex = outIndex.toInt(); + + QFileInfo info(QString("sweep-%1").arg(o.txid)); + o.available = info.exists(); + m_outputs.append(o); + + if (!o.available) { + QString call("{\"jsonrpc\":\"2.0\"," + "\"method\":\"blockchain.transaction.get\"," + "\"params\": [\"%1\"], \"id\": 3}"); + call = call.arg(o.txid); + m_electronServer->write(call.toLatin1()); + m_electronServer->write("\n"); + } + } + } + checkAllAvailable(); + } + + if (id == 3) { // transaction.get + auto result = data["result"]; + if (!result.isString()) { + logCritical() << "Expected transaction data, didn't see that, though"; + return; + } + auto hexBytes = result.toString().toLatin1(); + auto pool = Streaming::pool(hexBytes.size()); + pool->writeHex(hexBytes.constData(), hexBytes.size()); + auto txData = pool->commit(); + + auto filename = QString("sweep-%1"); + auto txHash = Tx(txData).createHash(); + const auto txid = QString::fromStdString(txHash.ToString()); + filename = filename.arg(txid); + QFile out(filename); + if (!out.open(QIODevice::WriteOnly)) { + logFatal() << "Could not write in currentDir"; + abort(); + } + out.write(txData.begin(), txData.size()); + + + bool found = false; + for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) { + if (txid == i->txid) { + i->available = true; + found = true; + break; + } + } + if (!found) + logCritical() << "Received a tx from server that I didn't request" << txid; + + checkAllAvailable(); + } +} + +void TransactionsFetcher::checkAllAvailable() +{ + for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) { + if (!i->available) + return; + } + + logFatal() << "DONE"; + // TODO hand back to the caller that can start to build the transaction. +} diff --git a/modules/send-sweep/TransactionsFetcher.h b/modules/send-sweep/TransactionsFetcher.h new file mode 100644 index 0000000..e7fdf2c --- /dev/null +++ b/modules/send-sweep/TransactionsFetcher.h @@ -0,0 +1,57 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TRANSACTIONSFETCHER_H +#define TRANSACTIONSFETCHER_H + +#include +#include +#include + +class TransactionsFetcher : public ElectronXClient +{ + Q_OBJECT +public: + explicit TransactionsFetcher(QObject *parent = nullptr); + + void start(const Streaming::ConstBuffer &scripthash); + +signals: + + +protected: + void handshakeCompleted() override; + void handleResponse(int id, const QJsonObject &data) override; + +private: + void checkAllAvailable(); + + Streaming::ConstBuffer m_scriptHash; + + int m_coinsFound = 0; + int m_tokensFound = 0; + int64_t m_balance = 0; + + struct Output { + QString txid; + int outIndex = -1; + bool available = false; + }; + QList m_outputs; +}; + +#endif -- 2.54.0 From 08e2216ce3f5935e32cc8b0df91cf23c51d3a7d5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 3 Oct 2024 17:55:26 +0200 Subject: [PATCH 0953/1428] Allow result to be used by SweepHandler --- modules/send-sweep/QMLSweepHandler.cpp | 12 +++++++ modules/send-sweep/QMLSweepHandler.h | 4 +++ modules/send-sweep/TransactionsFetcher.cpp | 39 ++++++++++++++-------- modules/send-sweep/TransactionsFetcher.h | 13 ++++---- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 866bc7b..3f66757 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -41,6 +41,11 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) // try again this->start(); }, Qt::QueuedConnection); + + connect (m_fetcher, &TransactionsFetcher::finished, this, [=](const QList &result) { + logFatal() << "Emit came through 1"; + }); + connect (m_fetcher, &TransactionsFetcher::finished, this, &QMLSweepHandler::startTxBuilder); } QString QMLSweepHandler::privKey() const @@ -109,3 +114,10 @@ void QMLSweepHandler::start() m_fetcher->setService(service); m_fetcher->start(m_addressHash); } + +void QMLSweepHandler::startTxBuilder(const QList &result) +{ + logFatal() << "Emit came through 2"; + m_fetcher->deleteLater(); + m_fetcher = nullptr; +} diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 4263f84..e108687 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -18,6 +18,9 @@ #ifndef QMLSWEEPHANDLER_H #define QMLSWEEPHANDLER_H +#include "TransactionsFetcher.h" + +#include #include #include @@ -49,6 +52,7 @@ signals: private slots: void start(); + void startTxBuilder(const QList &result); private: QString m_privKey; diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index fb45a57..8a86f7f 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -30,6 +30,7 @@ TransactionsFetcher::TransactionsFetcher(QObject *parent) void TransactionsFetcher::start(const Streaming::ConstBuffer &scripthash) { + logCritical(1007) << "Starting check online, scripthash:" << scripthash.toHex_reversed(); m_scriptHash = scripthash; connectToServer(); } @@ -49,6 +50,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) { if (id == 2) { // the list-unspent auto result = data["result"].toArray(); + QSet txids; for (auto item = result.begin(); item != result.end(); item++) { if (item->isObject()) { auto result = item->toObject(); @@ -61,7 +63,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) auto outIndex = result[QLatin1String("tx_pos")]; auto amount = result[QLatin1String("value")]; if (txid.isNull() || outIndex.isNull() || amount.isNull()) { - logCritical() << "Missing data in result row"; + logCritical(10007) << "Missing data in result row"; continue; } m_balance += amount.toInteger(); @@ -70,12 +72,19 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) Output o; o.txid = txid.toString(); o.outIndex = outIndex.toInt(); - - QFileInfo info(QString("sweep-%1").arg(o.txid)); - o.available = info.exists(); m_outputs.append(o); - if (!o.available) { + // check for duplicates to avoid downloading the same transaction multiple + // times if there are multiple outputs on the same tx for this one address. + if (txids.contains(o.txid)) + continue; + QFileInfo info(QString("sweep-%1").arg(o.txid)); + if (info.exists()) { + m_outputs.back().filename = info.absoluteFilePath(); + continue; + } + + if (o.filename.isEmpty()) { QString call("{\"jsonrpc\":\"2.0\"," "\"method\":\"blockchain.transaction.get\"," "\"params\": [\"%1\"], \"id\": 3}"); @@ -85,13 +94,14 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) } } } + logCritical(10007) << "Remote indexer reported" << m_tokensFound << "tokens &" << m_coinsFound << "coins"; checkAllAvailable(); } if (id == 3) { // transaction.get auto result = data["result"]; if (!result.isString()) { - logCritical() << "Expected transaction data, didn't see that, though"; + logCritical(10007) << "Expected transaction data, didn't see that, though"; return; } auto hexBytes = result.toString().toLatin1(); @@ -105,7 +115,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) filename = filename.arg(txid); QFile out(filename); if (!out.open(QIODevice::WriteOnly)) { - logFatal() << "Could not write in currentDir"; + logFatal(10007) << "Could not write the transaction"; abort(); } out.write(txData.begin(), txData.size()); @@ -114,13 +124,14 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) bool found = false; for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) { if (txid == i->txid) { - i->available = true; + i->filename = out.fileName(); found = true; - break; + // note, do NOT add a break; here. + // Multiple outputs may all spend the same txid (but different output indexes). } } if (!found) - logCritical() << "Received a tx from server that I didn't request" << txid; + logCritical(10007) << "Received a tx from server that I didn't request" << txid; checkAllAvailable(); } @@ -129,10 +140,12 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) void TransactionsFetcher::checkAllAvailable() { for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) { - if (!i->available) + if (i->filename.isEmpty()) { + logInfo(10007) << "Waiting for download of" << i->txid; return; + } } - logFatal() << "DONE"; - // TODO hand back to the caller that can start to build the transaction. + logCritical(10007) << "Download of" << m_outputs.size() << "transactions finished"; + emit finished(m_outputs); } diff --git a/modules/send-sweep/TransactionsFetcher.h b/modules/send-sweep/TransactionsFetcher.h index e7fdf2c..a0f6857 100644 --- a/modules/send-sweep/TransactionsFetcher.h +++ b/modules/send-sweep/TransactionsFetcher.h @@ -28,10 +28,16 @@ class TransactionsFetcher : public ElectronXClient public: explicit TransactionsFetcher(QObject *parent = nullptr); + struct Output { + QString txid; + int outIndex = -1; + QString filename; + }; + void start(const Streaming::ConstBuffer &scripthash); signals: - + void finished(const QList &result); protected: void handshakeCompleted() override; @@ -46,11 +52,6 @@ private: int m_tokensFound = 0; int64_t m_balance = 0; - struct Output { - QString txid; - int outIndex = -1; - bool available = false; - }; QList m_outputs; }; -- 2.54.0 From f6846a22a8ece35510921d406f2ed4a2bf5a023e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 3 Oct 2024 17:56:06 +0200 Subject: [PATCH 0954/1428] Print the most common error full. SSL Handshake failure typically means self-signed. --- src/ElectronXClient.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index 6e4e0d5..4add65b 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -75,7 +75,10 @@ void ElectronXClient::disconnected() void ElectronXClient::socketError(QAbstractSocket::SocketError error) { - logCritical(10005) << error; + if (error == QAbstractSocket::SslHandshakeFailedError) + logCritical(10005) << "Remote peer failed SSL handshake" << m_serviceAddress; + else + logCritical(10005) << error; m_error = true; emit failed(); } -- 2.54.0 From 5def4efd26142ed6fa3c642532e8dc14f2980d8a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 4 Oct 2024 16:56:34 +0200 Subject: [PATCH 0955/1428] Build the transaction from the given transactions --- modules/send-sweep/QMLSweepHandler.cpp | 80 +++++++++++++++++++--- modules/send-sweep/QMLSweepHandler.h | 21 +++++- modules/send-sweep/TransactionsFetcher.cpp | 4 ++ 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 3f66757..198d38b 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -17,14 +17,12 @@ */ #include "QMLSweepHandler.h" #include "IndexerServices.h" -#include "TransactionsFetcher.h" - -#include - #include +#include #include #include #include +#include QMLSweepHandler::QMLSweepHandler(QObject *parent) @@ -42,9 +40,6 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) this->start(); }, Qt::QueuedConnection); - connect (m_fetcher, &TransactionsFetcher::finished, this, [=](const QList &result) { - logFatal() << "Emit came through 1"; - }); connect (m_fetcher, &TransactionsFetcher::finished, this, &QMLSweepHandler::startTxBuilder); } @@ -77,6 +72,7 @@ void QMLSweepHandler::setPrivKey(const QString &newPrivKey) setError(InvalidInput); return; } + m_key = key; const auto id = key.getPubKey().getKeyId(); assert(id.size() == 20); CashAddress::Content cashContent; @@ -101,6 +97,36 @@ void QMLSweepHandler::setError(Error err) emit errorChanged(); } +AccountInfo *QMLSweepHandler::currentAccount() const +{ + return m_account; +} + +void QMLSweepHandler::setCurrentAccount(AccountInfo *account) +{ + if (m_account == account) + return; + m_account = account; + emit currentAccountChanged(); +} + +void QMLSweepHandler::markUserApproved() +{ + assert(m_account); + if (!m_account) { + logFatal(10007) << "Missing account"; + return; + } + if (m_builder.outputCount() == 0 || m_builder.inputCount() == 0) { + logFatal(10007) << "No Tx to approve"; + return; + } + + // TODO add an output from the m_account->wallet + // TODO broadcast. + // TODO proxy the broadcast properties, like Payment does. +} + void QMLSweepHandler::start() { auto service = FloweePay::instance()->indexerServices()->service(); @@ -117,7 +143,45 @@ void QMLSweepHandler::start() void QMLSweepHandler::startTxBuilder(const QList &result) { - logFatal() << "Emit came through 2"; + assert(m_fetcher); + assert(m_builder.inputCount() == 0); + assert(m_builder.outputCount() == 0); m_fetcher->deleteLater(); m_fetcher = nullptr; + + m_builder.setAnonimize(false); // Makes no sense.. + int64_t inputs = 0; + for (const auto &prevOut : result) { + auto txIdBytes = QByteArray::fromHex(prevOut.txid.toLatin1()); + assert (txIdBytes.size() == 32); + m_builder.appendInput(uint256(txIdBytes.begin()), prevOut.outIndex); + + QFile txIn(prevOut.filename); + if (!txIn.open(QIODevice::ReadOnly)) { + logCritical(10007) << "Failed to open file" << prevOut.filename; + setError(FileError); + return; + } + auto pool = Streaming::pool(txIn.size()); + txIn.read(pool->begin(), txIn.size()); + Tx tx(pool->commit(txIn.size())); + Tx::Iterator iter(tx); + Tx::Output out; + for (int index = 0; index <= prevOut.outIndex; ++index) { + out = Tx::nextOutput(iter); + } + if (out.outputValue < 0) { + logCritical(10007) << "Invalid indexer data"; + setError(DataInconsistency); + return; + } + inputs += out.outputValue; + m_builder.pushInputSignature(m_key, out.outputScript, out.outputValue, TransactionBuilder::Schnorr); + } + m_builder.appendOutput(inputs); + m_builder.setOutputFeeSource(0); + // We're not doing the last step here of assigning our address since the user may change account before we send. + + logInfo(10007) << "Built a transaction with" << m_builder.inputCount() << " => " << m_builder.outputCount(); + logInfo(10007) << "Total sats:" << inputs; } diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index e108687..78ae591 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -18,19 +18,24 @@ #ifndef QMLSWEEPHANDLER_H #define QMLSWEEPHANDLER_H -#include "TransactionsFetcher.h" -#include #include +#include +#include #include +#include "TransactionsFetcher.h" +#include "AccountInfo.h" class TransactionsFetcher; +class AccountInfo; + class QMLSweepHandler : public QObject { Q_OBJECT Q_PROPERTY(QString privKey READ privKey WRITE setPrivKey NOTIFY privKeyChanged FINAL) Q_PROPERTY(Error error READ error NOTIFY errorChanged FINAL) + Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) public: QMLSweepHandler(QObject *parent = nullptr); @@ -38,6 +43,8 @@ public: NoError, InvalidInput, NoBackendFound, // no indexer services + FileError, + DataInconsistency // specifically the data we got from the indexer. }; QString privKey() const; @@ -46,19 +53,27 @@ public: Error error() const; void setError(Error newError); + AccountInfo *currentAccount() const; + void setCurrentAccount(AccountInfo *account); + + Q_INVOKABLE void markUserApproved(); + signals: void privKeyChanged(); void errorChanged(); + void currentAccountChanged(); private slots: void start(); void startTxBuilder(const QList &result); private: + AccountInfo *m_account = nullptr; QString m_privKey; Streaming::ConstBuffer m_addressHash; Error m_error = NoError; - + PrivateKey m_key; + TransactionBuilder m_builder; TransactionsFetcher *m_fetcher; }; diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index 8a86f7f..b0da185 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -71,6 +71,10 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) Output o; o.txid = txid.toString(); + if (o.txid.size() != 64) { + logCritical(10007) << "Malformed txid, not reaping"; + continue; + } o.outIndex = outIndex.toInt(); m_outputs.append(o); -- 2.54.0 From d1dfeb1a020212eaa98d9636115c76d30c4cfbb9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 5 Oct 2024 11:00:30 +0200 Subject: [PATCH 0956/1428] Stop using deprecatd q_enums --- src/Payment.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Payment.h b/src/Payment.h index 590eea9..0e70697 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -91,15 +91,13 @@ class Payment : public QObject Q_PROPERTY(QString error READ error NOTIFY errorChanged) Q_PROPERTY(QStringList warnings READ warnings NOTIFY warningsChanged) - - - Q_ENUMS(DetailType BroadcastStatus) public: enum DetailType { InputSelector, PayToAddress, CommentOutput // aka op-return }; + Q_ENUM(DetailType) /** * The broadcast status is a statemachine to indicate if the transaction @@ -127,13 +125,14 @@ public: TxBroadcastSuccess, //< Tx broadcast and accepted by multiple peers. TxRejected //< Tx has been offered, downloaded and rejected by at least one peer. }; + Q_ENUM(BroadcastStatus) enum Warning { InsecureTransport, DownloadFailed, OfflineWarning }; - Q_ENUM(Warning); + Q_ENUM(Warning) Payment(QObject *parent = nullptr); -- 2.54.0 From 8ce6b744aed22ef3480f99174074764afb149cb1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 5 Oct 2024 20:34:39 +0200 Subject: [PATCH 0957/1428] Make the broadcast of a tx more generic The tx-broadcast was tied completely to a 'payment' object, this is now more made into individual parts that can be reused outside of the 'payment' usecase. --- guis/Flowee/BroadcastFeedback.qml | 74 +++++++------------------------ guis/Flowee/ProgressCheckIcon.qml | 55 +++++++++++++++++++++++ guis/widgets.qrc | 1 + src/FloweePay.h | 28 ++++++++++++ src/Payment.cpp | 33 +++++--------- src/Payment.h | 34 ++------------ src/TxInfoObject.cpp | 28 +++++++++--- src/TxInfoObject.h | 11 ++--- 8 files changed, 141 insertions(+), 123 deletions(-) create mode 100644 guis/Flowee/ProgressCheckIcon.qml diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index b7d5153..02b441b 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -19,7 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts -import QtQuick.Shapes +// import QtQuick.Shapes import "../ControlColors.js" as ControlColors import Flowee.org.pay @@ -39,49 +39,49 @@ QQC2.Control { states: [ State { name: "notStarted" - when: payment.broadcastStatus === Payment.NotStarted + when: payment.broadcastStatus === FloweePay.NotStarted }, State { name: "preparing" - when: payment.broadcastStatus === Payment.TxOffered + when: payment.broadcastStatus === FloweePay.TxOffered PropertyChanges { target: background; opacity: 1 y: 0 } - PropertyChanges { target: progressCircle; sweepAngle: 90 } + PropertyChanges { target: progressIcon; sweepAngle: 90 } StateChangeScript { script: ControlColors.applyLightSkin(root) } }, State { name: "sent1" // sent to only one peer extend: "preparing" - when: payment.broadcastStatus === Payment.TxSent1 - PropertyChanges { target: progressCircle; sweepAngle: 150 } + when: payment.broadcastStatus === FloweePay.TxSent1 + PropertyChanges { target: progressIcon; sweepAngle: 150 } PropertyChanges { target: root; status: qsTr("Sending Payment") } }, State { name: "waiting" // waiting for possible rejection. - when: payment.broadcastStatus === Payment.TxWaiting + when: payment.broadcastStatus === FloweePay.TxWaiting extend: "preparing" - PropertyChanges { target: progressCircle; sweepAngle: 320 } + PropertyChanges { target: progressIcon; sweepAngle: 320 } }, State { name: "success" // no reject, great success - when: payment.broadcastStatus === Payment.TxBroadcastSuccess + when: payment.broadcastStatus === FloweePay.TxBroadcastSuccess extend: "preparing" - PropertyChanges { target: progressCircle + PropertyChanges { target: progressIcon sweepAngle: 320 startAngle: -20 } - PropertyChanges { target: checkShape; opacity: 1 } + PropertyChanges { target: progressIcon; showCheck: true } PropertyChanges { target: root; status: qsTr("Payment Sent") } }, State { name: "rejected" // a peer didn't like our tx - when: payment.broadcastStatus === Payment.TxRejected + when: payment.broadcastStatus === FloweePay.TxRejected extend: "preparing" StateChangeScript { script: ControlColors.applyDarkSkin(root) } PropertyChanges { target: background; color: "#7f0000" } - PropertyChanges { target: circleShape; opacity: 0 } + PropertyChanges { target: progressIcon; opacity: 0 } PropertyChanges { target: root; status: qsTr("Failed") } PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } } @@ -109,53 +109,9 @@ QQC2.Control { width: parent.width } - Item { + ProgressCheckIcon { + id: progressIcon anchors.horizontalCenter: parent.horizontalCenter - width: 160 - height: 160 - - // The 'progress' icon. - Shape { - id: circleShape - width: 160 - height: width - smooth: true - ShapePath { - strokeWidth: 20 - strokeColor: "#dedede" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 100; startY: 10 - - PathAngleArc { - id: progressCircle - centerX: 80 - centerY: 80 - radiusX: 70; radiusY: 70 - startAngle: -80 - sweepAngle: 0 - Behavior on sweepAngle { NumberAnimation { duration: 2500 } } - Behavior on startAngle { NumberAnimation { } } - } - } - Behavior on opacity { NumberAnimation { } } - } - Shape { - id: checkShape - anchors.fill: circleShape - smooth: true - opacity: 0 - ShapePath { - id: checkPath - strokeWidth: 16 - strokeColor: "green" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 52; startY: 80 - PathLine { x: 76; y: 110 } - PathLine { x: 125; y: 47 } - } - } } Label { diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml new file mode 100644 index 0000000..97b0bf8 --- /dev/null +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Shapes + +Item { + id: root + width: 160 + height: 160 + + property alias sweepAngle: progressCircle.sweepAngle + property alias startAngle: progressCircle.startAngle + property bool showCheck: false + + // The 'progress' icon. + Shape { + id: circleShape + width: 160 + height: width + smooth: true + ShapePath { + strokeWidth: 20 + strokeColor: "#dedede" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 100; startY: 10 + + PathAngleArc { + id: progressCircle + centerX: 80 + centerY: 80 + radiusX: 70; radiusY: 70 + startAngle: -80 + sweepAngle: 0 + Behavior on sweepAngle { NumberAnimation { duration: 2500 } } + Behavior on startAngle { NumberAnimation { } } + } + } + Behavior on opacity { NumberAnimation { } } + } + Shape { + id: checkShape + anchors.fill: circleShape + smooth: true + opacity: root.showCheck ? 1 : 0 + ShapePath { + id: checkPath + strokeWidth: 16 + strokeColor: "green" + fillColor: "transparent" + capStyle: ShapePath.RoundCap + startX: 52; startY: 80 + PathLine { x: 76; y: 110 } + PathLine { x: 125; y: 47 } + } + } +} diff --git a/guis/widgets.qrc b/guis/widgets.qrc index d8c9ad7..573aaf7 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -58,5 +58,6 @@ Flowee/AddressInfoWidget.qml Flowee/FiatTxInfo.qml Flowee/Progressbar.qml + Flowee/ProgressCheckIcon.qml diff --git a/src/FloweePay.h b/src/FloweePay.h index cab38ae..b81d6c2 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -97,6 +97,34 @@ public: }; Q_ENUM(ApplicationProtection) + /** + * The broadcast status is a statemachine to indicate if the transaction + * has been offered to the network to the final state of + * either TxRejected or TxBroadcastSuccess + * + * The statemachine goes like this; + * + * 0. `NotStarted` + * 1. After the API call 'broadcast()' we offer the transaction to all peers. + * `TxOffered` + * 2. A peer responds by downloading the actual transaction from us. + * `TxWaiting` + * 3. Optionally, a peer responds with 'rejected' if the transaction is somehow wrong. + * `TxRejected` + * Stop here. + * 4. We waited a little time and no rejected came in, implying 2 or more peers like our tx. + * `TxBroadcastSuccess` + */ + enum BroadcastStatus { + NotStarted, //< We have not yet seen a call to broadcast() + TxOffered, //< Tx has not been offered to any peers. + TxSent1, //< Tx has been sent to at least one peer. + TxWaiting, //< Tx has been downloaded by more than one peer. + TxBroadcastSuccess, //< Tx broadcast and accepted by multiple peers. + TxRejected //< Tx has been offered, downloaded and rejected by at least one peer. + }; + Q_ENUM(BroadcastStatus) + FloweePay(); /** diff --git a/src/Payment.cpp b/src/Payment.cpp index 39d42d0..e78e5a5 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -19,7 +19,6 @@ #include "Payment.h" #include "PaymentDetailInputs_p.h" #include "PaymentDetailOutput_p.h" -#include "FloweePay.h" #include "AccountInfo.h" #include "PriceDataProvider.h" #include "AccountConfig.h" @@ -405,10 +404,12 @@ void Payment::broadcast() if (!m_userComment.isEmpty()) m_wallet->setTransactionComment(m_tx, m_userComment); - m_infoObject = std::make_shared(this, m_tx); + m_infoObject = std::make_shared(m_wallet, m_tx); connect(m_infoObject.get(), &TxInfoObject::sentOne, this, [=]() { emit broadcastStatusChanged(); // we wait a second and force a recalc in the following singleShot. + // NOTE: if you change this number, keep it slightly above the WaitingTimeoutMs + // time in the TxInfoObject class. QTimer::singleShot(1000, this, SIGNAL(broadcastStatusChanged())); }, Qt::QueuedConnection); // notice that rejections are automatically forwarded to the wallet from the TxInfoObject @@ -646,7 +647,7 @@ QList Payment::paymentDetails() const return pds; } -Payment::BroadcastStatus Payment::broadcastStatus() const +FloweePay::BroadcastStatus Payment::broadcastStatus() const { #if 0 // This default-disabled code-snippet is fun to allow developing the UX/GUI by stepping through the steps. @@ -663,29 +664,19 @@ Payment::BroadcastStatus Payment::broadcastStatus() const }); } switch (i) { - case 0: return NotStarted; - case 1: return TxOffered; - case 3: return TxRejected; - case 4: return TxBroadcastSuccess;; - default: return TxWaiting; + case 0: return FloweePay::NotStarted; + case 1: return FloweePay::TxOffered; + case 3: return FloweePay::TxRejected; + case 4: return FloweePay::TxBroadcastSuccess; + default: return FloweePay::TxWaiting; } #endif if (!m_txBroadcastStarted) - return NotStarted; + return FloweePay::NotStarted; auto infoObject = m_infoObject; if (infoObject.get() == nullptr) - return TxBroadcastSuccess; - // The 'calcStatus' arg should match (or be slightly less than) the wait time before we do the - // emit making QML re-fetch this variable. - auto status = infoObject->calcStatus(950); - logDebug() << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; - if (status.sent == 0) - return TxOffered; - if (status.failed) - return TxRejected; - if (status.sent - status.inProgress >= 2) - return TxBroadcastSuccess; - return TxWaiting; + return FloweePay::TxBroadcastSuccess; + return infoObject->broadcastStatus(); } PaymentDetail *Payment::addExtraOutput() diff --git a/src/Payment.h b/src/Payment.h index 0e70697..87d69ff 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -18,8 +18,8 @@ #ifndef PAYMENT_H #define PAYMENT_H +#include "FloweePay.h" -#include #include #include @@ -71,7 +71,7 @@ class Payment : public QObject /// If input is valid, tx can be prepared. \see prepare() Q_PROPERTY(bool isValid READ validate NOTIFY validChanged) Q_PROPERTY(QList details READ paymentDetails NOTIFY paymentDetailsChanged) - Q_PROPERTY(BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) + Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) /// The price of on BCH Q_PROPERTY(int fiatPrice READ fiatPrice WRITE setFiatPrice NOTIFY fiatPriceChanged) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) @@ -99,34 +99,6 @@ public: }; Q_ENUM(DetailType) - /** - * The broadcast status is a statemachine to indicate if the transaction - * has been offered to the network to the final state of - * either TxRejected or TxBroadcastSuccess - * - * The statemachine goes like this; - * - * 0. `NotStarted` - * 1. After the API call 'broadcast()' we offer the transaction to all peers. - * `TxOffered` - * 2. A peer responds by downloading the actual transaction from us. - * `TxWaiting` - * 3. Optionally, a peer responds with 'rejected' if the transaction is somehow wrong. - * `TxRejected` - * Stop here. - * 4. We waited a little time and no rejected came in, implying 2 or more peers like our tx. - * `TxBroadcastSuccess` - */ - enum BroadcastStatus { - NotStarted, //< We have not yet seen a call to broadcast() - TxOffered, //< Tx has not been offered to any peers. - TxSent1, //< Tx has been sent to at least one peer. - TxWaiting, //< Tx has been downloaded by more than one peer. - TxBroadcastSuccess, //< Tx broadcast and accepted by multiple peers. - TxRejected //< Tx has been offered, downloaded and rejected by at least one peer. - }; - Q_ENUM(BroadcastStatus) - enum Warning { InsecureTransport, DownloadFailed, @@ -242,7 +214,7 @@ public: QList paymentDetails() const; - BroadcastStatus broadcastStatus() const; + FloweePay::BroadcastStatus broadcastStatus() const; /// The exchange rate. The amount of cents for one BCH. int fiatPrice() const; diff --git a/src/TxInfoObject.cpp b/src/TxInfoObject.cpp index e54c1b1..a3fa1be 100644 --- a/src/TxInfoObject.cpp +++ b/src/TxInfoObject.cpp @@ -26,13 +26,13 @@ #include constexpr int SECTION = 10004; +constexpr int WaitingTimeoutMs = 950; -TxInfoObject::TxInfoObject(Payment *payment, const Tx &tx) +TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx) : BroadcastTxData(tx), - m_wallet(payment->wallet()) + m_wallet(wallet) { - assert(payment); - assert(m_wallet); + assert(wallet); auto iter = m_wallet->m_txidCache.find(hash()); assert(iter != m_wallet->m_txidCache.end()); m_txIndex = iter->second; @@ -85,7 +85,7 @@ int TxInfoObject::txIndex() const return m_txIndex; } -TxInfoObject::Status TxInfoObject::calcStatus(int waitingTimeoutMS) const +TxInfoObject::Status TxInfoObject::calcStatus() const { Status rc; auto copy = m_broadcasts; @@ -94,7 +94,7 @@ TxInfoObject::Status TxInfoObject::calcStatus(int waitingTimeoutMS) const const auto time = std::chrono::system_clock::now(); const auto milliTime = std::chrono::duration_cast(time.time_since_epoch()); - const uint64_t cutoff = milliTime.count() - waitingTimeoutMS; + const uint64_t cutoff = milliTime.count() - WaitingTimeoutMs; for (const auto &broadcast : copy) { if (broadcast.failed) @@ -105,6 +105,20 @@ TxInfoObject::Status TxInfoObject::calcStatus(int waitingTimeoutMS) const return rc; } +FloweePay::BroadcastStatus TxInfoObject::broadcastStatus() const +{ + assert(thread() == QThread::currentThread()); + Status status = calcStatus(); + logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; + if (status.sent == 0) + return FloweePay::TxOffered; + if (status.failed) + return FloweePay::TxRejected; + if (status.sent - status.inProgress >= 2) + return FloweePay::TxBroadcastSuccess; + return FloweePay::TxWaiting; +} + /* * The broadcast happens in INV style, and we wait for the peer to then get the data, * which we notice with the 'sentOne' call. Called once per peer. @@ -119,7 +133,7 @@ TxInfoObject::Status TxInfoObject::calcStatus(int waitingTimeoutMS) const void TxInfoObject::checkState() { assert(thread() == QThread::currentThread()); - Status status = calcStatus(3000); + Status status = calcStatus(); logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; // a typical wallet has 3 peers. // we react when at least 2 downloaded our transaction and didn't report a failure diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h index 401fd4f..cb69a3c 100644 --- a/src/TxInfoObject.h +++ b/src/TxInfoObject.h @@ -18,17 +18,16 @@ #ifndef TXINFOOBJECT_H #define TXINFOOBJECT_H -#include +#include "FloweePay.h" + #include #include #include #include - class Wallet; class Payment; - /** * This is used to broadcast transactions. * @@ -54,7 +53,7 @@ class TxInfoObject : public QObject, public BroadcastTxData { Q_OBJECT public: - TxInfoObject(Payment *payment, const Tx &tx); + TxInfoObject(Wallet *wallet, const Tx &tx); TxInfoObject(Wallet *parent, int txIndex, const Tx &tx); void sentVia(const std::shared_ptr &peer) override; @@ -68,7 +67,9 @@ public: int inProgress = 0; // number of peers in progress. int failed = 0; // number of peers reported failure. }; - Status calcStatus(int waitingTimeoutMS = 3000) const; + Status calcStatus() const; + + FloweePay::BroadcastStatus broadcastStatus() const; private slots: // called some seconds after the request from a peer for data -- 2.54.0 From df9f294ee9d3e9e34dc1527811a4de1b9323e8fb Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 5 Oct 2024 21:13:14 +0200 Subject: [PATCH 0958/1428] Add camera help text to SendSweep page. --- guis/Flowee/QRScanner.qml | 14 +++++---- modules/send-sweep/QMLSweepHandler.cpp | 41 ++++++++++++++++++++++++-- modules/send-sweep/QMLSweepHandler.h | 20 +++++++++---- modules/send-sweep/SendPage.qml | 3 +- src/CameraController.cpp | 17 +++++++++++ src/CameraController.h | 6 ++++ src/QRScanner.cpp | 17 +++++++++-- src/QRScanner.h | 12 +++++++- 8 files changed, 111 insertions(+), 19 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index b264072..b4ad176 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -47,7 +47,7 @@ FocusScope { anchors.fill: parent } - // The 'progress' icon. + // The 'cutout' overlay. Shape { id: cutout anchors.fill: parent.fill @@ -203,20 +203,22 @@ FocusScope { text: { if (isLoading) return ""; - if (!CameraController.isPayment) - return ""; + // users of the QRScanner QML object (not this file!) can request a helpText to show if nothing else is. + var bareText = CameraController.helpText; + if (!CameraController.isPayment) + return bareText; // slight hack, make this field be re-calculated on camera active change in order to ensure - // we have the latest limit (which doesn't have a signal of its owo) + // we have the latest limit (which doesn't have a signal of its own) var dummy = CameraController.cameraActive; let cur = portfolio.current; if (cur === null || !cur.allowInstaPay) - return ""; + return bareText; let fiatName = Fiat.currencyName; let limit = cur.fiatInstaPayLimit(fiatName); if (limit === 0) - return ""; + return bareText; var answer = qsTr("Instant Pay limit is %1").arg(Fiat.formattedPrice(limit)); if (!portfolio.singleAccountSetup) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 198d38b..a3a1621 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -106,12 +106,17 @@ void QMLSweepHandler::setCurrentAccount(AccountInfo *account) { if (m_account == account) return; + assert(!m_txBroadcastStarted); + if (m_txBroadcastStarted) + return; m_account = account; emit currentAccountChanged(); } void QMLSweepHandler::markUserApproved() { + if (m_txBroadcastStarted) + return; assert(m_account); if (!m_account) { logFatal(10007) << "Missing account"; @@ -122,9 +127,39 @@ void QMLSweepHandler::markUserApproved() return; } - // TODO add an output from the m_account->wallet - // TODO broadcast. - // TODO proxy the broadcast properties, like Payment does. + KeyId address; + /*int privKeyId = */ m_account->wallet()->reserveUnusedAddress(address, Wallet::ChangePath); + m_builder.selectOutput(0); + m_builder.pushOutputPay2Address(address); + auto tx = m_builder.createTransaction(); + m_account->wallet()->newTransaction(tx); + m_account->wallet()->setTransactionComment(tx, tr("Swept funds")); + + // call to wallet to mark outputs locked and save tx. + m_infoObject = std::make_shared(m_account->wallet(), tx); + connect(m_infoObject.get(), &TxInfoObject::sentOne, this, [=]() { + emit broadcastStatusChanged(); + // we wait a second and force a recalc in the following singleShot. + // NOTE: if you change this number, keep it slightly above the WaitingTimeoutMs + // time in the TxInfoObject class. + QTimer::singleShot(1000, this, SIGNAL(broadcastStatusChanged())); + }, Qt::QueuedConnection); + + // notice that rejections are automatically forwarded to the wallet from the TxInfoObject + connect(m_infoObject.get(), SIGNAL(rejectionSeen()), this, SIGNAL(broadcastStatusChanged()), Qt::QueuedConnection); + FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); + m_txBroadcastStarted = true; + emit broadcastStatusChanged(); +} + +FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const +{ + if (!m_txBroadcastStarted) + return FloweePay::NotStarted; + auto infoObject = m_infoObject; + if (infoObject.get() == nullptr) + return FloweePay::TxBroadcastSuccess; + return infoObject->broadcastStatus(); } void QMLSweepHandler::start() diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 78ae591..633cca6 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -19,23 +19,26 @@ #define QMLSWEEPHANDLER_H -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "TransactionsFetcher.h" -#include "AccountInfo.h" +#include + +#include class TransactionsFetcher; class AccountInfo; - class QMLSweepHandler : public QObject { Q_OBJECT Q_PROPERTY(QString privKey READ privKey WRITE setPrivKey NOTIFY privKeyChanged FINAL) Q_PROPERTY(Error error READ error NOTIFY errorChanged FINAL) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) + Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) public: QMLSweepHandler(QObject *parent = nullptr); @@ -58,10 +61,13 @@ public: Q_INVOKABLE void markUserApproved(); + FloweePay::BroadcastStatus broadcastStatus() const; + signals: void privKeyChanged(); void errorChanged(); void currentAccountChanged(); + void broadcastStatusChanged(); private slots: void start(); @@ -75,6 +81,8 @@ private: PrivateKey m_key; TransactionBuilder m_builder; TransactionsFetcher *m_fetcher; + bool m_txBroadcastStarted = false; + std::shared_ptr m_infoObject; }; #endif diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 6a7884f..955e392 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -27,12 +27,12 @@ Mobile.Page { id: root headerText: qsTr("Sweep a wallet") - Item { // data QRScanner { id: scanner scanType: QRScanner.PrivateKeyWIF autostart: true + helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult if (rc === "") { // scanning interrupted @@ -45,6 +45,7 @@ Mobile.Page { SweepHandler { id: sweeper + account: pay.portfolio.current } } diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 6fdce51..6257f4c 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -63,6 +63,7 @@ public: AskingState state; QObject *camera = nullptr; QObject *videoSink = nullptr; + QString helpText; QPointer scanRequest; bool isPaymentType = true; // follows ScanType from the request @@ -264,6 +265,19 @@ void CameraController::initCamera() d->initCamera(); } +QString CameraController::helpText() const +{ + return d->helpText; +} + +void CameraController::setHelpText(const QString &text) +{ + if (d->helpText == text) + return; + d->helpText = text; + emit helpTextChanged(); +} + // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) @@ -513,6 +527,7 @@ void CameraController::startRequest(QRScanner *request) { assert(request); d->scanRequest = request; + setHelpText(request->helpText()); d->isPaymentType = request->scanType() == QRScanner::PaymentDetails || request->scanType() == QRScanner::PaymentDetailsTestnet; emit isPaymentChanged(); @@ -669,6 +684,7 @@ bool CameraController::pasteData(const QString &string) // then the above emit would have no effect; qrScanFinished(); } + setHelpText(QString()); } return !string.isEmpty(); } @@ -744,6 +760,7 @@ void CameraController::qrScanFinished() d->torchEnabled = false; emit torchEnabledChanged(); } + setHelpText(QString()); } void CameraController::checkState() diff --git a/src/CameraController.h b/src/CameraController.h index 33d2b93..acc12bf 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -49,6 +49,8 @@ class CameraController : public QObject Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) Q_PROPERTY(bool torchEnabled READ torchEnabled WRITE setTorchEnabled NOTIFY torchEnabledChanged) + /// The page that requested the camera provided a help text for the user. + Q_PROPERTY(QString helpText READ helpText NOTIFY helpTextChanged FINAL) Q_PROPERTY(QObject* camera READ camera WRITE setCamera NOTIFY cameraChanged) Q_PROPERTY(QObject* videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged) public: @@ -77,6 +79,9 @@ public: bool torchEnabled() const; void setTorchEnabled(bool on); + QString helpText() const; + void setHelpText(const QString &text); + signals: void cameraChanged(); void videoSinkChanged(); @@ -89,6 +94,7 @@ signals: // \internal (used to move thread) void startCheckState(); + void helpTextChanged(); private slots: void qrScanFinished(); diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index b40b999..d42f085 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -26,7 +26,7 @@ QRScanner::QRScanner(QObject *parent) : QObject(parent), - m_scanType(static_cast(100)) + m_scanType(InvalidType) { // after construction and after QML setting all the properties, run the 'completed' slot. QTimer::singleShot(1, this, SLOT(completed())); @@ -36,7 +36,7 @@ void QRScanner::start() { #ifndef NO_MULTIMEDIA resetScanResult(); - if (m_scanType == static_cast(100)) + if (m_scanType == InvalidType) throw std::runtime_error("Required property scanType not set"); setIsScanning(true); FloweePay::instance()->cameraController()->startRequest(this); @@ -118,6 +118,19 @@ void QRScanner::completed() start(); } +QString QRScanner::helpText() const +{ + return m_helpText; +} + +void QRScanner::setHelpText(const QString &newHelpText) +{ + if (m_helpText == newHelpText) + return; + m_helpText = newHelpText; + emit helpTextChanged(); +} + QRScanner::ResultSource QRScanner::resultSource() const { return m_resultSource; diff --git a/src/QRScanner.h b/src/QRScanner.h index 17c2144..b4d3b74 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -29,6 +29,8 @@ class QRScanner : public QObject Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) + /// Set a help text to be displayed at the top of the scanning-page. + Q_PROPERTY(QString helpText READ helpText WRITE setHelpText NOTIFY helpTextChanged FINAL) public: explicit QRScanner(QObject *parent = nullptr); @@ -39,7 +41,9 @@ public: SeedOrPrivKey, PaymentDetails, PaymentDetailsTestnet, - PrivateKeyWIF + PrivateKeyWIF, + + InvalidType = 100 }; Q_ENUM(ScanType) @@ -65,6 +69,9 @@ public: ResultSource resultSource() const; + QString helpText() const; + void setHelpText(const QString &newHelpText); + private slots: void completed(); @@ -75,10 +82,13 @@ signals: void autostartChanged(); void isScanningChanged(); + void helpTextChanged(); + private: ScanType m_scanType; ResultSource m_resultSource = Camera; QString m_scanResult; + QString m_helpText; bool m_autostart = false; bool m_isScanning = false; }; -- 2.54.0 From 975408a7ca69c0b5f687e25e9f88021e0be90723 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 5 Oct 2024 21:29:32 +0200 Subject: [PATCH 0959/1428] Add UX improvement. This replaces or starts the error message directing the user to unlock the wallet before payment can commence. --- src/Payment.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index e78e5a5..bbd9f7e 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -191,8 +191,10 @@ void Payment::prepare() throw std::runtime_error("can't prepare an invalid Payment"); m_wallet = m_account->wallet(); if (m_wallet->encryption() > Wallet::NotEncrypted) { - if (!m_wallet->isDecrypted()) + if (!m_wallet->isDecrypted()) { + forwardError(tr("Wallet is locked")); throw std::runtime_error("Wallet needs to be decrypted first"); + } } TransactionBuilder builder; -- 2.54.0 From 0ecb7521adc224751fb7f9eaaa4a5091cdf28dfa Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 5 Oct 2024 21:30:39 +0200 Subject: [PATCH 0960/1428] Make AccountSelectorWidget reusable without a Payment --- guis/mobile/AccountSelectorWidget.qml | 10 ++++++---- guis/mobile/PayWithQR.qml | 1 + modules/build-transaction/PayToOthers.qml | 1 + modules/send-sweep/SendPage.qml | 8 +++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 80186dd..0b36cfe 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -32,6 +32,8 @@ Rectangle { property bool stickyAccount: false // list of actions to put in a menu when clicking on the 'balance' side. property var balanceActions: [ ] + property var startingAccount: portfolio.current + property var selectedAccount: startingAccount height: { var w = 20 + currentWalletValue.implicitWidth; @@ -57,7 +59,7 @@ Rectangle { y: 10 x: root.stickyAccount ? 10 : 20 width: parent.width - 30 - text: payment.account.name + text: root.selectedAccount.name visible: !portfolio.singleAccountSetup color: root.stickyAccount ? palette.brightText : palette.windowText } @@ -68,7 +70,7 @@ Rectangle { anchors.bottom: parent.bottom anchors.margins: 10 value: { - var wallet = payment.account; + var wallet = root.selectedAccount return wallet.balanceConfirmed + wallet.balanceUnconfirmed; } } @@ -100,8 +102,8 @@ Rectangle { id: accountSelector width: root.width y: -10 - onSelectedAccountChanged: payment.account = selectedAccount - selectedAccount: payment.account + onSelectedAccountChanged: root.selectedAccount = selectedAccount + selectedAccount: root.startingAccount // when showing the selector widget these make little to no sense. showTotalBalance: false diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 8772535..79473f3 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -336,6 +336,7 @@ Page { return Math.max(y, altY); } + onSelectedAccountChanged: payment.account = selectedAccount balanceActions: { if (editPrice.checked) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index cfc4188..44e87d7 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -320,6 +320,7 @@ Page { anchors.bottom: numericKeyboard.top anchors.bottomMargin: 10 stickyAccount: true + onSelectedAccountChanged: payment.account = selectedAccount balanceActions: [ sendAllAction ] } diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 955e392..8709edc 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -31,7 +31,7 @@ Mobile.Page { QRScanner { id: scanner scanType: QRScanner.PrivateKeyWIF - autostart: true + // autostart: true helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult @@ -49,4 +49,10 @@ Mobile.Page { } } + Mobile.AccountSelectorWidget { + id: walletSelector + y: 200 + onSelectedAccountChanged: sweeper.account = selectedAccount + } + } -- 2.54.0 From 6bc6063bf0c0d4c4b602280726e0d903f8033615 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 00:15:18 +0200 Subject: [PATCH 0961/1428] Visual fix. Make it white. --- guis/Flowee/ProgressCheckIcon.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml index 97b0bf8..570f49c 100644 --- a/guis/Flowee/ProgressCheckIcon.qml +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -44,7 +44,7 @@ Item { ShapePath { id: checkPath strokeWidth: 16 - strokeColor: "green" + strokeColor: "#e8e8e8" fillColor: "transparent" capStyle: ShapePath.RoundCap startX: 52; startY: 80 -- 2.54.0 From d7be2d27d602197910d74329502f7f9847316483 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 00:17:04 +0200 Subject: [PATCH 0962/1428] Add sendsweep properties This exposes some of the backend data to be shown in the UI. Also Make 'paste' available for WIFs. --- guis/Flowee/QRScanner.qml | 13 +- guis/mobile/Page.qml | 4 +- modules/send-sweep/QMLSweepHandler.cpp | 67 ++++++++- modules/send-sweep/QMLSweepHandler.h | 20 ++- modules/send-sweep/SendPage.qml | 194 ++++++++++++++++++++++++- src/CameraController.cpp | 25 ++-- src/CameraController.h | 18 +-- 7 files changed, 311 insertions(+), 30 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index b4ad176..a551887 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -114,7 +114,7 @@ FocusScope { x: 50 anchors.top: topBar.bottom anchors.topMargin: 6 - visible: CameraController.supportsPaste && cbh.text !== "" + visible: cbh.text !== "" radius: 6 width: pasteButton.width height: pasteButton.height @@ -128,7 +128,16 @@ FocusScope { ClipboardHelper { id: cbh - filter: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses | ClipboardHelper.AddressUrl + filter: { + if (CameraController.isPayment) + return ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses | ClipboardHelper.AddressUrl + var type = CameraController.scanType; + if (type === QRScanner.PrivateKeyWIF) + return ClipboardHelper.PrivateKey; + if (type === QRScanner.SeedOrPrivKey) + return ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed; + return 1024; + } enabled: CameraController.cameraActive } diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index d82cb3b..10cc181 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -32,13 +32,14 @@ QQC2.Control { leftPadding: 10 rightPadding: 10 - topPadding: header.height + topPadding: hideHeader ? 0 : header.height default property alias content: focusScope.children property alias headerText: headerLabel.text property alias headerButtonVisible: headerButton.visible property alias headerButtonText: headerButton.text property alias headerButtonEnabled: headerButton.enabled + property bool hideHeader: false /** * A list of action objects to populate the menu with * Setting this will make a hamburger button show up in the header @@ -77,6 +78,7 @@ QQC2.Control { width: parent.width // + 20 height: 50 color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue + visible: !root.hideHeader Image { id: backButton diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index a3a1621..bdb66a0 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -131,6 +131,8 @@ void QMLSweepHandler::markUserApproved() /*int privKeyId = */ m_account->wallet()->reserveUnusedAddress(address, Wallet::ChangePath); m_builder.selectOutput(0); m_builder.pushOutputPay2Address(address); + setTargetAddress(renderAddress(address)); + auto tx = m_builder.createTransaction(); m_account->wallet()->newTransaction(tx); m_account->wallet()->setTransactionComment(tx, tr("Swept funds")); @@ -156,6 +158,28 @@ FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const { if (!m_txBroadcastStarted) return FloweePay::NotStarted; +#if 0 + // This default-disabled code-snippet is fun to allow developing the UX/GUI by stepping through the steps. + // Alter the 'i == 4' to another value to make it stop at the step you want to see longer. + static int i = 0; + static QTimer *timer = nullptr; + if (timer == nullptr) { + timer = new QTimer(const_cast(this)); + timer->start(3000); + connect(timer, &QTimer::timeout, [=]() { + if (++i == 4) + timer->stop(); + emit const_cast(this)->broadcastStatusChanged(); + }); + } + switch (i) { + case 0: return FloweePay::NotStarted; + case 1: return FloweePay::TxOffered; + case 3: return FloweePay::TxRejected; + case 4: return FloweePay::TxBroadcastSuccess; + default: return FloweePay::TxWaiting; + } +#endif auto infoObject = m_infoObject; if (infoObject.get() == nullptr) return FloweePay::TxBroadcastSuccess; @@ -189,7 +213,7 @@ void QMLSweepHandler::startTxBuilder(const QList &r for (const auto &prevOut : result) { auto txIdBytes = QByteArray::fromHex(prevOut.txid.toLatin1()); assert (txIdBytes.size() == 32); - m_builder.appendInput(uint256(txIdBytes.begin()), prevOut.outIndex); + m_builder.appendInput(uint256(txIdBytes.constData()), prevOut.outIndex); QFile txIn(prevOut.filename); if (!txIn.open(QIODevice::ReadOnly)) { @@ -219,4 +243,45 @@ void QMLSweepHandler::startTxBuilder(const QList &r logInfo(10007) << "Built a transaction with" << m_builder.inputCount() << " => " << m_builder.outputCount(); logInfo(10007) << "Total sats:" << inputs; + setSweepTotal(inputs); + setPrepared(true); +} + +double QMLSweepHandler::sweepTotal() const +{ + return m_sweepTotal; +} + +void QMLSweepHandler::setSweepTotal(double newSweepTotal) +{ + if (qFuzzyCompare(m_sweepTotal, newSweepTotal)) + return; + m_sweepTotal = newSweepTotal; + emit sweepTotalChanged(); +} + +bool QMLSweepHandler::prepared() const +{ + return m_prepared; +} + +void QMLSweepHandler::setPrepared(bool newPrepared) +{ + if (m_prepared == newPrepared) + return; + m_prepared = newPrepared; + emit preparedChanged(); +} + +QString QMLSweepHandler::targetAddress() const +{ + return m_targetAddress; +} + +void QMLSweepHandler::setTargetAddress(const QString &newTargetAddress) +{ + if (m_targetAddress == newTargetAddress) + return; + m_targetAddress = newTargetAddress; + emit targetAddressChanged(); } diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 633cca6..2ca1ac5 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -39,6 +39,9 @@ class QMLSweepHandler : public QObject Q_PROPERTY(Error error READ error NOTIFY errorChanged FINAL) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) + Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged FINAL) + Q_PROPERTY(bool prepared READ prepared WRITE setPrepared NOTIFY preparedChanged FINAL) + Q_PROPERTY(double sweepTotal READ sweepTotal NOTIFY sweepTotalChanged FINAL) public: QMLSweepHandler(QObject *parent = nullptr); @@ -63,11 +66,23 @@ public: FloweePay::BroadcastStatus broadcastStatus() const; + QString targetAddress() const; + void setTargetAddress(const QString &newTargetAddress); + + bool prepared() const; + void setPrepared(bool newPrepared); + + double sweepTotal() const; + void setSweepTotal(double newSweepTotal); + signals: void privKeyChanged(); void errorChanged(); void currentAccountChanged(); void broadcastStatusChanged(); + void targetAddressChanged(); + void preparedChanged(); + void sweepTotalChanged(); private slots: void start(); @@ -76,13 +91,16 @@ private slots: private: AccountInfo *m_account = nullptr; QString m_privKey; + QString m_targetAddress; Streaming::ConstBuffer m_addressHash; Error m_error = NoError; PrivateKey m_key; TransactionBuilder m_builder; TransactionsFetcher *m_fetcher; - bool m_txBroadcastStarted = false; std::shared_ptr m_infoObject; + bool m_prepared = false; // tx is prepared + bool m_txBroadcastStarted = false; + double m_sweepTotal = 0; }; #endif diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 8709edc..c347246 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -31,14 +31,15 @@ Mobile.Page { QRScanner { id: scanner scanType: QRScanner.PrivateKeyWIF - // autostart: true + autostart: true helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult if (rc === "") { // scanning interrupted - thePile.pop(); + // thePile.pop(); return; } + root.forceActiveFocus(); sweeper.privKey = rc; } } @@ -49,10 +50,199 @@ Mobile.Page { } } + Flowee.Label { + text: "bla" + // TODO incorporate the findings of the sweeper. + } + Mobile.AccountSelectorWidget { id: walletSelector y: 200 onSelectedAccountChanged: sweeper.account = selectedAccount } + Mobile.SlideToApprove { + id: slideToApprove + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + width: parent.width + enabled: sweeper.prepared + visible: !sweeper.account.needsPinToOpen + onActivated: { + sweeper.markUserApproved(); + root.hideHeader = true; + } + } + + QQC2.Control { + id: broadcastFeedback + anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. + anchors.rightMargin: -10 + anchors.fill: parent + font.pixelSize: root.font.pixelSize * 1.2 + + property int status: sweeper.broadcastStatus + property double bitcoinAmount: sweeper.sweepTotal + property int fiatAmount: bitcoinAmount / 100000000 * Fiat.price + property string targetAddress: sweeper.targetAddress + + states: [ + State { + name: "notStarted" + when: broadcastFeedback.status === FloweePay.NotStarted + }, + State { + name: "preparing" + when: broadcastFeedback.status === FloweePay.TxOffered + PropertyChanges { target: progressIcon; sweepAngle: 90 } + PropertyChanges { target: background; opacity: 1; y: 0 } + }, + State { + name: "sent1" // sent to only one peer + extend: "preparing" + when: broadcastFeedback.status === FloweePay.TxSent1 + PropertyChanges { target: progressIcon; sweepAngle: 150 } + PropertyChanges { target: statusLabel; text: qsTr("Sending Payment") } + }, + State { + name: "waiting" // waiting for possible rejection. + when: broadcastFeedback.status === FloweePay.TxWaiting + extend: "preparing" + PropertyChanges { target: progressIcon; sweepAngle: 320 } + }, + State { + name: "success" // no reject, great success + when: broadcastFeedback.status === FloweePay.TxBroadcastSuccess + extend: "preparing" + PropertyChanges { target: progressIcon + sweepAngle: 320 + startAngle: -20 + } + PropertyChanges { target: progressIcon; showCheck: true } + PropertyChanges { target: statusLabel; text: qsTr("Payment Sent") } + }, + State { + name: "rejected" // a peer didn't like our tx + when: broadcastFeedback.status === FloweePay.TxRejected + extend: "preparing" + PropertyChanges { target: background; color: "#7f0000" } + PropertyChanges { target: progressIcon; opacity: 0 } + PropertyChanges { target: statusLabel; text: qsTr("Failed") } + PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } + } + ] + + Rectangle { + id: background + width: parent.width + height: parent.height + opacity: 0 + visible: opacity > 0 + color: "#7bb688" + y: height + 2 + MouseArea { + anchors.fill: parent // eat all mouse events. + } + Flowee.Label { + id: statusLabel + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + y: 30 + color: "#e8e8e8" + } + + Flowee.ProgressCheckIcon { + id: progressIcon + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: statusLabel.bottom + anchors.topMargin: 20 + } + + Column { + width: parent.width + anchors.top: progressIcon.bottom + anchors.topMargin: 6 + spacing: 6 + Rectangle { + id: errorTextPane + visible: errorLabel.text !== "" + color: mainWindow.errorRedBg + radius: 10 + width: parent.width + height: errorLabel.height + 20 + + Flowee.Label { + id: errorLabel + wrapMode: Text.Wrap + x: 10 + y: 10 + width: parent.width - 20 + horizontalAlignment: Qt.AlignHCenter + } + } + Flowee.Label { + id: fiatLabel + anchors.horizontalCenter: parent.horizontalCenter + color: statusLabel.color + font.pixelSize: statusLabel.font.pixelSize * 2.5 + text: Fiat.formattedPrice(broadcastFeedback.fiatAmount) + visible: Fiat.price !== 0 + } + + Flowee.BitcoinAmountLabel { + id: cryptoAmount + anchors.horizontalCenter: parent.horizontalCenter + value: broadcastFeedback.bitcoinAmount + colorize: false + showFiat: false + color: statusLabel.color + } + Item { width: 10; height: 10 } // spacer + + Flowee.Label { + id: addressLabel + color: statusLabel.color + visible: broadcastFeedback.targetAddress !== "" + width: parent.width - 20 + x: 10 + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.Wrap + text: qsTr("The payment has been sent to %1", "the address we sent it to") + .arg(broadcastFeedback.targetAddress) + } + } + + Rectangle { + id: closeButtonBg + color: statusLabel.color + radius: 10 + width: parent.width - 20 + x: 10 + height: closeButtonLabel.height + 25 + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + Flowee.Label { + id: closeButtonLabel + text: qsTr("Close") + anchors.centerIn: parent + color: background.color + } + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + focus: true + onClicked: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); + } + } + } + + Behavior on opacity { NumberAnimation { } } + Behavior on y { NumberAnimation { } } + Behavior on color { ColorAnimation { } } + } + } } diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 6257f4c..a03ff23 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -66,7 +66,6 @@ public: QString helpText; QPointer scanRequest; - bool isPaymentType = true; // follows ScanType from the request mutable QMutex lock; QVideoFrame currentFrame; @@ -270,6 +269,13 @@ QString CameraController::helpText() const return d->helpText; } +QRScanner::ScanType CameraController::scanType() const +{ + if (d->scanRequest == nullptr) + return QRScanner::InvalidType; + return d->scanRequest->scanType(); +} + void CameraController::setHelpText(const QString &text) { if (d->helpText == text) @@ -528,9 +534,7 @@ void CameraController::startRequest(QRScanner *request) assert(request); d->scanRequest = request; setHelpText(request->helpText()); - d->isPaymentType = request->scanType() == QRScanner::PaymentDetails - || request->scanType() == QRScanner::PaymentDetailsTestnet; - emit isPaymentChanged(); + emit isScanTypeChanged(); if (!d->visible) { d->visible = true; @@ -615,16 +619,12 @@ void CameraController::abort() abortRequest(d->scanRequest); } -bool CameraController::supportsPaste() const +bool CameraController::isPayment() const { if (d->scanRequest == nullptr) return false; - return d->scanRequest->scanType() == QRScanner::PaymentDetails; -} - -bool CameraController::isPayment() const -{ - return d->isPaymentType; + return d->scanRequest->scanType() == QRScanner::PaymentDetails + || d->scanRequest->scanType() == QRScanner::PaymentDetailsTestnet; } bool CameraController::torchEnabled() const @@ -671,7 +671,8 @@ bool CameraController::pasteData(const QString &string) { if (d->scanRequest == nullptr) return false; - if (d->scanRequest->scanType() != QRScanner::PaymentDetails) + if (d->scanRequest->scanType() != QRScanner::PaymentDetails + && d->scanRequest->scanType() != QRScanner::PrivateKeyWIF) return false; if (!string.isEmpty()) { diff --git a/src/CameraController.h b/src/CameraController.h index acc12bf..70477a3 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -18,12 +18,10 @@ #ifndef CAMERACONTROLLER_H #define CAMERACONTROLLER_H -#include -#include -#include + +#include "QRScanner.h" class CameraControllerPrivate; -class QRScanner; /** * This class works together with the QRScannerOverlay QML file. @@ -42,10 +40,9 @@ class CameraController : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) - /// return true if calling importScanFromClipboard can work with current QRScanner::ScanType - Q_PROPERTY(bool supportsPaste READ supportsPaste NOTIFY visibleChanged) /// if the QRScanner::ScanType is a payment. - Q_PROPERTY(bool isPayment READ isPayment NOTIFY isPaymentChanged) + Q_PROPERTY(bool isPayment READ isPayment NOTIFY isScanTypeChanged FINAL) + Q_PROPERTY(QRScanner::ScanType scanType READ scanType NOTIFY isScanTypeChanged FINAL) Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) Q_PROPERTY(bool torchEnabled READ torchEnabled WRITE setTorchEnabled NOTIFY torchEnabledChanged) @@ -74,13 +71,11 @@ public: bool loadCamera() const; bool cameraActive() const; bool visible() const; - bool supportsPaste() const; bool isPayment() const; bool torchEnabled() const; void setTorchEnabled(bool on); - QString helpText() const; - void setHelpText(const QString &text); + QRScanner::ScanType scanType() const; signals: void cameraChanged(); @@ -90,7 +85,7 @@ signals: void cameraActiveChanged(); void visibleChanged(); void torchEnabledChanged(); - void isPaymentChanged(); + void isScanTypeChanged(); // \internal (used to move thread) void startCheckState(); @@ -102,6 +97,7 @@ private slots: void initCamera(); private: + void setHelpText(const QString &text); CameraControllerPrivate * d; }; -- 2.54.0 From 1a5e2c5d5a160131c381b15cd43d28c6ff47220c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 14:05:54 +0200 Subject: [PATCH 0963/1428] UX tweaks. --- guis/mobile/AccountSelectorWidget.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 0b36cfe..33960e9 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -73,15 +73,17 @@ Rectangle { var wallet = root.selectedAccount return wallet.balanceConfirmed + wallet.balanceUnconfirmed; } + colorize: false } MouseArea { anchors.fill: parent onClicked: (mouse) => { - if (mouse.x < parent.width / 2) { + var haveActions = balanceActions.length > 0; + if (!haveActions || mouse.x < parent.width / 2) { if (root.stickyAccount === false && !portfolio.singleAccountSetup) accountSelector.open(); - } else if (balanceActions.length > 0) { + } else if (haveActions) { while (priceMenu.count > 0) { priceMenu.takeItem(0); } -- 2.54.0 From e64a262151c8b93571ce18785837e93175aee046 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 14:06:43 +0200 Subject: [PATCH 0964/1428] Aid in development, increase log level of remote --- src/ElectronXClient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index 4add65b..f1d1705 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -104,7 +104,7 @@ void ElectronXClient::socketDataAvailable() void ElectronXClient::connectToServer() { - logInfo(10005) << "ImportHandler connecting to" << m_serviceAddress; + logCritical(10005) << "ElectronXClient connecting to" << m_serviceAddress; m_electronServer->connectToHostEncrypted( QString::fromStdString(m_serviceAddress.hostname), m_serviceAddress.announcePort); } -- 2.54.0 From 6aff98edf108874389c91de1c5f14a9fb9cc5869 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 14:07:33 +0200 Subject: [PATCH 0965/1428] Export more properties and make UX of sweep look good --- modules/send-sweep/QMLSweepHandler.cpp | 52 +++++++++++++++++++++- modules/send-sweep/QMLSweepHandler.h | 19 ++++++++ modules/send-sweep/SendPage.qml | 48 +++++++++++++++++--- modules/send-sweep/TransactionsFetcher.cpp | 1 + modules/send-sweep/TransactionsFetcher.h | 9 +++- 5 files changed, 121 insertions(+), 8 deletions(-) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index bdb66a0..273dcb5 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -40,6 +40,14 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) this->start(); }, Qt::QueuedConnection); + connect (m_fetcher, &TransactionsFetcher::searchComplete, this, [=]() { + auto f = m_fetcher; + if (f) { + setNumOutputsFound(m_fetcher->numOutputsFound()); + setNumTokensFound(m_fetcher->numTokensFound()); + } + }); + connect (m_fetcher, &TransactionsFetcher::finished, this, &QMLSweepHandler::startTxBuilder); } @@ -75,10 +83,11 @@ void QMLSweepHandler::setPrivKey(const QString &newPrivKey) m_key = key; const auto id = key.getPubKey().getKeyId(); assert(id.size() == 20); + setSweepAddress(renderAddress(id)); + CashAddress::Content cashContent; cashContent.type = CashAddress::PUBKEY_TYPE; cashContent.hash = std::vector(id.begin(), id.end()); - m_addressHash = CashAddress::createHashedOutputScript(cashContent); QTimer::singleShot(10, this, SLOT(start())); @@ -205,6 +214,8 @@ void QMLSweepHandler::startTxBuilder(const QList &r assert(m_fetcher); assert(m_builder.inputCount() == 0); assert(m_builder.outputCount() == 0); + setNumOutputsFound(m_fetcher->numOutputsFound()); + setNumTokensFound(m_fetcher->numTokensFound()); m_fetcher->deleteLater(); m_fetcher = nullptr; @@ -247,6 +258,45 @@ void QMLSweepHandler::startTxBuilder(const QList &r setPrepared(true); } +QString QMLSweepHandler::sweepAddress() const +{ + return m_sweepAddress; +} + +void QMLSweepHandler::setSweepAddress(const QString &newSweepAddress) +{ + if (m_sweepAddress == newSweepAddress) + return; + m_sweepAddress = newSweepAddress; + emit sweepAddressChanged(); +} + +int QMLSweepHandler::numOutputsFound() const +{ + return m_numOutputsFound; +} + +void QMLSweepHandler::setNumOutputsFound(int newNumOutputsFound) +{ + if (m_numOutputsFound == newNumOutputsFound) + return; + m_numOutputsFound = newNumOutputsFound; + emit numOutputsFoundChanged(); +} + +int QMLSweepHandler::numTokensFound() const +{ + return m_numTokensFound; +} + +void QMLSweepHandler::setNumTokensFound(int newNumTokensFound) +{ + if (m_numTokensFound == newNumTokensFound) + return; + m_numTokensFound = newNumTokensFound; + emit numTokensFoundChanged(); +} + double QMLSweepHandler::sweepTotal() const { return m_sweepTotal; diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 2ca1ac5..9fac889 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -39,9 +39,12 @@ class QMLSweepHandler : public QObject Q_PROPERTY(Error error READ error NOTIFY errorChanged FINAL) Q_PROPERTY(AccountInfo *account READ currentAccount WRITE setCurrentAccount NOTIFY currentAccountChanged) Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged) + Q_PROPERTY(QString sweepAddress READ sweepAddress WRITE setSweepAddress NOTIFY sweepAddressChanged FINAL) Q_PROPERTY(QString targetAddress READ targetAddress WRITE setTargetAddress NOTIFY targetAddressChanged FINAL) Q_PROPERTY(bool prepared READ prepared WRITE setPrepared NOTIFY preparedChanged FINAL) Q_PROPERTY(double sweepTotal READ sweepTotal NOTIFY sweepTotalChanged FINAL) + Q_PROPERTY(int numTokensFound READ numTokensFound NOTIFY numTokensFoundChanged FINAL) + Q_PROPERTY(int numOutputsFound READ numOutputsFound NOTIFY numOutputsFoundChanged FINAL) public: QMLSweepHandler(QObject *parent = nullptr); @@ -75,6 +78,15 @@ public: double sweepTotal() const; void setSweepTotal(double newSweepTotal); + int numTokensFound() const; + void setNumTokensFound(int newNumTokensFound); + + int numOutputsFound() const; + void setNumOutputsFound(int newNumOutputsFound); + + QString sweepAddress() const; + void setSweepAddress(const QString &newSweepAddress); + signals: void privKeyChanged(); void errorChanged(); @@ -83,6 +95,9 @@ signals: void targetAddressChanged(); void preparedChanged(); void sweepTotalChanged(); + void numTokensFoundChanged(); + void numOutputsFoundChanged(); + void sweepAddressChanged(); private slots: void start(); @@ -91,6 +106,7 @@ private slots: private: AccountInfo *m_account = nullptr; QString m_privKey; + QString m_sweepAddress; QString m_targetAddress; Streaming::ConstBuffer m_addressHash; Error m_error = NoError; @@ -101,6 +117,9 @@ private: bool m_prepared = false; // tx is prepared bool m_txBroadcastStarted = false; double m_sweepTotal = 0; + + int m_numTokensFound = 0; + int m_numOutputsFound = 0; }; #endif diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index c347246..b461c10 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -25,7 +25,7 @@ import Flowee.org.pay.SendSweep; Mobile.Page { id: root - headerText: qsTr("Sweep a wallet") + headerText: qsTr("Sweep coins") Item { // data QRScanner { @@ -36,7 +36,7 @@ Mobile.Page { onFinished: { var rc = scanResult if (rc === "") { // scanning interrupted - // thePile.pop(); + thePile.pop(); return; } root.forceActiveFocus(); @@ -50,16 +50,52 @@ Mobile.Page { } } - Flowee.Label { - text: "bla" - // TODO incorporate the findings of the sweeper. + Column { + width: parent.width + spacing: 6 + + Flowee.Label { + text: qsTr("Sweeping from address:") + font.bold: true + width: parent.width + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.Wrap + } + Flowee.Label { + text: sweeper.sweepAddress + width: parent.width + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.Wrap + fontSizeMode: Text.HorizontalFit + } + Flowee.Label { + id: coinsLabel + visible: sweeper.prepared + text: qsTr("Found %1 coins on address.").arg(sweeper.numOutputsFound) + } + Flowee.Label { + visible: sweeper.prepared && sweeper.numTokensFound + text: qsTr("Ignoring %1 tokens.").arg(sweeper.numTokensFound) + } + Flowee.BitcoinAmountLabel { + font.pixelSize: coinsLabel.font.pixelSize * 1.2 + visible: sweeper.prepared + value: sweeper.sweepTotal + anchors.right: parent.right + } } Mobile.AccountSelectorWidget { id: walletSelector - y: 200 + visible: !portfolio.singleAccountSetup + y: 220 onSelectedAccountChanged: sweeper.account = selectedAccount } + Flowee.Label { + visible: walletSelector.visible + anchors.bottom: walletSelector.top + text: qsTr("Transfer to:") + } Mobile.SlideToApprove { id: slideToApprove diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index b0da185..37a6269 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -100,6 +100,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) } logCritical(10007) << "Remote indexer reported" << m_tokensFound << "tokens &" << m_coinsFound << "coins"; checkAllAvailable(); + emit searchComplete(); } if (id == 3) { // transaction.get diff --git a/modules/send-sweep/TransactionsFetcher.h b/modules/send-sweep/TransactionsFetcher.h index a0f6857..60b0337 100644 --- a/modules/send-sweep/TransactionsFetcher.h +++ b/modules/send-sweep/TransactionsFetcher.h @@ -36,7 +36,15 @@ public: void start(const Streaming::ConstBuffer &scripthash); + int numTokensFound() const { + return m_tokensFound; + } + int numOutputsFound() const { + return m_coinsFound; + } + signals: + void searchComplete(); // the numCoins/numTokens have been determined void finished(const QList &result); protected: @@ -45,7 +53,6 @@ protected: private: void checkAllAvailable(); - Streaming::ConstBuffer m_scriptHash; int m_coinsFound = 0; -- 2.54.0 From 4634e17578b5e77b9bc23d6b99e577be8761a70c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 14:16:43 +0200 Subject: [PATCH 0966/1428] Cleanup after ourselves. The transactions we spent from are downloaded in full, after adding the spending transaction to the wallet we delete them. --- modules/send-sweep/QMLSweepHandler.cpp | 7 +++++++ modules/send-sweep/TransactionsFetcher.cpp | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 273dcb5..27ae240 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -18,6 +18,7 @@ #include "QMLSweepHandler.h" #include "IndexerServices.h" #include +#include #include #include #include @@ -161,6 +162,12 @@ void QMLSweepHandler::markUserApproved() FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); m_txBroadcastStarted = true; emit broadcastStatusChanged(); + + // the transactions we spent were stored in a subdir. + // lets clean up after ourselves. + QDir subdir("sweep"); + if (subdir.exists()) + subdir.removeRecursively(); } FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index 37a6269..8c26b61 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,8 @@ void TransactionsFetcher::start(const Streaming::ConstBuffer &scripthash) { logCritical(1007) << "Starting check online, scripthash:" << scripthash.toHex_reversed(); m_scriptHash = scripthash; + QDir current; + current.mkpath("sweep"); connectToServer(); } @@ -82,7 +85,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) // times if there are multiple outputs on the same tx for this one address. if (txids.contains(o.txid)) continue; - QFileInfo info(QString("sweep-%1").arg(o.txid)); + QFileInfo info(QString("sweep/%1").arg(o.txid)); if (info.exists()) { m_outputs.back().filename = info.absoluteFilePath(); continue; @@ -114,7 +117,7 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) pool->writeHex(hexBytes.constData(), hexBytes.size()); auto txData = pool->commit(); - auto filename = QString("sweep-%1"); + auto filename = QString("sweep/%1"); auto txHash = Tx(txData).createHash(); const auto txid = QString::fromStdString(txHash.ToString()); filename = filename.arg(txid); -- 2.54.0 From abef29f2ba62ba387c2ee3cb312954fe4ca112a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 16:34:36 +0200 Subject: [PATCH 0967/1428] Add support for QR prefix for WIF The protocol handler 'bch-wif:' is now allowed to be in front of the wif encoded private key. --- src/CameraController.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index a03ff23..7e8b126 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -321,10 +321,16 @@ void QRScanningThread::run() switch (m_scanType) { case QRScanner::PrivateKeyWIF: case QRScanner::SeedOrPrivKey: { - // we expect WIF encoded private keys heree - if (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L')) { + // we expect WIF encoded private keys here + // first, when it starts with 'bch-wif:' this helps, but needs to be cut off. + const bool wifPrefix = bytes.size() >= 58 && bytes.size() < 63 + && (bytes[8] == 'K' || bytes[8] == 'L') + && 0 == memcmp(&bytes[0], "bch-wif:", 8); + + if (wifPrefix || (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L'))) { // might be one!! - const std::string str(reinterpret_cast(bytes.data()), bytes.size()); + const size_t prefixSize = wifPrefix ? 8 : 0; + const std::string str(reinterpret_cast(bytes.data() + prefixSize), bytes.size() - prefixSize); std::vector dummy; if (Base58::decodeCheck(str, dummy)) { // good enough for me. Further checking is done by the app, we just exit scanning now. -- 2.54.0 From 392215f31a6f11ace4fb23e649eb1bbbfeb0ff3c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 11:24:30 +0200 Subject: [PATCH 0968/1428] ensure 'rejected' transactions don't have a time shown. --- guis/desktop/Transaction.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index 653371f..c9b70f2 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -78,7 +78,8 @@ Rectangle { function updateText() { if (txRoot.isRejected) text = qsTr("rejected") - text = Pay.formatDateTime(model.date); + else + text = Pay.formatDateTime(model.date); } opacity: detailsPane.item === null ? 0.8 : 1; font.pointSize: mainLabel.font.pointSize * 0.9 @@ -149,6 +150,8 @@ Rectangle { anchors.right: parent.right visible: bitcoinAmountLabel.visible === false text: { + if (isRejected) + return ""; var fiatPrice = Fiat.historicalPrice(model.date); return Fiat.formattedPrice(bitcoinAmountLabel.value, fiatPrice) } -- 2.54.0 From e96a401bc2cb55479a74a511e80f5261fea1bec6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Oct 2024 22:36:10 +0200 Subject: [PATCH 0969/1428] Cleanup and polish the send/sweep module --- guis/mobile/Page.qml | 7 +- modules/send-sweep/QMLSweepHandler.cpp | 34 +++++---- modules/send-sweep/QMLSweepHandler.h | 1 + modules/send-sweep/SendPage.qml | 81 ++++++++++++++++++++-- modules/send-sweep/TransactionsFetcher.cpp | 2 + src/ElectronXClient.cpp | 14 ++-- src/ElectronXClient.h | 2 +- src/QMLImportHelper.cpp | 4 +- 8 files changed, 115 insertions(+), 30 deletions(-) diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 10cc181..955617f 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -32,7 +32,8 @@ QQC2.Control { leftPadding: 10 rightPadding: 10 - topPadding: hideHeader ? 0 : header.height + topPadding: header.height + wheelEnabled: true // eat wheel events default property alias content: focusScope.children property alias headerText: headerLabel.text @@ -75,8 +76,8 @@ QQC2.Control { Rectangle { id: header - width: parent.width // + 20 - height: 50 + width: parent.width + height: root.hideHeader ? 0 : 50 color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue visible: !root.hideHeader diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 27ae240..820420c 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -34,9 +34,11 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) // need them later. FloweePay::instance()->indexerServices()->populate(); - connect (m_fetcher, &TransactionsFetcher::failed, this, [=]() { + m_builder.setAnonimize(true); + + connect (m_fetcher, &TransactionsFetcher::failed, this, [=](int failedLevel) { auto services = FloweePay::instance()->indexerServices(); - services->punish(m_fetcher->service(), 40); + services->punish(m_fetcher->service(), failedLevel); // try again this->start(); }, Qt::QueuedConnection); @@ -67,30 +69,31 @@ void QMLSweepHandler::setPrivKey(const QString &newPrivKey) // if the private key is valid, find the address and outputscript hash which // we'll use to ask electrum-cash for content. - PrivateKey key; CBase58Data string; if (string.SetString(newPrivKey.toStdString())) { auto chain = FloweePay::instance()->chain(); if ((chain == P2PNet::MainChain && string.isMainnetPrivKey()) || (chain == P2PNet::Testnet4Chain && string.isTestnetPrivKey())) { - key.set(string.data().begin(), string.data().begin() + 32, + m_key.set(string.data().begin(), string.data().begin() + 32, string.data().size() > 32 && string.data().at(32) == 1); } } - if (!key.isValid()) { + if (!m_key.isValid()) { setError(InvalidInput); return; } - m_key = key; - const auto id = key.getPubKey().getKeyId(); + const auto id = m_key.getPubKey().getKeyId(); assert(id.size() == 20); - setSweepAddress(renderAddress(id)); - CashAddress::Content cashContent; cashContent.type = CashAddress::PUBKEY_TYPE; cashContent.hash = std::vector(id.begin(), id.end()); m_addressHash = CashAddress::createHashedOutputScript(cashContent); + const std::string &chainPref = FloweePay::instance()->chainPrefix(); + auto s = CashAddress::encodeCashAddr(chainPref, cashContent); + const auto size = chainPref.size(); + setSweepAddress(QString::fromLatin1(s.c_str() + size + 1, s.size() - size -1)); // the 1 is for the colon + QTimer::singleShot(10, this, SLOT(start())); } @@ -139,11 +142,13 @@ void QMLSweepHandler::markUserApproved() KeyId address; /*int privKeyId = */ m_account->wallet()->reserveUnusedAddress(address, Wallet::ChangePath); + // ignore the returned value as we have no intention of ever 'unreserving' the key + // as we just broadcast in this same flow. m_builder.selectOutput(0); m_builder.pushOutputPay2Address(address); setTargetAddress(renderAddress(address)); - auto tx = m_builder.createTransaction(); + const auto tx = m_builder.createTransaction(); m_account->wallet()->newTransaction(tx); m_account->wallet()->setTransactionComment(tx, tr("Swept funds")); @@ -160,6 +165,7 @@ void QMLSweepHandler::markUserApproved() // notice that rejections are automatically forwarded to the wallet from the TxInfoObject connect(m_infoObject.get(), SIGNAL(rejectionSeen()), this, SIGNAL(broadcastStatusChanged()), Qt::QueuedConnection); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); + m_txBroadcastStarted = true; emit broadcastStatusChanged(); @@ -221,17 +227,17 @@ void QMLSweepHandler::startTxBuilder(const QList &r assert(m_fetcher); assert(m_builder.inputCount() == 0); assert(m_builder.outputCount() == 0); + assert(m_key.isValid()); setNumOutputsFound(m_fetcher->numOutputsFound()); setNumTokensFound(m_fetcher->numTokensFound()); m_fetcher->deleteLater(); m_fetcher = nullptr; - m_builder.setAnonimize(false); // Makes no sense.. int64_t inputs = 0; for (const auto &prevOut : result) { - auto txIdBytes = QByteArray::fromHex(prevOut.txid.toLatin1()); - assert (txIdBytes.size() == 32); - m_builder.appendInput(uint256(txIdBytes.constData()), prevOut.outIndex); + logDebug(10007) << "Using input:" << prevOut.txid << prevOut.outIndex; + uint256 txid = uint256S(prevOut.txid.toStdString()); + m_builder.appendInput(txid, prevOut.outIndex); QFile txIn(prevOut.filename); if (!txIn.open(QIODevice::ReadOnly)) { diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 9fac889..3afac23 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -55,6 +55,7 @@ public: FileError, DataInconsistency // specifically the data we got from the indexer. }; + Q_ENUM(Error) QString privKey() const; void setPrivKey(const QString &newPrivKey); diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index b461c10..529ad35 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -65,9 +65,69 @@ Mobile.Page { text: sweeper.sweepAddress width: parent.width horizontalAlignment: Qt.AlignHCenter - wrapMode: Text.Wrap fontSizeMode: Text.HorizontalFit } + + Item { + id: busyIndicator + visible: !sweeper.prepared && sweeper.error === SweepHandler.NoError + width: 105 + height: visible ? 105 : 1 + anchors.horizontalCenter: parent.horizontalCenter + RotationAnimation on rotation { + loops: Animation.Infinite + from: 0 + to: 360 + duration: 6000 + running: busyIndicator.visible + } + + Repeater { + model: 6 + Item { + width: 105 + height: 35 + y: 35 + Rectangle { + color: mainWindow.floweeGreen + width: 30 + height: 30 + x: 70 + radius: 15 + } + rotation: 360 / 6 * index + } + } + } + + Rectangle { + color: mainWindow.errorRedBg + width: parent.width + height: sweepErrorLabel.text === "" ? 0 : sweepErrorLabel.height + 20 + radius: 6 + Flowee.Label { + id: sweepErrorLabel + width: parent.width - 20 + wrapMode: Text.Wrap + horizontalAlignment: Qt.AlignHCenter + anchors.centerIn: parent + text: { + var err = sweeper.error; + if (err === SweepHandler.InvalidInput) + return qsTr("Failed to understand QR"); + if (err === SweepHandler.NoBackendFound) + return "No indexing servers found"; + if (err === SweepHandler.FileError) + return "File not found error"; // not translated as this should never happen. + if (err === SweepHandler.DataInconsistency) + return qsTr("Indexer results invalid. Please try again."); + + if (err === SweepHandler.NoError) + return ""; + } + } + } + Flowee.Label { id: coinsLabel visible: sweeper.prepared @@ -105,8 +165,10 @@ Mobile.Page { enabled: sweeper.prepared visible: !sweeper.account.needsPinToOpen onActivated: { - sweeper.markUserApproved(); - root.hideHeader = true; + root.hideHeader = true; + background.y = 0; + background.opacity = 1; + sweeper.markUserApproved(); } } @@ -131,7 +193,6 @@ Mobile.Page { name: "preparing" when: broadcastFeedback.status === FloweePay.TxOffered PropertyChanges { target: progressIcon; sweepAngle: 90 } - PropertyChanges { target: background; opacity: 1; y: 0 } }, State { name: "sent1" // sent to only one peer @@ -243,8 +304,16 @@ Mobile.Page { x: 10 horizontalAlignment: Qt.AlignHCenter wrapMode: Text.Wrap - text: qsTr("The payment has been sent to %1", "the address we sent it to") - .arg(broadcastFeedback.targetAddress) + text: qsTr("The payment has been sent to:", "Followed by the address") + } + Flowee.Label { + color: statusLabel.color + visible: broadcastFeedback.targetAddress !== "" + width: parent.width - 20 + x: 10 + horizontalAlignment: Qt.AlignHCenter + text: broadcastFeedback.targetAddress + fontSizeMode: Text.HorizontalFit } } diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index 8c26b61..31d2741 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -140,6 +140,8 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) } if (!found) logCritical(10007) << "Received a tx from server that I didn't request" << txid; + out.flush(); + out.close(); checkAllAvailable(); } diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index f1d1705..dbf48e1 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -75,12 +75,18 @@ void ElectronXClient::disconnected() void ElectronXClient::socketError(QAbstractSocket::SocketError error) { - if (error == QAbstractSocket::SslHandshakeFailedError) + int failureLevel = 40; + if (error == QAbstractSocket::SslHandshakeFailedError) { logCritical(10005) << "Remote peer failed SSL handshake" << m_serviceAddress; - else + failureLevel = 400; + } else if (error == QAbstractSocket::NetworkError) { + // probably our side... + failureLevel = 1; + } else { logCritical(10005) << error; + } m_error = true; - emit failed(); + emit failed(failureLevel); } void ElectronXClient::socketDataAvailable() @@ -117,7 +123,7 @@ void ElectronXClient::handleVersion(const QJsonObject &rootObject) logFatal(10005) << "No viable protocol-version found for this server"; m_error = true; m_electronServer->close(); - emit failed(); + emit failed(500); return; } diff --git a/src/ElectronXClient.h b/src/ElectronXClient.h index 9e1b140..cca0755 100644 --- a/src/ElectronXClient.h +++ b/src/ElectronXClient.h @@ -40,7 +40,7 @@ public: bool errored() const; signals: - void failed(); + void failed(int faulureLevel); private slots: void connectionEstablished(); diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp index 73cc967..d4b6f39 100644 --- a/src/QMLImportHelper.cpp +++ b/src/QMLImportHelper.cpp @@ -34,9 +34,9 @@ QMLImportHelper::QMLImportHelper(QObject *parent) connect (m_importHandler, SIGNAL(finished()), this, SLOT(checkFinished()), Qt::QueuedConnection); - connect (m_importHandler, &ImportHandler::failed, this, [=]() { + connect (m_importHandler, &ImportHandler::failed, this, [=](int score) { auto services = FloweePay::instance()->indexerServices(); - services->punish(m_importHandler->service(), 40); + services->punish(m_importHandler->service(), score); m_checking = false; // try again this->startCheck(); -- 2.54.0 From 1c466b62f6c2c18886a513997b2c75fa7c681204 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 20:46:26 +0200 Subject: [PATCH 0970/1428] add some 'as_const' to avoid detaching Qt containers --- src/FloweePay.cpp | 7 +++---- src/ModuleManager.cpp | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 27e48fb..fd3d03a 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -52,7 +52,6 @@ # include #endif -#include #include #include #include @@ -325,7 +324,7 @@ void FloweePay::shutdown() auto *dl = m_downloadManager.get(); if (dl) // p2pNet follows lazy initialization. dl->removeHeaderListener(this); - for (auto wallet : m_wallets) { + for (auto wallet : std::as_const(m_wallets)) { if (dl) { // p2pNet follows lazy initialization. dl->removeDataListener(wallet); dl->removeHeaderListener(wallet); @@ -469,7 +468,7 @@ void FloweePay::init() void FloweePay::loadingCompleted() { - for (auto wallet : m_wallets) { + for (auto wallet : std::as_const(m_wallets)) { wallet->performUpgrades(); } if (m_chain == P2PNet::MainChain) { @@ -1255,7 +1254,7 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const try { int firstWord = -2; // split into words. - for (const auto word : words) { + for (const auto word : std::as_const(words)) { int index = m_hdSeedValidator.findWord(word.toString()); if (firstWord == -2) { bool lowerCased = false; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index f2a8b20..4ba7e33 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include @@ -69,7 +68,7 @@ ModuleManager::ModuleManager(QObject *parent) // connect to signals of the sections so we tell the QML to get the new // filtered list on the user enabling / disabling something. - for (const auto *m : m_modules) { + for (const auto *m : std::as_const(m_modules)) { for (auto *s : m->sections()) { connect (s, &ModuleSection::enabledChanged, this, [=]() { switch (s->type()) { -- 2.54.0 From 7664e167a0ca8f67ec1fea633aa170073a61c57c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 21:08:06 +0200 Subject: [PATCH 0971/1428] Add default-enabled modules section. Lets make the 'sweep' module enabled by default on first start. --- src/ModuleManager.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 4ba7e33..2214056 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,20 @@ ModuleManager::ModuleManager(QObject *parent) auto path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); boost::filesystem::create_directories(boost::filesystem::path(path.toStdString())); m_configFile = path + '/' + "modules.conf"; + + // Default modules to be enabled + auto pool = Streaming::pool(100); + Streaming::MessageBuilder builder(pool); + builder.add(ModuleId, "sendSweepModule"); + builder.add(ModuleSectionId, 0); + builder.add(ModuleSectionEnabled, true); + + auto data = builder.buffer(); + auto configFile = m_configFile.toStdString(); + std::ofstream outFile(configFile); + outFile.write(data.begin(), data.size()); + outFile.flush(); + outFile.close(); } #ifdef TARGET_OS_Android @@ -116,7 +131,7 @@ void ModuleManager::load(const char *translationUnit, const std::functionsections()) { s->setEnabled(false); } @@ -136,7 +151,7 @@ void ModuleManager::load() if (parser.tag() == ModuleId) { info = nullptr; type = -1; - for (const auto *module : m_modules) { + for (const auto *module : std::as_const(m_modules)) { const QString wantedId = QString::fromUtf8(parser.stringData()); if (module->id() == wantedId) { info = module; -- 2.54.0 From 0d1b2023028b0e919932ae72249618e7bd80596e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 22:04:23 +0200 Subject: [PATCH 0972/1428] Fix typo in methodname --- src/MenuModel.cpp | 2 +- src/ModuleManager.cpp | 2 +- src/ModuleManager.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 9c16c57..fc48db5 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -81,7 +81,7 @@ void MenuModel::initData() m_data.append(m_baseItems.at(2)); // security // from plugins - for (auto module : m_moduleManager->maindMenuSections()) { + for (auto module : m_moduleManager->mainMenuSections()) { m_data.append( { module->text(), module->startQMLFile() } ); } diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 2214056..34ff7a0 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -250,7 +250,7 @@ QList ModuleManager::sendMenuSections() const return answer; } -QList ModuleManager::maindMenuSections() const +QList ModuleManager::mainMenuSections() const { QList answer; for (const auto *m : m_modules) { diff --git a/src/ModuleManager.h b/src/ModuleManager.h index dae5e6d..4fd0567 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -52,7 +52,7 @@ public: // lists per type QList sendMenuSections() const; - QList maindMenuSections() const; + QList mainMenuSections() const; Q_INVOKABLE ModuleSection* sectionOnPlugin(const QString &pluginId, const QString §ionId) const; -- 2.54.0 From 9c148598e0c77eded4a94435564b6f9076b44009 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 22:04:45 +0200 Subject: [PATCH 0973/1428] Add button on startup screen. --- guis/mobile/StartupScreen.qml | 39 ++++++++++++++++++++++++++++++++++- src/ModuleInfo.h | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 1b4cd9a..ab82eda 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -180,6 +180,43 @@ Page { } } + Rectangle { + radius: 20 + height:button2Text.height + 20 + width: button2Text.width + 40 + color: "#178b3a" + anchors.horizontalCenter: parent.horizontalCenter + property QtObject infoObject: { + for (var mod of ModuleManager.registeredModules) { + if (mod.id === "sendSweepModule") { + for (var section of mod.sections) { + if (section.isSendMethod) { + if (section.enabled) + return section; + break; + } + } + break; + } + } + return null; + } + + visible: infoObject !== null + + Flowee.Label { + id: button2Text + color: "white" + text: qsTr("Claim a Cash Stamp") + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: thePile.replace(parent.infoObject.qml); + } + } + + Item { width: 1; height: 40 } // spacer } } diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 5423474..f48632a 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -40,6 +40,7 @@ class ModuleInfo : public QObject Q_PROPERTY(QString description READ description CONSTANT FINAL) Q_PROPERTY(QList sections READ sections CONSTANT FINAL) Q_PROPERTY(QString iconSource READ iconSource CONSTANT FINAL) + Q_PROPERTY(QString id READ id CONSTANT FINAL) public: explicit ModuleInfo(QObject *parent = nullptr); -- 2.54.0 From 6ab061966ae04c79df7efc2677ee467e55c2a167 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 22:13:44 +0200 Subject: [PATCH 0974/1428] New version number --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3132883..2d5666a 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="28" android:versionName="2024.10.0"> diff --git a/src/main.cpp b/src/main.cpp index b1352d2..06df810 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.07.0"); + qapp.setApplicationVersion("2024.10.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From d7c656cf94d5ea50e31c9ae100e3a5f9cdf9ef48 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 22:30:02 +0200 Subject: [PATCH 0975/1428] Make the new module part of the translation system --- .gitignore | 1 + CMakeLists.txt | 5 +++++ translations/mobile-i18n.qrc | 1 + 3 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 0192812..4863fc9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ floweepay-mobile.ts module-build-transaction.ts module-example.ts module-peers-view.ts +module-send-sweep.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index d894e52..cda48e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,8 @@ if(NOT ANDROID) translations/module-peers-view_es.ts translations/module-peers-view_pt.ts translations/module-peers-view_ha.ts + + translations/module-send-sweep_nl.ts ) qt6_add_translation(qmFiles ${TS_FILES}) @@ -145,6 +147,9 @@ if(NOT ANDROID) COMMAND lupdate modules/peers-view/peers-page-data.qrc modules/peers-view/PeersViewModuleInfo.cpp -ts translations/module-peers-view.ts + COMMAND lupdate modules/send-sweep/send-sweep-data.qrc + modules/send-sweep/SendSweepModuleInfo.cpp + -ts translations/module-send-sweep.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index b206bd0..b069637 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -28,5 +28,6 @@ module-peers-view_es.qm module-peers-view_pt.qm module-peers-view_ha.qm + module-send-sweep_nl.qm -- 2.54.0 From cfa0716d4a3acd168726d2c8644b6de43df81494 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Oct 2024 22:34:18 +0200 Subject: [PATCH 0976/1428] Update Dutch translations --- translations/floweepay-common_nl.ts | 59 ++-- translations/floweepay-desktop_nl.ts | 450 ++++++++++++++------------- translations/floweepay-mobile_nl.ts | 217 ++++++------- translations/module-send-sweep_nl.ts | 97 ++++++ 4 files changed, 479 insertions(+), 344 deletions(-) create mode 100644 translations/module-send-sweep_nl.ts diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index bac30cc..407b987 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -99,27 +99,27 @@ Transactie afgewezen door het netwerk - + Payment has been sent to: Betaling is verzonden naar: - + Copied address to clipboard Adres gekopieerd naar klembord - + Opening Website Open Website - + Add a personal note Voeg een persoonlijke notitie toe - + Close Sluiten @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -279,32 +279,37 @@ Payment - + Invalid PIN Ongeldige PIN - + + Wallet is locked + Portemonnee is vergrendeld + + + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. - + Request received over insecure channel. Anyone could have altered it! Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! - + Download of payment request failed. Downloaden van betalingsverzoek mislukt. @@ -312,13 +317,13 @@ PaymentProtocolBip70 - + Payment request unreadable Betalingsverzoek onleesbaar - - + + Payment request expired Betalingsverzoek verlopen @@ -331,17 +336,17 @@ Plak - + Failed Mislukt - + Instant Pay limit is %1 Directbetalen limiet is %1 - + Selected wallet: '%1' Geselecteerde portemonnee: '%1' @@ -421,7 +426,7 @@ - + Change #%1 Wisselmunt #%1 diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 17fce03..aa6e0a6 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -84,72 +84,72 @@ Ongeldige PIN - + Include balance in total Saldo opnemen in totaal - + Hide in private mode Verbergen in privémodus - + Address List Adreslijst - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Backup details Back-up details - + Seed-phrase Herstelzin - + Seed format Herstelzin formaat - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -170,42 +170,42 @@ IP-adressen - + Total found Totaal gevonden - + Tried Geprobeerd - + Punished count Aantal bestraft - + Banned count Aantal verbannen - + IP-v4 count IP-v4-teller - + IP-v6 count IP-v6 teller - + Pardon the Banned Pardon de verbannen - + Close Sluiten @@ -221,38 +221,38 @@ - + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - + Peer for wallet: %1 Peer voor portemonnee: %1 - + Disconnect Peer Verbreek verbinding met peer - + Ban Peer Verban Peer - + Close Sluiten @@ -269,11 +269,6 @@ Name Naam - - - Go - Start - Force Single Address @@ -286,6 +281,11 @@ This ensures only one private key will need to be backed up Wanneer ingeschakeld zal deze portemonnee worden beperkt tot één adres. Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt + + + Go + Start + NewAccountCreateHDAccount @@ -317,12 +317,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountImportAccount - - - - Name - Naam - Select import method @@ -367,59 +361,60 @@ Change will come back to the imported key. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + + New Wallet Name + Nieuwe naam Portemonnee + + + + Start Start - + Password Wachtwoord - - Optional - Optioneel + + imported wallet password + Wachtwoord geïmporteerde portemonnee - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Details opzoeken + Vind de details - - Nothing found for seed - Niets gevonden voor herstelzin - - - + Derivation Derivatie + + + Nothing found for seed + Niets gevonden voor herstelzin + NewAccountPane @@ -428,11 +423,39 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. New HD wallet Nieuwe HD portemonnee + + + Seed-phrase based + Context: wallet type + Herstelzin gebaseerd + + + + Easy to backup + Context: wallet type + Back-upt makkelijk + + + + Most compatible + The most compatible wallet type + Meest compatibel + Import Existing Wallet Importeer bestaande portemonnee + + + Imports seed-phrase + Importeert herstelzin + + + + Imports private key + Importeert Privésleutel + New Basic Wallet @@ -456,52 +479,34 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Context: wallet type Ideaal voor kort gebruik - - - Seed-phrase based - Context: wallet type - Herstelzin gebaseerd - - - - Easy to backup - Context: wallet type - Back-upt makkelijk - - - - Most compatible - The most compatible wallet type - Meest compatibel - - - - Imports seed-phrase - Importeert herstelzin - - - - Imports private key - Importeert Privésleutel - PaymentTweakingPanel - + Add Detail Details toevoegen - + Coin Selector Muntselectie - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. De standaard selectie van munten die een transactie betalen kunt u veranderen, u kunt een eigen 'Muntselectie' selecteren waar de munten van deze portemonnee beschikbaar zijn. + + + Comment + Opmerking + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + Hiermee kan een publieke opmerking worden toegevoegd in de transactie, die door iedereen kan worden gelezen. + ReceiveTransactionPane @@ -521,52 +526,52 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Bezig met importeren... - + Checking Controleren - + Transaction high risk Transactie met hoog risico - + Payment Seen Betaling gezien - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Instant payment failed. Wait for confirmation. (double spent proof received) Directe betaling is mislukt. Wacht op bevestiging. (dubbel uitgegeven bewijs ontvangen) - + Description Omschrijving - + Amount Bedrag - + Clear Wissen - + Done Klaar @@ -574,147 +579,147 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. SendTransactionPane - + Confirm delete Verwijderen bevestigen - + Do you really want to delete this detail? Wilt u dit detail echt wissen? - + Add Destination Voeg bestemming toe - + Prepare Bereid voor - + Enter your PIN Voer uw PIN in - - + + Warning Waarschuwing - + Payment request warnings: Betalingsverzoek meldingen: - + Transaction Details Transactiedetails - + Not prepared yet Nog niet voorbereid - + Copy transaction-ID Kopieer transactie-ID - + Fee Transactiekosten - + Transaction size Transactie grootte - + %1 bytes %1 bytes - + Fee per byte Transactiekosten per byte - + %1 sat/byte fee %1 sat/byte - + Send Verstuur - + Destination Bestemming - + Max available The maximum balance available Max. beschikbaar - + %1 to %2 summary text to pay X-euro to address M %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - - + + Copy Address Kopieer adres - + Amount Bedrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Cancel Afbreken - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -723,56 +728,81 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt + + + Public-comment + Publiek commentaar + + + + Add a comment you want to include in the transaction, visible for everyone. + Voeg een opmerking toe die u wilt opnemen in de transactie, zichtbaar voor iedereen. + + + + Custom message, to be included in the transaction. + Speciaal bericht dat wordt opgenomen in de transactie. + + + + Text + Tekst + + + + Size + Grootte + SettingsPane @@ -888,27 +918,27 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Transactiedetails - + First Seen Eerst gezien - + Rejected - Geweigerd + Afgewezen - + Unconfirmed Onbevestigd - + Mined at Gedolven in - + %1 blocks ago %1 blok terug @@ -916,67 +946,67 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Comment Opmerking - + Fees paid Betaalde kosten - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Grootte - + %1 bytes %1 bytes - + Is Coinbase Is Coinbase - + Copy transaction-ID Kopieer transactie-ID - + Fused from my addresses Mijn gefuseerde adressen - + Sent from my addresses Verzonden vanaf mijn adressen - + Sent from addresses Verzonden vanaf adressen - + Fused into my addresses Gefuseerd naar mijn adressen - + Received at addresses Ontvangen op adressen - + Received at my addresses Ontvangen op mijn adressen @@ -1036,119 +1066,119 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Beveilig uw portomonee met een wachtwoord - + Pin to Pay PIN bij betalen - + Protect your funds pin to pay Bescherm uw geld - + Fully open, except for sending funds pin to pay Volledig open, behalve voor het verzenden van geld - + Keeps in sync pin to pay Synchroniseert - + Pin to Open PIN bij openen - + Protect your entire wallet pin to open Bescherm uw gehele portemonnee - + Balance and history protected pin to open Saldo en geschiedenis beveiligd - + Requires Pin to view, sync or pay pin to open Vereist Pin om te bekijken, synchroniseren of betalen - + Make "%1" wallet require a pin to pay Verplicht een Pin voor betaling op portemonnee "%1" - + Make "%1" wallet require a pin to open Verplicht een Pin om portemonnee "%1" te openen - - + + Wallet already has pin to open applied Portomonee heeft al Pin to Open - + Wallet already has pin to pay applied Portomonee heeft al Pin to Pay - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Uw portemonnee zal gedeeltelijk versleuteld worden en betalen zal onmogelijk worden zonder een wachtwoord. Als u geen backup heeft van deze portemonnee, maak er dan eerst een. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Uw volledige portemonnee wordt versleuteld, bij openen heeft u een wachtwoord nodig. Als u geen backup heeft van deze portemonnee, maak er eerst een. - + Password Wachtwoord - + Wallet Portemonnee - + Encrypt Versleutelen - + Invalid password to open this wallet Ongeldig wachtwoord om deze portemonnee te openen - + Close Sluiten - + Repeat password Herhaal wachtwoord - + Please confirm the password by entering it again Bevestig het wachtwoord door het opnieuw in te voeren - + Typed passwords do not match Wachtwoorden komen niet overeen @@ -1156,17 +1186,17 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. WalletEncryptionStatus - + Pin to Open PIN bij openen - + Pin to Pay PIN bij betalen - + (Opened) Wallet is decrypted (Geopend) @@ -1175,95 +1205,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1272,12 +1302,12 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... - + QR-Scan QR-Scannen diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 749408f..c10d022 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -62,36 +62,6 @@ Receive Ontvang - - - Miner Reward - Mijnwerker Beloning - - - - Fused - Gefuseerd - - - - Received - Ontvangen - - - - Moved - Zelf-betaling - - - - Sent - Verzonden - - - - Rejected - Geweigerd - AccountPageListItem @@ -240,7 +210,7 @@ AccountSyncState - + Status: Offline Status: offline @@ -309,7 +279,7 @@ Ontdek - + ON Enabled. SHORT TEXT! AAN @@ -361,112 +331,107 @@ Portemonnee importeren - - - Name - Naam - - - - Force Single Address - Forceer één adres - - - + Select import method Kies import-methode - + Camera Kamera - + OR OF - + Secret as text The seed-phrase or private key Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Next Volgende - + Address to import Te importeren adres - + + Force Single Address + Forceer één adres + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - Nothing found for seed - Niets gevonden voor herstelzin - - - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + + New Wallet Name + Nieuwe naam Portemonnee + + + + Start Start - + Password Wachtwoord - - Optional - Optioneel + + imported wallet password + Wachtwoord geïmporteerde portemonnee - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Details opzoeken + Vind de details - - Derivation - Derivatie + + Derivation Path + Derivatie pad + + + + Nothing found for seed + Niets gevonden voor herstelzin @@ -593,6 +558,11 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. New Bitcoin Cash Wallet Nieuwe Bitcoin Cash Portemonnee + + + New HD Wallet + Nieuwe HD portemonnee + Seed-phrase based @@ -611,6 +581,26 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. The most compatible wallet type Meest compatibel + + + Import Existing Wallet + Importeer bestaande portemonnee + + + + Imports seed-phrase + Importeert herstelzin + + + + Imports private key + Importeert Privésleutel + + + + New Basic Wallet + Nieuwe Basic Portemonnee + Private keys based @@ -629,31 +619,6 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Context: wallet type Ideaal voor kort gebruik - - - Import Existing Wallet - Importeer bestaande portemonnee - - - - New HD Wallet - Nieuwe HD portemonnee - - - - Imports seed-phrase - Importeert herstelzin - - - - Imports private key - Importeert Privésleutel - - - - New Basic Wallet - Nieuwe Basic Portemonnee - New Wallet @@ -760,7 +725,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -768,25 +733,25 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 - + 7d 7 days 7d - + 1m 1 month 1m - + 3m 3 months 3m @@ -968,6 +933,11 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Add a different wallet Een andere portemonnee toevoegen + + + Claim a Cash Stamp + Claim een Cash Stamp + TransactionDetails @@ -1137,6 +1107,39 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Transactiedetails + + TransactionListItem + + + Miner Reward + Miner Beloning + + + + Fused + Gefuseerd + + + + Received + Ontvangen + + + + Moved + Verschoven + + + + Sent + Verzonden + + + + Rejected + Afgewezen + + UnlockWidget diff --git a/translations/module-send-sweep_nl.ts b/translations/module-send-sweep_nl.ts new file mode 100644 index 0000000..98a44ea --- /dev/null +++ b/translations/module-send-sweep_nl.ts @@ -0,0 +1,97 @@ + + + + + SendPage + + + Sweep coins + Sweep coins + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + + + Sweeping from address: + Sweeping from address: + + + + Failed to understand QR + Failed to understand QR + + + + Indexer results invalid. Please try again. + Indexer results invalid. Please try again. + + + + Found %1 coins on address. + Found %1 coins on address. + + + + Ignoring %1 tokens. + Ignoring %1 tokens. + + + + Transfer to: + Transfer to: + + + + Sending Payment + Betaling wordt verzonden + + + + Payment Sent + Betaling Verzonden + + + + Failed + Mislukt + + + + Transaction rejected by network + Transactie afgewezen door het netwerk + + + + The payment has been sent to: + Followed by the address + The payment has been sent to: + + + + Close + Sluiten + + + + SendSweepModuleInfo + + + Sweep & Send + Sweep & Send + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Allows sweeping a paper-wallet, moving the contents to your own wallet + + + + Sweep Paper Wallet + Sweep Paper Wallet + + + -- 2.54.0 From 435bf89e2b87a35abb751030d472eb238a64e0b9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 12:09:33 +0200 Subject: [PATCH 0977/1428] Make compile with config option; network-log Adding -DNetworkLogClient=ON to a desktop build (non-android, honestly) now works. --- src/main_utils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 989c2c0..be02e05 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -27,6 +27,10 @@ #include #include +#ifdef NETWORK_LOGGER +# include "NetworkLogClient.h" +#endif + struct CommandLineParserData { explicit CommandLineParserData(QGuiApplication &qapp) @@ -117,8 +121,6 @@ void initLogger(CommandLineParserData *cld) std::unique_ptr handleStaticChain(CommandLineParserData *cld) { std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. - - blockheaders.reset(new QFile(QString("/usr/share/floweepay/") + (cld->chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); #ifndef NDEBUG -- 2.54.0 From 333f1fb40101ae01f82f3357b27c7170bd2730e3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 16:43:05 +0200 Subject: [PATCH 0978/1428] Re-engineer the TxInfoObject This separates it into two objects in order to make thead-safety easy using Qt's signal/slot mechanism. Additionally this removes magic timeout numbers and simply starts a timer to check for changes and emit a signal if the status actually needs updating. Testing also shows that QML calls the getter a LOT, so I also moved the broadcastStatus to be buffered and just return that on the call. This incidentally also simplifies the code, double win. --- modules/send-sweep/QMLSweepHandler.cpp | 13 +- src/Payment.cpp | 9 +- src/TxInfoObject.cpp | 223 ++++++++++++++----------- src/TxInfoObject.h | 47 ++---- src/TxInfoObject_p.h | 80 +++++++++ src/Wallet.cpp | 2 +- 6 files changed, 229 insertions(+), 145 deletions(-) create mode 100644 src/TxInfoObject_p.h diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 820420c..643aa25 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -152,20 +152,9 @@ void QMLSweepHandler::markUserApproved() m_account->wallet()->newTransaction(tx); m_account->wallet()->setTransactionComment(tx, tr("Swept funds")); - // call to wallet to mark outputs locked and save tx. m_infoObject = std::make_shared(m_account->wallet(), tx); - connect(m_infoObject.get(), &TxInfoObject::sentOne, this, [=]() { - emit broadcastStatusChanged(); - // we wait a second and force a recalc in the following singleShot. - // NOTE: if you change this number, keep it slightly above the WaitingTimeoutMs - // time in the TxInfoObject class. - QTimer::singleShot(1000, this, SIGNAL(broadcastStatusChanged())); - }, Qt::QueuedConnection); - - // notice that rejections are automatically forwarded to the wallet from the TxInfoObject - connect(m_infoObject.get(), SIGNAL(rejectionSeen()), this, SIGNAL(broadcastStatusChanged()), Qt::QueuedConnection); + connect(m_infoObject.get(), SIGNAL(broadcastStatusChanged()), this, SIGNAL(broadcastStatusChanged())); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); - m_txBroadcastStarted = true; emit broadcastStatusChanged(); diff --git a/src/Payment.cpp b/src/Payment.cpp index bbd9f7e..deb397e 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -407,15 +407,8 @@ void Payment::broadcast() m_wallet->setTransactionComment(m_tx, m_userComment); m_infoObject = std::make_shared(m_wallet, m_tx); - connect(m_infoObject.get(), &TxInfoObject::sentOne, this, [=]() { - emit broadcastStatusChanged(); - // we wait a second and force a recalc in the following singleShot. - // NOTE: if you change this number, keep it slightly above the WaitingTimeoutMs - // time in the TxInfoObject class. - QTimer::singleShot(1000, this, SIGNAL(broadcastStatusChanged())); - }, Qt::QueuedConnection); + connect(m_infoObject.get(), SIGNAL(broadcastStatusChanged()), this, SIGNAL(broadcastStatusChanged())); // notice that rejections are automatically forwarded to the wallet from the TxInfoObject - connect(m_infoObject.get(), SIGNAL(rejectionSeen()), this, SIGNAL(broadcastStatusChanged()), Qt::QueuedConnection); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); m_txBroadcastStarted = true; emit broadcastStatusChanged(); diff --git a/src/TxInfoObject.cpp b/src/TxInfoObject.cpp index a3fa1be..177f867 100644 --- a/src/TxInfoObject.cpp +++ b/src/TxInfoObject.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "TxInfoObject.h" +#include "TxInfoObject_p.h" #include "Wallet.h" #include "Payment.h" @@ -28,75 +29,90 @@ constexpr int SECTION = 10004; constexpr int WaitingTimeoutMs = 950; -TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx) +TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex) : BroadcastTxData(tx), - m_wallet(wallet) + d(new TxInfoPrivate(this)), + m_status(FloweePay::NotStarted) { assert(wallet); - auto iter = m_wallet->m_txidCache.find(hash()); - assert(iter != m_wallet->m_txidCache.end()); - m_txIndex = iter->second; + d->wallet = wallet; + if (walletTxIndex < 1) { // lets look it up now! + auto iter = d->wallet->m_txidCache.find(hash()); + if (iter == d->wallet->m_txidCache.end()) + throw std::runtime_error("Transaction not in wallet"); + walletTxIndex = iter->second; + } + d->txIndex = walletTxIndex; - connect(this, SIGNAL(finished(int,bool)), - m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); + connect(this, SIGNAL(finished(int,bool)), d->wallet, SLOT(broadcastTxFinished(int,bool))); } -TxInfoObject::TxInfoObject(Wallet *wallet, int txIndex, const Tx &tx) - : BroadcastTxData(tx), - m_wallet(wallet), - m_txIndex(txIndex) +void TxInfoObject::sentVia(const std::shared_ptr &peer) { - connect(this, SIGNAL(finished(int,bool)), - m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); + d->sentVia(peer); } void TxInfoObject::txRejected(int connectionId, RejectReason reason, const std::string &message) { - // reason is hinted using BroadcastTxData::RejectReason - logCritical(SECTION) << "Transaction rejected" << reason << message; - - /* - * This is called directly from a peer's network processing code. Each peer may - * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, - * and thus this method needs to be thread-safe. - */ - QMutexLocker l(&m_lock); - for (auto iter = m_broadcasts.begin(); iter != m_broadcasts.end(); ++iter) { - if (iter->connectionId == connectionId) { - assert(iter->failed == false); // the p2p lib should avoid this - iter->failed = true; - iter->rejected = reason; - iter->rejectMsg = message; - break; - } - } - emit rejectionSeen(); + d->txRejected(connectionId, reason, message); } uint16_t TxInfoObject::privSegment() const { - assert(m_wallet); - assert(m_wallet->segment()); - return m_wallet->segment()->segmentId(); + assert(d->wallet); + assert(d->wallet->segment()); + return d->wallet->segment()->segmentId(); } int TxInfoObject::txIndex() const { - return m_txIndex; + return d->txIndex; } -TxInfoObject::Status TxInfoObject::calcStatus() const +void TxInfoObject::setBroadcastStatus(const FloweePay::BroadcastStatus &status) { + assert(thread() == QThread::currentThread()); + if (m_status == status) + return; + if (m_status < FloweePay::TxSent1 && status >= FloweePay::TxSent1) + emit sentOne(); + if (m_status < FloweePay::TxRejected && status >= FloweePay::TxRejected) + emit rejectionSeen(); + m_status = status; + emit broadcastStatusChanged(); +} + +FloweePay::BroadcastStatus TxInfoObject::broadcastStatus() const +{ + return m_status; +} + + +/// ---------------------------------------------- + +TxInfoPrivate::TxInfoPrivate(TxInfoObject *parent) + : QObject(parent), + q(parent) +{ + checkStatusTimer.setInterval(220); + connect (&checkStatusTimer, SIGNAL(timeout()), this, SLOT(checkState())); + // the next one is queued because of the jumping of threads. + connect (this, SIGNAL(broadcastsChanged()), + this, SLOT(checkState()), Qt::QueuedConnection); +} + +TxInfoPrivate::Status TxInfoPrivate::calcStatus() const +{ + QMutexLocker l(&lock); Status rc; - auto copy = m_broadcasts; - rc.sent = copy.size(); + rc.sent = broadcasts.size(); rc.inProgress = 0; const auto time = std::chrono::system_clock::now(); const auto milliTime = std::chrono::duration_cast(time.time_since_epoch()); const uint64_t cutoff = milliTime.count() - WaitingTimeoutMs; - for (const auto &broadcast : copy) { + for (const auto &broadcast : broadcasts) { if (broadcast.failed) rc.failed++; else if (broadcast.sentTime > cutoff) @@ -105,18 +121,53 @@ TxInfoObject::Status TxInfoObject::calcStatus() const return rc; } -FloweePay::BroadcastStatus TxInfoObject::broadcastStatus() const +void TxInfoPrivate::sentVia(const std::shared_ptr &peer) { - assert(thread() == QThread::currentThread()); - Status status = calcStatus(); - logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; - if (status.sent == 0) - return FloweePay::TxOffered; - if (status.failed) - return FloweePay::TxRejected; - if (status.sent - status.inProgress >= 2) - return FloweePay::TxBroadcastSuccess; - return FloweePay::TxWaiting; + assert(peer.get()); + /* + * This is called directly from a peer's network processing code. Each peer may + * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, + * so we lock. + */ + QMutexLocker l(&lock); + const auto id = peer->connectionId(); + for (auto iter = broadcasts.begin(); iter != broadcasts.end(); ++iter) { + if (iter->connectionId == id) // avoid duplicates + return; + } + + Broadcast broadcast; + broadcast.connectionId = id; + broadcast.ep = peer->peerAddress().peerAddress(); + const auto time = std::chrono::system_clock::now(); + const auto millis = std::chrono::duration_cast(time.time_since_epoch()); + broadcast.sentTime = millis.count(); + broadcasts.append(broadcast); + emit broadcastsChanged(); +} + +void TxInfoPrivate::txRejected(int connectionId, BroadcastTxData::RejectReason reason, const std::string &message) +{ + // reason is hinted at using BroadcastTxData::RejectReason + logCritical(SECTION) << "Transaction rejected" << reason << message; + + /* + * This is called directly from a peer's network processing code. Each peer may + * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, + * so we lock. + */ + QMutexLocker l(&lock); + for (auto iter = broadcasts.begin(); iter != broadcasts.end(); ++iter) { + if (iter->connectionId == connectionId) { + if (iter->failed == false) { + iter->failed = true; + iter->rejected = reason; + iter->rejectMsg = message; + emit broadcastsChanged(); + } + break; + } + } } /* @@ -130,51 +181,35 @@ FloweePay::BroadcastStatus TxInfoObject::broadcastStatus() const * And that is where this method is most useful, to detect that the network rejects * our transaction. */ -void TxInfoObject::checkState() +void TxInfoPrivate::checkState() { assert(thread() == QThread::currentThread()); - Status status = calcStatus(); + const Status status = calcStatus(); // this uses the mutex. Just so you know.. logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; - // a typical wallet has 3 peers. - // we react when at least 2 downloaded our transaction and didn't report a failure - if (status.sent - status.inProgress >= 2) { - emit finished(m_txIndex, - /* bool success = */ status.sent - status.inProgress - status.failed >= 1); + + FloweePay::BroadcastStatus bs = FloweePay::TxWaiting; + if (status.sent == 0) + bs = FloweePay::TxOffered; + else if (status.sent > 0) + bs = FloweePay::TxSent1; + if (status.failed) + bs = FloweePay::TxRejected; + else if (status.sent - status.inProgress >= 2) + bs = FloweePay::TxBroadcastSuccess; + q->setBroadcastStatus(bs); + + if (!finished && status.failed == 0 && status.sent >= 1) { + // a typical wallet has 3 peers. + // we react when at least 2 downloaded our transaction and didn't report a failure + if (status.sent - status.inProgress >= 2) { + emit q->finished(txIndex, + /* bool success = */ status.sent - status.inProgress - status.failed >= 1); + + finished = true; + checkStatusTimer.stop(); + } + + if (!finished && !checkStatusTimer.isActive()) + checkStatusTimer.start(); } } - -void TxInfoObject::sentVia(const std::shared_ptr &peer) -{ - assert(peer.get()); - /* - * This is called directly from a peer's network processing code. Each peer may - * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, - * and thus this method needs to be thread-safe. - */ - QMutexLocker l(&m_lock); - const auto id = peer->connectionId(); - for (auto iter = m_broadcasts.begin(); iter != m_broadcasts.end(); ++iter) { - if (iter->connectionId == id) - return; - } - - Broadcast broadcast; - broadcast.connectionId = id; - broadcast.ep = peer->peerAddress().peerAddress(); - const auto time = std::chrono::system_clock::now(); - const auto millis = std::chrono::duration_cast(time.time_since_epoch()); - broadcast.sentTime = millis.count(); - m_broadcasts.append(broadcast); - - if (m_broadcasts.size() >= 2) { - // Two stage singleshots. First (Qt requires that to be zero ms) to move - // to a thread that Qt owns. - // Second (in the lambda) to actually wait several seconds. - QTimer::singleShot(0, this, [=]() { - assert(thread() == QThread::currentThread()); - QTimer::singleShot(4 * 1000, this, SLOT(checkState())); - }); - } - - emit sentOne(); -} diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h index cb69a3c..6c29b83 100644 --- a/src/TxInfoObject.h +++ b/src/TxInfoObject.h @@ -20,13 +20,10 @@ #include "FloweePay.h" -#include -#include -#include #include class Wallet; -class Payment; +class TxInfoPrivate; /** * This is used to broadcast transactions. @@ -44,6 +41,7 @@ class Payment; * The wallet will go through a similar process whenever a new block is found and * a transaction it holds is not mined (nor made invalid), broadcasting the unconfirmed * transaction to all peers. + * * The p2p layer will also send it to new peers the moment they connect (and get * associated with this wallet). * @@ -53,47 +51,36 @@ class TxInfoObject : public QObject, public BroadcastTxData { Q_OBJECT public: - TxInfoObject(Wallet *wallet, const Tx &tx); - TxInfoObject(Wallet *parent, int txIndex, const Tx &tx); + /** + * Constructor. + * @param wallet is the owner of the new transaction. + * @param tx the actual to-be-broadcast transaction. + * + * The wallet has to have already accepted the transaction, this throws otherwise. + */ + TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex = -1); + // broadcastTxData interface. void sentVia(const std::shared_ptr &peer) override; void txRejected(int connectionId, RejectReason reason, const std::string &message) override; uint16_t privSegment() const override; + /// the wallet internal index of the transaction we're broadcasting. int txIndex() const; - struct Status { - int sent = 0; // number of peers we sent this transaction to. - int inProgress = 0; // number of peers in progress. - int failed = 0; // number of peers reported failure. - }; - Status calcStatus() const; - + // set internally. + void setBroadcastStatus(const FloweePay::BroadcastStatus &status); FloweePay::BroadcastStatus broadcastStatus() const; -private slots: - // called some seconds after the request from a peer for data - void checkState(); - signals: void finished(int txIndex, bool success); void sentOne(); void rejectionSeen(); + void broadcastStatusChanged(); private: - mutable QMutex m_lock; - const Wallet *m_wallet; - int m_txIndex; - - struct Broadcast { - int connectionId = -1; - EndPoint ep; - bool failed = false; - RejectReason rejected = NoReason; - std::string rejectMsg; - uint64_t sentTime = 0; // ms since epoch. - }; - QList m_broadcasts; + TxInfoPrivate *d; + FloweePay::BroadcastStatus m_status; }; diff --git a/src/TxInfoObject_p.h b/src/TxInfoObject_p.h new file mode 100644 index 0000000..339ddc0 --- /dev/null +++ b/src/TxInfoObject_p.h @@ -0,0 +1,80 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TXINFOOBJECT_P_H +#define TXINFOOBJECT_P_H + +#include + +#include +#include +#include + +class Wallet; +class TxInfoObject; + +class TxInfoPrivate : public QObject +{ + Q_OBJECT +public: + TxInfoPrivate(TxInfoObject *parent); + + struct Status { + int sent = 0; // number of peers we sent this transaction to. + int inProgress = 0; // number of peers in progress. + int failed = 0; // number of peers reported failure. + }; + /// Fill a status struct where we take the time things in flight into account. + Status calcStatus() const; + + void sentVia(const std::shared_ptr &peer); + void txRejected(int connectionId, BroadcastTxData::RejectReason reason, const std::string &message); + + mutable QMutex lock; + const Wallet *wallet; + int txIndex; + + struct Broadcast { + int connectionId = -1; + EndPoint ep; + bool failed = false; + BroadcastTxData::RejectReason rejected = BroadcastTxData::NoReason; + std::string rejectMsg; + uint64_t sentTime = 0; // ms since epoch. + }; + QList broadcasts; + TxInfoObject *q; + + // to avoid emitting finished() more then once. + bool finished = false; + QTimer checkStatusTimer; + + // this class is multi-threading by need. The callbacks forwarded from the + // main class can happen in any thread, so the sentVia/txRejected are not + // on our associated thread. + // As these methods are also our main events and thus triggers, we notify the + // via signals about changes which are connected to our own slots below that + // then will be executed in our own thread. + // multi-threading for dummies ;-) +signals: + void broadcastsChanged(); + +private slots: + void checkState(); +}; + +#endif diff --git a/src/Wallet.cpp b/src/Wallet.cpp index ab47c64..14d6a6a 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1494,7 +1494,7 @@ void Wallet::broadcastUnconfirmed() if (iter->second.minedBlockHeight == Wallet::Unconfirmed) { auto tx = loadTransaction(iter->second.txid, Streaming::pool(0)); if (tx.data().size() > 64) { - auto bc = std::make_shared(this, iter->first, tx); + auto bc = std::make_shared(this, tx, iter->first); bc->moveToThread(thread()); logDebug(LOG_WALLET) << " broadcasting transaction" << tx.createHash() << tx.size(); m_broadcastingTransactions.append(bc); -- 2.54.0 From e9ee0a22b8937599dca5a69bc6237d77ecdb9fb9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 16:51:37 +0200 Subject: [PATCH 0979/1428] Show num coins found as soon as we know. --- modules/send-sweep/SendPage.qml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 529ad35..2aeee03 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -67,6 +67,15 @@ Mobile.Page { horizontalAlignment: Qt.AlignHCenter fontSizeMode: Text.HorizontalFit } + Flowee.Label { + id: coinsLabel + visible: sweeper.numOutputsFound > 0 || sweeper.prepared + text: qsTr("Found %1 coins on address.").arg(sweeper.numOutputsFound) + } + Flowee.Label { + visible: sweeper.numTokensFound > 0 + text: qsTr("Ignoring %1 tokens.").arg(sweeper.numTokensFound) + } Item { id: busyIndicator @@ -128,15 +137,6 @@ Mobile.Page { } } - Flowee.Label { - id: coinsLabel - visible: sweeper.prepared - text: qsTr("Found %1 coins on address.").arg(sweeper.numOutputsFound) - } - Flowee.Label { - visible: sweeper.prepared && sweeper.numTokensFound - text: qsTr("Ignoring %1 tokens.").arg(sweeper.numTokensFound) - } Flowee.BitcoinAmountLabel { font.pixelSize: coinsLabel.font.pixelSize * 1.2 visible: sweeper.prepared @@ -162,7 +162,7 @@ Mobile.Page { anchors.bottom: parent.bottom anchors.bottomMargin: 10 width: parent.width - enabled: sweeper.prepared + enabled: sweeper.prepared && sweeper.numOutputsFound > 0 visible: !sweeper.account.needsPinToOpen onActivated: { root.hideHeader = true; -- 2.54.0 From 4347e94ca30b68b3cd3d2b9951cb249a4e7d690a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 17:35:59 +0200 Subject: [PATCH 0980/1428] fixlets. --- src/TxInfoObject.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/TxInfoObject.cpp b/src/TxInfoObject.cpp index 177f867..c3162e0 100644 --- a/src/TxInfoObject.cpp +++ b/src/TxInfoObject.cpp @@ -44,7 +44,8 @@ TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex) } d->txIndex = walletTxIndex; - connect(this, SIGNAL(finished(int,bool)), d->wallet, SLOT(broadcastTxFinished(int,bool))); + // this is 'queued' because the wallet may be owning us and may delete us in that slot. + connect(this, SIGNAL(finished(int,bool)), d->wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); } void TxInfoObject::sentVia(const std::shared_ptr &peer) @@ -198,7 +199,7 @@ void TxInfoPrivate::checkState() bs = FloweePay::TxBroadcastSuccess; q->setBroadcastStatus(bs); - if (!finished && status.failed == 0 && status.sent >= 1) { + if (!finished && status.sent >= 1) { // a typical wallet has 3 peers. // we react when at least 2 downloaded our transaction and didn't report a failure if (status.sent - status.inProgress >= 2) { -- 2.54.0 From e9d0af2f0d12e00073bbb61b72daef8d5a1afe28 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 22:06:17 +0200 Subject: [PATCH 0981/1428] Fix paying from a wallet containing rejected tx This adds more checks on loading and saving and avoids trying to spend a rejected transaction output due to confused internal state. --- src/Wallet.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 14d6a6a..6eb8215 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1877,9 +1877,11 @@ void Wallet::loadWallet() m_txidCache.insert(std::make_pair(wtx.txid, index)); m_nextWalletTransactionId = std::max(m_nextWalletTransactionId, index); // insert outputs of new tx. - for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { - OutputRef ref(index, i->first); - m_unspentOutputs.insert(std::make_pair(ref.encoded(), i->second.value)); + if (wtx.minedBlockHeight != Rejected) { + for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { + OutputRef ref(index, i->first); + m_unspentOutputs.insert(std::make_pair(ref.encoded(), i->second.value)); + } } newTx.insert(index); @@ -1952,17 +1954,21 @@ void Wallet::loadWallet() // an ouput can get locked, stopping the user from spending it. else if (parser.tag() == WalletPriv::OutputLockState) { - WalletPriv::OutputLockStateEnum inputLock = static_cast(parser.intData()); - if (inputLock == WalletPriv::UserLocked) { // we handle the 'auto-locked' case in the OutputLockAutoSpender - // ref is made up of WalletPriv::Index and OutputIndex - OutputRef ref(index, outputIndex); - m_lockedOutputs.insert(std::make_pair(ref.encoded(), 0)); + if (wtx.minedBlockHeight != Rejected) { + WalletPriv::OutputLockStateEnum inputLock = static_cast(parser.intData()); + if (inputLock == WalletPriv::UserLocked) { // we handle the 'auto-locked' case in the OutputLockAutoSpender + // ref is made up of WalletPriv::Index and OutputIndex + OutputRef ref(index, outputIndex); + m_lockedOutputs.insert(std::make_pair(ref.encoded(), 0)); + } } } else if (parser.tag() == WalletPriv::OutputLockAutoSpender) { - // ref is made up of WalletPriv::Index and OutputIndex - OutputRef ref(index, outputIndex); - m_lockedOutputs.insert(std::make_pair(ref.encoded(), parser.intData())); + if (wtx.minedBlockHeight != Rejected) { + // ref is made up of WalletPriv::Index and OutputIndex + OutputRef ref(index, outputIndex); + m_lockedOutputs.insert(std::make_pair(ref.encoded(), parser.intData())); + } } else if (parser.tag() == WalletPriv::KeyStoreIndex) { @@ -2157,7 +2163,7 @@ void Wallet::saveWallet() // outputs that have been locked for some reason. // One reason is we spent it already but that tx has not yet been confirmed. Otherwise it could be user-decided. auto lock = m_lockedOutputs.find(OutputRef(item.first, i->first).encoded()); - if (lock != m_lockedOutputs.end()) { + if (item.second.minedBlockHeight != Rejected && lock != m_lockedOutputs.end()) { builder.add(WalletPriv::OutputLockState, lock->second == 0 ? WalletPriv::UserLocked : WalletPriv::AutoLocked); if (lock->second != 0) builder.add(WalletPriv::OutputLockAutoSpender, lock->second); -- 2.54.0 From b470f739a692391b2f684a8bba375710fad27bb7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Oct 2024 23:31:59 +0200 Subject: [PATCH 0982/1428] Handle wallet exceptions better This code now catches the exceptions and logs it. For instance when a wallet is aware of a transaction but can't find the file on disk, we now don't just weirdly misbehave but forward an error. --- src/Payment.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index deb397e..d9ca1b6 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -255,7 +255,7 @@ void Payment::prepare() auto tx = builder.createTransaction(); - // find and add outputs that can be used to pay for our required output + // find and add unspent-outputs that can be used to pay for our required output int64_t change = -1; Wallet::OutputSet funding; if (inputs) { @@ -283,17 +283,22 @@ void Payment::prepare() } for (auto ref : funding.outputs) { - builder.appendInput(m_wallet->txid(ref), ref.outputIndex()); - auto output = m_wallet->txOutput(ref); - auto priv = m_wallet->unlockKey(ref); - if (priv.sigType == Wallet::NotUsedYet) { - priv.sigType = m_preferSchnorr ? Wallet::SignedAsSchnorr : Wallet::SignedAsEcdsa; - m_wallet->updateSignatureType(priv); // remember the signing type for next time. + try { + builder.appendInput(m_wallet->txid(ref), ref.outputIndex()); + auto output = m_wallet->txOutput(ref); + auto priv = m_wallet->unlockKey(ref); + if (priv.sigType == Wallet::NotUsedYet) { + priv.sigType = m_preferSchnorr ? Wallet::SignedAsSchnorr : Wallet::SignedAsEcdsa; + m_wallet->updateSignatureType(priv); // remember the signing type for next time. + } + TransactionBuilder::SignatureType typeToUse = + (priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr; + assert(priv.key.isValid()); + builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); + } catch (const std::exception &e) { + logCritical() << "Payment prepare failed adding input with:" << e; + return forwardError("Internal error"); } - TransactionBuilder::SignatureType typeToUse = - (priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr; - assert(priv.key.isValid()); - builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); } m_assignedFee = 0; -- 2.54.0 From c805dee6e34d1518cd8389f06410b488d74b827e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Oct 2024 13:24:31 +0200 Subject: [PATCH 0983/1428] UX: show camera screen instantly When you open the payment screen now the black camera screen is instantly shown and the camera as soon as possible. This avoids confusing UI states. --- guis/Flowee/QRScanner.qml | 4 ++-- src/CameraController.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index a551887..ccd85df 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -39,8 +39,8 @@ FocusScope { // eat all clicks } - // We put the 'Camera' in a loader to avoid Android permissions to be popped up until the - // feature is actually requisted by the user. + // We put the 'Camera' in a loader to avoid Android permissions to be popped + // up until the feature is actually requested by the user. Loader { sourceComponent: videoFeedPanel active: CameraController.loadCamera diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 7e8b126..1cdf921 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -601,7 +601,11 @@ void CameraController::startRequest(QRScanner *request) # endif #endif } - d->checkState(); + // give the overlay screen time to appear before + // activating the camera. + // This avoids waiting with the overlay screen until the camera + // is ready to stream which (depending on drivers) may take half a second. + QTimer::singleShot(1, this, SLOT(checkState())); } void CameraController::abortRequest(QRScanner *request) -- 2.54.0 From e61822015313bbb2dca97507ee31495af9b9a16d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Oct 2024 19:27:59 +0200 Subject: [PATCH 0984/1428] Allow a label to have a 'disabled' color --- guis/Flowee/Label.qml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml index 9e8e968..a175026 100644 --- a/guis/Flowee/Label.qml +++ b/guis/Flowee/Label.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,12 @@ import QtQuick.Controls as QQC2 QQC2.Label { - // With Qt6.4 on Android, this extra line is needed to + // With Qt6.4 on Android, this method is needed to // get the label to follow the app-color-style - color: palette.windowText + color: { + var c = palette.windowText; + if (enabled === false) + c = Qt.darker(c, Pay.useDarkSkin ? 1.5 : -1.5); + return c; + } } -- 2.54.0 From ebf0f38b1f65fe2a7ff356728bf223eafce6e6d0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 11 Oct 2024 19:35:34 +0200 Subject: [PATCH 0985/1428] Add wallet delete / reindex features This adds the backend code and the buttons that allow deletion of a wallet. Formerly you could archive a wallet and thus remove it from view, at popular request an archived wallet now gets a button in the wallets overview to delete the actual wallet completely. This indeed also removes the files from disk, making it unrecoverable. Additionally, an actually more complex feature, this allows an existing wallet to have its history removed and start a re-scan to re-download all transactions and build the balance. This is useful for debugging or for recovering from buggy code we probably had in older versions. (nobody is perfect all the time) --- guis/mobile/AccountPageListItem.qml | 55 ++++++++++++++++++++++++++++- src/AccountInfo.cpp | 53 +++++++++++++++++++++++++++ src/AccountInfo.h | 8 ++++- src/FloweePay.cpp | 30 +++++++++++++++- src/FloweePay.h | 5 ++- src/PortfolioDataProvider.cpp | 21 ++++++++--- src/Wallet.cpp | 5 +++ src/Wallet.h | 2 ++ 8 files changed, 170 insertions(+), 9 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 85fb73d..2aa1dad 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -404,8 +404,9 @@ QQC2.Control { color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" width: archiveButtonText.width + 30 visible: !singleAccountSetup && root.account.isUserOwned + radius: 3 - Text { + Flowee.Label { id: archiveButtonText text: root.account.isArchived ? qsTr("Unarchive Wallet") : qsTr("Archive Wallet") anchors.centerIn: parent @@ -417,5 +418,57 @@ QQC2.Control { onClicked: root.account.isArchived = !root.account.isArchived } } + + Item { width: 1; height: 30 } // spacer. + + Rectangle { + id: reindexButton + height: archiveButtonText.height + 20 + color: mainWindow.errorRedBg + width: reindexLabel.width + 30 + radius: 3 + + Flowee.Label { + id: reindexLabel + text: qsTr("Re-scan Chain"); + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.ArrowCursor + onClicked: { + thePile.pop(); + portfolio.current = root.account; + root.account.rescanBlockchain(); + } + } + } + + Item { width: 1; height: 30 } // spacer. + + Rectangle { + id: removeWallet + height: archiveButtonText.height + 20 + color: mainWindow.errorRedBg + width: removeWalletLabel.width + 30 + visible: root.account.isArchived + radius: 3 + + Flowee.Label { + id: removeWalletLabel + text: qsTr("Remove Wallet"); + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.ArrowCursor + onClicked: { + thePile.pop(); + root.account.removeAccount(); + } + } + } + + Item { width: 1; height: 20 } // spacer. } } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 54afbf8..a5e668a 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -27,6 +27,7 @@ #include #include +#include AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) @@ -400,6 +401,58 @@ void AccountInfo::closeWallet() setHasFreshTransactions(false); } +void AccountInfo::rescanBlockchain() +{ + assert(m_wallet); + auto prio = m_wallet->segment()->priority(); + const auto segmentId = m_wallet->segment()->segmentId(); + const auto seed = m_wallet->encryptionSeed(); + QDir dir(QString::fromStdString(m_wallet->walletDir().string())); + m_wallet->saveWallet(); // ensure wallet.dat changes are saved here, not on destructor. + m_wallet->deleteLater(); + auto *oldWallet = m_wallet; + + // the only thing we need to do is remove 'wallet.dat' + dir.remove("wallet.dat"); + + // now we start a new wallet object that will be without the history. + dir.cdUp(); + m_wallet = new Wallet(dir.absolutePath().toStdString(), segmentId, seed); + m_wallet->moveToThread(thread()); + if (prio == PrivacySegment::OnlyManual) // un-archive it. + prio = PrivacySegment::Normal; + m_wallet->segment()->setPriority(prio); + auto *f = FloweePay::instance(); + f->addWallet(m_wallet); + f->removeWallet(oldWallet); + + // initiate the download + if (!f->isOffline()) + f->p2pNet()->addAction(); + + auto m = m_model.release(); + if (m) + m->deleteLater(); + emit modelsChanged(); + emit lastBlockSynchedChanged(); + emit balanceChanged(); + emit utxosChanged(); + emit timeBehindChanged(); + emit isArchivedChanged(); +} + +void AccountInfo::removeAccount() +{ + assert(m_wallet); + QDir dir(QString::fromStdString(m_wallet->walletDir().string())); + m_wallet->saveWallet(); // ensure wallet.dat changes are saved here, not on destructor. + m_wallet->deleteLater(); + // Remove the actual files on disk. + dir.removeRecursively(); + // the next will implicitly remove this AccountInfo instance. + FloweePay::instance()->removeWallet(m_wallet); +} + bool AccountInfo::isSingleAddressAccount() const { return m_wallet->isSingleAddressWallet(); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 2bd967e..cde145f 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -122,6 +122,12 @@ public: /// Remove all secrets from the encrypted wallet. Q_INVOKABLE void closeWallet(); + /// Remove history and re-scan the chain for all transactions. + Q_INVOKABLE void rescanBlockchain(); + + // Remove, delete, throw in a black-hole, the wallet and all files. + Q_INVOKABLE void removeAccount(); + Wallet *wallet() const { return m_wallet; } @@ -210,7 +216,7 @@ private slots: void walletEncryptionChanged(); private: - Wallet * const m_wallet; + Wallet *m_wallet; AccountConfig m_config; QTimer *m_closeWalletTimer = nullptr; std::unique_ptr m_model; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index fd3d03a..0407ab0 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -831,7 +831,7 @@ int FloweePay::chainHeight() void FloweePay::setHeaderSyncHeight(int newHeight) { if (m_wallets.count() > 1) { - for (auto *wallet : m_wallets) { + for (auto *wallet : std::as_const(m_wallets)) { if (!wallet->userOwnedWallet()) { // we create a default wallet on first startup but we ignore that one // should the user create a new one directly afterwards. @@ -935,6 +935,34 @@ IndexerServices *FloweePay::indexerServices() const return m_indexerServices; } +void FloweePay::removeWallet(Wallet *wallet) +{ + auto count = m_wallets.removeAll(wallet); + assert(count); + + auto *dl = m_downloadManager.get(); + assert(dl); // should be initialized by now. + dl->removeDataListener(wallet); + dl->removeHeaderListener(wallet); + dl->connectionManager().removePrivacySegment(wallet->segment()); + emit walletsChanged(); +} + +void FloweePay::addWallet(Wallet *wallet) +{ + assert(wallet); + assert(wallet->segment()); + assert(!m_wallets.contains(wallet)); + auto *dl = m_downloadManager.get(); + assert(dl); // should be initialized by now. + dl->addDataListener(wallet); + dl->addHeaderListener(wallet); + dl->connectionManager().addPrivacySegment(wallet->segment()); + + m_wallets.append(wallet); + emit walletsChanged(); +} + bool FloweePay::skinFollowsPlatform() const { return m_skinFollowsPlatform; diff --git a/src/FloweePay.h b/src/FloweePay.h index b81d6c2..6e39474 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -372,6 +372,10 @@ public: IndexerServices *indexerServices() const; + /// removes wallet from the list of wallets this app knows about. + void removeWallet(Wallet *wallet); + void addWallet(Wallet *wallet); + signals: void loadComplete(); /// \internal @@ -394,7 +398,6 @@ signals: void privateModeChanged(); void appProtectionChanged(); void paymentProtocolRequestChanged(); - void skinFollowsPlatformChanged(); private slots: diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 178b736..7f8fa58 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -33,19 +33,30 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) connect (app, &FloweePay::walletsChanged, this, [=]() { const auto &wallets = FloweePay::instance()->wallets(); - if (wallets.isEmpty()) { - m_accounts.clear(); - for (auto ai : m_accountInfos) { + QList accountInfos; + QList accounts; + // remove infos that no longer exist + for (auto ai : std::as_const(m_accountInfos)) { + if (wallets.contains(ai->wallet())) { + accountInfos.append(ai); + accounts.append(ai->wallet()); + } + else { ai->deleteLater(); } - m_accountInfos.clear(); } + m_accountInfos = accountInfos; + m_accounts = accounts; + + // adding will do de-duplication. for (auto &w : wallets) { addWalletAccount(w); } - selectDefaultWallet(); updateIsSingleAccount(); emit accountsChanged(); + + if (m_currentAccount > m_accounts.size()) + selectDefaultWallet(); }); connect (app, SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); connect (app, &FloweePay::privateModeChanged, this, [=]() { diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 6eb8215..5c13e00 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -324,6 +324,11 @@ bool Wallet::walletIsImporting() const return m_walletIsImporting; } +boost::filesystem::path Wallet::walletDir() const +{ + return m_basedir; +} + void Wallet::updateSignatureTypes(const std::map &txData) { // this basically processes the output argument received from createWalletTransactionFromTx diff --git a/src/Wallet.h b/src/Wallet.h index 680f4e1..6c432d9 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -386,6 +386,8 @@ public: */ bool walletIsImporting() const; + boost::filesystem::path walletDir() const; + public slots: void delayedSave(); -- 2.54.0 From 651834caf5939054bfc7866f1eff57dc0e35fd30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Oct 2024 11:28:23 +0200 Subject: [PATCH 0986/1428] Google reportedly requires this 1 num to be changed to be allowed to upload it to the google store. Maybe once a year requiring an upgrade to the latest release is Ok for Google, for app-devs it is a chore that makes no sense. Avoiding using the deprecated one would make sense, just forcing me to use the latest, not so much. --- android/AndroidManifest.xml | 2 +- android/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 2d5666a..8d17ceb 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="29" android:versionName="2024.10.0"> diff --git a/android/build.gradle b/android/build.gradle index a5984fa..f87d9e9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -74,7 +74,7 @@ android { defaultConfig { resConfig "en" minSdkVersion qtMinSdkVersion - targetSdkVersion 33 + targetSdkVersion 34 ndk.abiFilters = qtTargetAbiList.split(",") } } -- 2.54.0 From 8872a1655959353c916e5cccb3f50b8c5dc85a37 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:08:43 +0200 Subject: [PATCH 0987/1428] Remove debug data from GUI. --- guis/desktop/NewAccountCreateBasicAccount.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/NewAccountCreateBasicAccount.qml b/guis/desktop/NewAccountCreateBasicAccount.qml index d415815..f5763c4 100644 --- a/guis/desktop/NewAccountCreateBasicAccount.qml +++ b/guis/desktop/NewAccountCreateBasicAccount.qml @@ -36,7 +36,7 @@ Item { Flowee.Label { id: title - text: qsTr("Create a new empty wallet with simple multi-address capability ") + columnWidth + text: qsTr("Create a new empty wallet with simple multi-address capability ") Layout.fillWidth: true font.bold: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere -- 2.54.0 From 32d1e22c77c9ece3ddac8ab72f72b8a41ad7fb84 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:38:12 +0200 Subject: [PATCH 0988/1428] Improve text Remove the atrocious text and replace it with the text we had on Desktop which is much simpler. --- guis/mobile/AccountPageListItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 2aa1dad..1b311ca 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -244,7 +244,7 @@ QQC2.Control { text: qsTr("Used Addresses"); visible: !root.account.isSingleAddressAccount onClicked: root.account.secrets.showUsedAddresses = checked - toolTipText: qsTr("Switches between still in use addresses and formerly used, new empty, addresses") + toolTipText: qsTr("Switches between unused and used Bitcoin addresses") } } -- 2.54.0 From ba5d6630ab2719a67583b0e98339389802cb4da5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:48:30 +0200 Subject: [PATCH 0989/1428] Add 'paste' right mouse action. --- guis/Flowee/TextField.qml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index 759fa9a..b8ae456 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls as QQC2 +import Flowee.org.pay; /* * Sane defaults. @@ -68,4 +69,26 @@ QQC2.TextField { } border.width: 0.8 } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: menu.popup(); + + QQC2.Menu { + id: menu + QQC2.MenuItem { + text: qsTr("Paste") + ClipboardHelper { + id: cbh + enabled: menu.visible + filter: ClipboardHelper.NoFilter + } + onTriggered: { + root.text = cbh.text + root.forceActiveFocus(); + } + } + } + } } -- 2.54.0 From 1ab012e5d72454a3582989335eb8bac73caef2f5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:48:47 +0200 Subject: [PATCH 0990/1428] Fix grammar. --- guis/desktop/ReceiveTransactionPane.qml | 4 ++-- guis/mobile/ReceiveTab.qml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index f83a4ae..8a46149 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -164,7 +164,7 @@ Item { var s = request.state; if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received - return qsTr("Transaction high risk") + return qsTr("High risk transaction") if (s === PaymentRequest.PaymentSeen) return qsTr("Payment Seen") if (s === PaymentRequest.PaymentSeenOk) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 183e4ff..4a97ba6 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -387,7 +387,7 @@ FocusScope { var s = request.state; if (s === PaymentRequest.DoubleSpentSeen) { // double-spent-proof received - return qsTr("Transaction high risk"); + return qsTr("High risk transaction"); } if (s === PaymentRequest.PartiallyPaid) return qsTr("Partially Paid"); -- 2.54.0 From 40e94b8255a9264b6b8df62d09afd46b4898bcc9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:51:42 +0200 Subject: [PATCH 0991/1428] Consistency in wording Now both the 'new account' tabs have the same style of grammar --- guis/desktop/NewAccountCreateHDAccount.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/NewAccountCreateHDAccount.qml b/guis/desktop/NewAccountCreateHDAccount.qml index 9d61c78..7368927 100644 --- a/guis/desktop/NewAccountCreateHDAccount.qml +++ b/guis/desktop/NewAccountCreateHDAccount.qml @@ -30,7 +30,7 @@ Item { width: parent.width Flowee.Label { id: title - text: qsTr("Creates a new wallet with smart creation of addresses from a single seed-phrase") + text: qsTr("Create a new wallet with smart creation of addresses from a single seed-phrase") Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.bold: true -- 2.54.0 From 1d7306d322a83ba5db4bfb1584e788b93ca7bd34 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:55:51 +0200 Subject: [PATCH 0992/1428] improve translator help text --- modules/build-transaction/PayToOthers.qml | 2 +- modules/peers-view/NetView.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 44e87d7..88284dc 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -183,7 +183,7 @@ Page { var s = paymentDetail.niceAddress if (s === "") { if (paymentDetail.address === "") // the user-specified text is empty - return qsTr("unset", "indication of empty"); + return qsTr("unset", "indication of desination not being set"); return qsTr("invalid", "address is not correct"); } return s; diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index 6a296a8..fd90863 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -106,7 +106,7 @@ Mobile.Page { if (validity === Wallet.CheckedOk) return qsTr("Validated"); if (validity === Wallet.KnownGood) - return qsTr("Good Peer"); + return qsTr("Good Peer", "A useful peer"); return ""; // unknown } var accounts = portfolio.rawAccounts; -- 2.54.0 From 1b4ee23f46fe98fc7b4c7b10b921b6a8ecf795f8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 13:25:22 +0200 Subject: [PATCH 0993/1428] Make sweep screen better translatable --- modules/send-sweep/SendPage.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 2aeee03..daad6c5 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -70,11 +70,11 @@ Mobile.Page { Flowee.Label { id: coinsLabel visible: sweeper.numOutputsFound > 0 || sweeper.prepared - text: qsTr("Found %1 coins on address.").arg(sweeper.numOutputsFound) + text: qsTr("Found %1 coins on address.", "this is a simple number", sweeper.numOutputsFound).arg(sweeper.numOutputsFound) } Flowee.Label { visible: sweeper.numTokensFound > 0 - text: qsTr("Ignoring %1 tokens.").arg(sweeper.numTokensFound) + text: qsTr("Ignoring %1 tokens.", "Number of CashTokens", sweeper.numTokensFound).arg(sweeper.numTokensFound) } Item { -- 2.54.0 From ce827ed776091ebe6dcbc47b33f7eb2a77bf2e86 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 12:15:32 +0200 Subject: [PATCH 0994/1428] Import translations --- CMakeLists.txt | 4 + translations/floweepay-common_de.ts | 81 ++-- translations/floweepay-common_es.ts | 81 ++-- translations/floweepay-common_nl.ts | 28 +- translations/floweepay-common_pl.ts | 87 ++-- translations/floweepay-desktop_de.ts | 460 +++++++++--------- translations/floweepay-desktop_es.ts | 452 +++++++++--------- translations/floweepay-desktop_nl.ts | 6 +- translations/floweepay-desktop_pl.ts | 494 +++++++++++--------- translations/floweepay-mobile_de.ts | 239 +++++----- translations/floweepay-mobile_es.ts | 237 +++++----- translations/floweepay-mobile_nl.ts | 20 +- translations/floweepay-mobile_pl.ts | 261 ++++++----- translations/mobile-i18n.qrc | 4 + translations/module-build-transaction_pl.ts | 16 +- translations/module-peers-view_de.ts | 21 +- translations/module-peers-view_es.ts | 21 +- translations/module-peers-view_nl.ts | 21 +- translations/module-peers-view_pl.ts | 59 +-- translations/module-send-sweep_de.ts | 105 +++++ translations/module-send-sweep_en.ts | 103 ++++ translations/module-send-sweep_es.ts | 105 +++++ translations/module-send-sweep_nl.ts | 56 ++- translations/module-send-sweep_pl.ts | 109 +++++ 24 files changed, 1849 insertions(+), 1221 deletions(-) create mode 100644 translations/module-send-sweep_de.ts create mode 100644 translations/module-send-sweep_en.ts create mode 100644 translations/module-send-sweep_es.ts create mode 100644 translations/module-send-sweep_pl.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index cda48e0..a7241b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,7 +132,11 @@ if(NOT ANDROID) translations/module-peers-view_pt.ts translations/module-peers-view_ha.ts + translations/module-send-sweep_en.ts translations/module-send-sweep_nl.ts + translations/module-send-sweep_es.ts + translations/module-send-sweep_de.ts + translations/module-send-sweep_pl.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 90debd3..75f2d47 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Geldbörse: Aktuell - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Hinterher: %1 Tag @@ -31,17 +31,17 @@ - + Up to date Aktuell - + Updating Aktualisierung - + Still %1 hours behind Noch %1 Stunde hinterher @@ -99,27 +99,27 @@ Transaktion wurde vom Netzwerk abgelehnt - + Payment has been sent to: Zahlung wurde gesendet an: - + Copied address to clipboard Adresse in Zwischenablage kopiert - + Opening Website Öffne Website - + Add a personal note Eine persönliche Notiz hinzufügen - + Close Schließen @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -279,32 +279,37 @@ Payment - + Invalid PIN Ungültige PIN - + + Wallet is locked + Wallet is locked + + + Not enough funds selected for fees Nicht genug Guthaben für Gebühren ausgewählt - + Not enough funds in wallet to make payment! Nicht genügend Guthaben in der Geldbörse, um eine Zahlung zu machen! - + Transaction too large. Amount selected needs too many coins. Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. - + Request received over insecure channel. Anyone could have altered it! Anfrage über unsicheren Kanal empfangen. Jeder hätte sie verändern können! - + Download of payment request failed. Download der Zahlungsanfrage fehlgeschlagen. @@ -312,13 +317,13 @@ PaymentProtocolBip70 - + Payment request unreadable Zahlungsanfrage unlesbar - - + + Payment request expired Zahlungsanfrage abgelaufen @@ -331,17 +336,17 @@ Einfügen - + Failed Fehlgeschlagen - + Instant Pay limit is %1 Sofortzahlungslimit ist %1 - + Selected wallet: '%1' Ausgewählte Geldbörse: '%1' @@ -354,6 +359,14 @@ In die Zwischenablage kopiert + + TextField + + + Paste + Einfügen + + TextPasteDecorator @@ -421,7 +434,7 @@ - + Change #%1 Wechselgeld #%1 diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 82b27b1..115c469 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Sin conexión - + Wallet: Up to date Monedero: Actualizado - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Retraso: %1 día @@ -31,17 +31,17 @@ - + Up to date Actualizado - + Updating Actualizando - + Still %1 hours behind Todavía %1 hora de retraso @@ -99,27 +99,27 @@ Transacción rechazada por la red - + Payment has been sent to: El pago ha sido enviado a: - + Copied address to clipboard Dirección copiada al portapapeles - + Opening Website Abriendo Sitio Web - + Add a personal note Añadir una nota personal - + Close Cerrar @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Monedero inicial - - + + Today Hoy - - + + Yesterday Ayer - + Now timestamp Ahora - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp Hace ½ hora - + %1 hours ago timestamp @@ -279,32 +279,37 @@ Payment - + Invalid PIN PIN inválido - + + Wallet is locked + Monedero bloqueado + + + Not enough funds selected for fees No se han seleccionado suficientes fondos para cubrir la comisión - + Not enough funds in wallet to make payment! ¡El monedero no posee suficientes fondos para procesar el pago! - + Transaction too large. Amount selected needs too many coins. Transacción demasiado larga. La cantidad seleccionada necesita demasiadas monedas. - + Request received over insecure channel. Anyone could have altered it! Solicitud recibida por un canal inseguro. ¡Cualquiera podría haberla alterado! - + Download of payment request failed. Descarga de solicitud de pago fallida. @@ -312,13 +317,13 @@ PaymentProtocolBip70 - + Payment request unreadable Solicitud de pago ilegible - - + + Payment request expired Solicitud de pago expirada @@ -331,17 +336,17 @@ Pegar - + Failed Fallido - + Instant Pay limit is %1 El límite de pago instantáneo es %1 - + Selected wallet: '%1' Monedero seleccionado: '%1' @@ -354,6 +359,14 @@ Copiado al portapapeles + + TextField + + + Paste + Pegar + + TextPasteDecorator @@ -421,7 +434,7 @@ - + Change #%1 Cambio #%1 diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 407b987..eb6d317 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Portemonnee: gesynchroniseerd - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days %1 dag oude data @@ -31,17 +31,17 @@ - + Up to date Is volledig bijgewerkt - + Updating Aan het bijwerken - + Still %1 hours behind Nog één uur @@ -299,17 +299,17 @@ Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. - + Request received over insecure channel. Anyone could have altered it! Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! - + Download of payment request failed. Downloaden van betalingsverzoek mislukt. @@ -359,6 +359,14 @@ Naar klembord gekopieerd + + TextField + + + Paste + Plak + + TextPasteDecorator diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index a1cc959..6c091f3 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -4,28 +4,28 @@ AccountInfo - + Offline Rozłączony - + Wallet: Up to date Portfel: Aktualny - + Behind: %1 weeks, %2 days counter on weeks W tyle: %1 tyg., %2 d. - W tyle: %1 tydzień, %2 dni - W tyle: %1 tygodni, %2 dzień + W tyle: %1 tyg., %2 d. + W tyle: %1 tyg., %2 d. W tyle: %1 tyg., %2 d. - + Behind: %1 days W tyle: %1 dzień @@ -35,17 +35,17 @@ - + Up to date Na bieżąco - + Updating Aktualizowanie - + Still %1 hours behind Nadal %1 godzinę w tyle @@ -79,7 +79,7 @@ self payment to self - Ja + własny @@ -105,27 +105,27 @@ Transakcja odrzucona przez sieć - + Payment has been sent to: Płatność została wysłana do: - + Copied address to clipboard Adres skopiowany do schowka - + Opening Website Otwieranie Strony - + Add a personal note Dodaj osobistą notatkę - + Close Zamknij @@ -154,30 +154,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -188,13 +188,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -293,32 +293,37 @@ Payment - + Invalid PIN Nieprawidłowy PIN - + + Wallet is locked + Portfel zablokowany + + + Not enough funds selected for fees Nie wybrano wystarczającej ilości środków, by pokryć koszt transakcji - + Not enough funds in wallet to make payment! Niewystarczająca ilość środków w portfelu, aby dokonać płatności! - + Transaction too large. Amount selected needs too many coins. Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. - + Request received over insecure channel. Anyone could have altered it! Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł je zmodyfikować! - + Download of payment request failed. Pobieranie żądania płatności nie powiodło się. @@ -326,13 +331,13 @@ PaymentProtocolBip70 - + Payment request unreadable Żądanie płatności nieczytelne - - + + Payment request expired Żądanie płatności wygasło @@ -345,17 +350,17 @@ Wklej - + Failed Niepowodzenie - + Instant Pay limit is %1 Limit szybkiej płatności wynosi %1 - + Selected wallet: '%1' Wybrany portfel: '%1' @@ -368,6 +373,14 @@ Skopiowano do schowka + + TextField + + + Paste + Wklej + + TextPasteDecorator @@ -443,7 +456,7 @@ - + Change #%1 Reszta #%1 diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 00a9685..a1f27b3 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -84,72 +84,72 @@ Ungültige PIN - + Include balance in total Inkludiere Guthaben in Gesamtsumme - + Hide in private mode Im privaten Modus ausblenden - + Address List Adressliste - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen - + Backup details Backup-Details - + Seed-phrase Seed-Phrase - + Seed format Seed-Format - + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -170,42 +170,42 @@ IP-Adressen - + Total found Gesamt gefunden - + Tried Versuchte - + Punished count Abgestrafte Anzahl - + Banned count Gebannte Anzahl - + IP-v4 count IPv4-Anzahl - + IP-v6 count IPv6-Anzahl - + Pardon the Banned Gesperrte Knoten entsperren - + Close Schließen @@ -221,38 +221,38 @@ - + Address network address (IP) Adresse - + Start-height: %1 Starthöhe: %1 - + ban-score: %1 ban-score: %1 - + Peer for wallet: %1 Peer für Geldbörse: %1 - + Disconnect Peer Trenne Knoten - + Ban Peer Sperre Knoten - + Close Schließen @@ -269,11 +269,6 @@ Name Name - - - Go - Los - Force Single Address @@ -286,13 +281,18 @@ This ensures only one private key will need to be backed up Wenn aktiviert, wird diese Geldbörse auf eine Adresse beschränkt. Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss + + + Go + Los + NewAccountCreateHDAccount - Creates a new wallet with smart creation of addresses from a single seed-phrase - Erstellt eine neue Geldbörse mit intelligenter Erstellung von Adressen aus einer einzigen Seed-Phrase + Create a new wallet with smart creation of addresses from a single seed-phrase + Create a new wallet with smart creation of addresses from a single seed-phrase @@ -317,12 +317,6 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss NewAccountImportAccount - - - - Name - Name - Select import method @@ -367,59 +361,60 @@ Change will come back to the imported key. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + + New Wallet Name + New Wallet Name + + + + Start Start - + Password Passwort - - Optional - Optional + + imported wallet password + imported wallet password - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Suchdetails + Discover Details - - Nothing found for seed - Nichts gefunden für Seed - - - + Derivation Ableitung + + + Nothing found for seed + Nichts gefunden für Seed + NewAccountPane @@ -428,11 +423,39 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. New HD wallet Neue HD-Geldbörse + + + Seed-phrase based + Context: wallet type + Seed-Phrase basiert + + + + Easy to backup + Context: wallet type + Einfach zu sichern + + + + Most compatible + The most compatible wallet type + Am kompatibelsten + Import Existing Wallet Import einer vorhandenen Geldbörse + + + Imports seed-phrase + Importiert Seed-Phrase + + + + Imports private key + Importiert privaten Schlüssel + New Basic Wallet @@ -456,52 +479,34 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Context: wallet type Ideal für kurze Nutzung - - - Seed-phrase based - Context: wallet type - Seed-Phrase basiert - - - - Easy to backup - Context: wallet type - Einfach zu sichern - - - - Most compatible - The most compatible wallet type - Am kompatibelsten - - - - Imports seed-phrase - Importiert Seed-Phrase - - - - Imports private key - Importiert privaten Schlüssel - PaymentTweakingPanel - + Add Detail Detail hinzufügen - + Coin Selector Coin Selektor - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. Um die Standardauswahl von Coins zu überschreiben, die zur Zahlung einer Transaktion verwendet werden, können Sie den 'Coin Selektor' hinzufügen, in dem die Coins der Geldbörse sichtbar gemacht werden. + + + Comment + Kommentar + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + This allows adding a public comment, that will be included in the transaction and seen by everyone. + ReceiveTransactionPane @@ -521,52 +526,52 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Import läuft... - + Checking Überprüfe - - Transaction high risk - Hoch-Risiko Transaktion + + High risk transaction + High risk transaction - + Payment Seen Zahlung gesichtet - + Payment Accepted Zahlung akzeptiert - + Payment Settled Zahlung abgeschlossen - + Instant payment failed. Wait for confirmation. (double spent proof received) Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) - + Description Beschreibung - + Amount Betrag - + Clear Leeren - + Done Erledigt @@ -574,147 +579,147 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. SendTransactionPane - + Confirm delete Löschen bestätigen - + Do you really want to delete this detail? Möchten Sie dieses Detail wirklich löschen? - + Add Destination Ziel hinzufügen - + Prepare Vorbereiten - + Enter your PIN Geben Sie Ihre PIN ein - - + + Warning Warnung - + Payment request warnings: Warnungen für Zahlungsanforderungen: - + Transaction Details Transaktionsdetails - + Not prepared yet Noch nicht vorbereitet - + Copy transaction-ID Transaktions-ID kopieren - + Fee Gebühr - + Transaction size Transaktionsgröße - + %1 bytes %1 Bytes - + Fee per byte Gebühr pro Byte - + %1 sat/byte fee %1 Sat/Byte - + Send Senden - + Destination Ziel - + Max available The maximum balance available Max. verfügbar - + %1 to %2 summary text to pay X-euro to address M %1 zu %2 - + Enter Bitcoin Cash Address Geben Sie eine Bitcoin Cash Adresse ein - - + + Copy Address Adresse kopieren - + Amount Betrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + Continue Fortfahren - + Cancel Abbrechen - + Coin Selector Coin Selektor - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -723,56 +728,81 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Total Number of coins Gesamt - + Needed Benötigt - + Selected Ausgewählt - + Value Wert - + Locked coins will never be used for payments. Right-click for menu. Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin Coin entsperren - + Lock coin Coin sperren + + + Public-comment + Public-comment + + + + Add a comment you want to include in the transaction, visible for everyone. + Add a comment you want to include in the transaction, visible for everyone. + + + + Custom message, to be included in the transaction. + Custom message, to be included in the transaction. + + + + Text + Text + + + + Size + Größe + SettingsPane @@ -888,27 +918,27 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Transaktionsdetails - + First Seen Erstsichtung - + Rejected - Abgelehnt + Rejected - + Unconfirmed Unbestätigt - + Mined at Abgebaut am - + %1 blocks ago %1 Block her @@ -916,67 +946,67 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Comment Kommentar - + Fees paid Bezahlte Gebühren - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 Bytes - + Size Größe - + %1 bytes %1 Bytes - + Is Coinbase Ist Coinbase - + Copy transaction-ID Kopiere Transaktions-ID - + Fused from my addresses Fusioniert von meinen Adressen - + Sent from my addresses Gesendet von meinen Adressen - + Sent from addresses Gesendet von den Adressen - + Fused into my addresses Fusioniert in meine Adressen - + Received at addresses Empfangen auf den Adressen - + Received at my addresses Empfangen auf meinen Adressen @@ -1015,7 +1045,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Copy transaction-ID - Kopiere Transaktions-ID + Transaktions-ID kopieren @@ -1036,119 +1066,119 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Schützen Sie Ihre Geldbörse mit einem Passwort - + Pin to Pay Pin um zu bezahlen - + Protect your funds pin to pay Schützen Sie Ihr Guthaben - + Fully open, except for sending funds pin to pay Vollständig geöffnet, außer für das Senden von Guthaben - + Keeps in sync pin to pay Bleibt synchron - + Pin to Open Pin zum Öffnen - + Protect your entire wallet pin to open Schützen Sie Ihre gesamte Geldbörse - + Balance and history protected pin to open Guthaben und Verlauf geschützt - + Requires Pin to view, sync or pay pin to open Benötigt Pin zum Ansehen, Synchronisieren oder Bezahlen - + Make "%1" wallet require a pin to pay Erfordere einen Pin zum Bezahlen für "%1" Geldbörse - + Make "%1" wallet require a pin to open Erfordere einen Pin zum Öffnen für "%1" Geldbörse - - + + Wallet already has pin to open applied Geldbörse hat bereits eine PIN zum Öffnen an - + Wallet already has pin to pay applied Geldbörse hat bereits eine PIN zum Bezahlen - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Ihre Geldbörse wird teilweise verschlüsselt und Zahlungen werden ohne Passwort nicht möglich. Wenn Sie über kein Backup dieser Geldbörse verfügen, erstellen Sie zuerst eines. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Ihre vollständige Geldbörse wird verschlüsselt, um sie zu öffnen, wird ein Passwort benötigen. Wenn Sie über kein Backup dieser Geldbörse verfügen, erstellen Sie zuerst eines. - + Password Passwort - + Wallet Geldbörse - + Encrypt Verschlüsseln - + Invalid password to open this wallet Ungültiges Passwort zum Öffnen dieser Geldbörse - + Close Schließen - + Repeat password Passwort wiederholen - + Please confirm the password by entering it again Bitte bestätigen Sie Ihr Passwort, indem Sie es erneut eingeben - + Typed passwords do not match Die eingegebenen Passwörter stimmen nicht überein @@ -1156,17 +1186,17 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. WalletEncryptionStatus - + Pin to Open Pin zum Öffnen - + Pin to Pay Pin um zu bezahlen - + (Opened) Wallet is decrypted (Geöffnet) @@ -1175,95 +1205,95 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. main - + Activity Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1272,12 +1302,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index 011a02d..1a52278 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -84,72 +84,72 @@ PIN inválido - + Include balance in total Incluir en el balance total - + Hide in private mode Ocultar en modo privado - + Address List Lista de direcciones - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Alterna entre direcciones donde otros pueden pagarte y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between unused and used Bitcoin addresses Alterna entre direcciones no usadas y usadas de Bitcoin - + Backup details Detalles de la copia de seguridad - + Seed-phrase Frase semilla - + Seed format Formato de semilla - + Derivation Derivación - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. @@ -170,42 +170,42 @@ Direcciones de IP - + Total found Total encontrado - + Tried Intentado - + Punished count Número de castigados - + Banned count Número de baneados - + IP-v4 count Recuento IP-v4 - + IP-v6 count Recuento IP-v6 - + Pardon the Banned Perdonar los Baneados - + Close Cerrar @@ -221,38 +221,38 @@ - + Address network address (IP) Dirección - + Start-height: %1 Altura de inicio: %1 - + ban-score: %1 calificación de no confiabilidad: %1 - + Peer for wallet: %1 Par para el monedero: %1 - + Disconnect Peer Desconectar par - + Ban Peer Banear par - + Close Cerrar @@ -269,11 +269,6 @@ Name Nombre - - - Go - Ir - Force Single Address @@ -286,12 +281,17 @@ This ensures only one private key will need to be backed up Cuando está habilitado, este monedero se limitará a una dirección. Esto asegura que solo una clave privada tendrá que ser respaldada + + + Go + Ir + NewAccountCreateHDAccount - Creates a new wallet with smart creation of addresses from a single seed-phrase + Create a new wallet with smart creation of addresses from a single seed-phrase Crea una nueva cartera con la creación inteligente de direcciones a partir de una sola frase semilla @@ -317,12 +317,6 @@ Esto asegura que solo una clave privada tendrá que ser respaldada NewAccountImportAccount - - - - Name - Nombre - Select import method @@ -367,59 +361,60 @@ Change will come back to the imported key. El cambio volverá a la clave importada. - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - + + + New Wallet Name + Nuevo nombre de billetera + + + + Start Comenzar - + Password Contraseña - - Optional - Opcional + + imported wallet password + imported wallet password - - Details - Detalles - - - - Lookup Details + + Discover Details online check for wallet details - Detalles de la búsqueda + Detalles de Descubre - - Nothing found for seed - No se encontró nada para la semilla - - - + Derivation Derivación + + + Nothing found for seed + No se encontró nada para la semilla + NewAccountPane @@ -428,11 +423,39 @@ El cambio volverá a la clave importada. New HD wallet Nuevo monedero-HD + + + Seed-phrase based + Context: wallet type + Basado en frase semilla + + + + Easy to backup + Context: wallet type + Fácil de respaldar + + + + Most compatible + The most compatible wallet type + El más compatible + Import Existing Wallet Importar un monedero existente + + + Imports seed-phrase + Importa la frase semilla + + + + Imports private key + Importa la clave privada + New Basic Wallet @@ -456,52 +479,34 @@ El cambio volverá a la clave importada. Context: wallet type Ideal para un uso breve - - - Seed-phrase based - Context: wallet type - Basado en frase semilla - - - - Easy to backup - Context: wallet type - Fácil de respaldar - - - - Most compatible - The most compatible wallet type - El más compatible - - - - Imports seed-phrase - Importa la frase semilla - - - - Imports private key - Importa la clave privada - PaymentTweakingPanel - + Add Detail Agregar detalles - + Coin Selector Selector de moneda - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. Para reemplazar la selección predeterminada de monedas que se utilizan para pagar una transacción, puedes añadir el 'Selector de Monedas' donde se harán visibles las monedas. + + + Comment + Comentario + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + This allows adding a public comment, that will be included in the transaction and seen by everyone. + ReceiveTransactionPane @@ -521,52 +526,52 @@ El cambio volverá a la clave importada. Ejecutando Importación... - + Checking Comprobando - - Transaction high risk + + High risk transaction Transacción de alto riesgo - + Payment Seen Pago Enviado - + Payment Accepted Pago Aceptado - + Payment Settled Pago realizado - + Instant payment failed. Wait for confirmation. (double spent proof received) Pago instantáneo fallido. Espere la confirmación. (prueba de doble gasto recibida) - + Description Descripción - + Amount Monto - + Clear Borrar - + Done Hecho @@ -574,147 +579,147 @@ El cambio volverá a la clave importada. SendTransactionPane - + Confirm delete Confirmar eliminación - + Do you really want to delete this detail? ¿Realmente quieres borrar esta especificación? - + Add Destination Añadir destino - + Prepare Preparar - + Enter your PIN Introduce tu código PIN - - + + Warning Advertencia - + Payment request warnings: Advertencias de la solicitud de pago: - + Transaction Details Detalles de la transacción - + Not prepared yet Aún no preparado - + Copy transaction-ID Copiar ID de la transacción - + Fee Comisión - + Transaction size Tamaño de la transacción - + %1 bytes %1 bytes - + Fee per byte Comisión por byte - + %1 sat/byte fee %1 sat/byte - + Send Enviar - + Destination Destino - + Max available The maximum balance available Máximo disponible - + %1 to %2 summary text to pay X-euro to address M %1 a %2 - + Enter Bitcoin Cash Address Introduzca la dirección de Bitcoin Cash - - + + Copy Address Copiar dirección - + Amount Monto - + Max Máx - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + Continue Continuar - + Cancel Cancelar - + Coin Selector Selector de monedas - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -723,56 +728,81 @@ El cambio volverá a la clave importada. - + Total Number of coins Total - + Needed Necesario - + Selected Seleccionado - + Value Valor - + Locked coins will never be used for payments. Right-click for menu. Las monedas bloqueadas nunca se utilizarán para los pagos. Clic derecho para desplegar el menú. - + Age Edad - + Unselect All Deseleccionar Todo - + Select All Seleccionar Todo - + Unlock coin Desbloquear moneda - + Lock coin Bloquear moneda + + + Public-comment + Public-comment + + + + Add a comment you want to include in the transaction, visible for everyone. + Add a comment you want to include in the transaction, visible for everyone. + + + + Custom message, to be included in the transaction. + Custom message, to be included in the transaction. + + + + Text + Text + + + + Size + Tamaño + SettingsPane @@ -888,27 +918,27 @@ El cambio volverá a la clave importada. Detalles de la transacción - + First Seen Visto por primera vez - + Rejected Rechazado - + Unconfirmed Sin confirmar - + Mined at Minado en - + %1 blocks ago Hace %1 bloque @@ -916,67 +946,67 @@ El cambio volverá a la clave importada. - + Comment Comentario - + Fees paid Comisiones pagadas - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Tamaño - + %1 bytes %1 bytes - + Is Coinbase Es Coinbase - + Copy transaction-ID Copiar ID de la transacción - + Fused from my addresses Fusionado desde mis direcciones - + Sent from my addresses Enviado desde mis direcciones - + Sent from addresses Enviado desde direcciones - + Fused into my addresses Fusionado en mis direcciones - + Received at addresses Recibido en direcciones - + Received at my addresses Recibido en mis direcciones @@ -1036,119 +1066,119 @@ El cambio volverá a la clave importada. Protege tu monedero con una contraseña - + Pin to Pay PIN para pagar - + Protect your funds pin to pay Proteja sus fondos - + Fully open, except for sending funds pin to pay Totalmente abierto, excepto para el envío de fondos - + Keeps in sync pin to pay Mantener sincronizado - + Pin to Open PIN para abrir - + Protect your entire wallet pin to open Protege todo tu monedero - + Balance and history protected pin to open Balance e historial protegidos - + Requires Pin to view, sync or pay pin to open Requiere Pin para ver, sincronizar o pagar - + Make "%1" wallet require a pin to pay Hacer "%1" monedero requiere un pin para pagar - + Make "%1" wallet require a pin to open Hacer "%1" monedero requiere un pin para abrir - - + + Wallet already has pin to open applied El monedero ya tiene un pin para abrir aplicado - + Wallet already has pin to pay applied El monedero ya tiene pin para pagar aplicado - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Su monedero se cifrará parcialmente y los pagos serán imposibles sin una contraseña. Si no tienes una copia de seguridad de esta cartera, haz una primero. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Tu monedero completo se cifra, para abrirla necesitará una contraseña. Si no tienes una copia de seguridad de esta cartera, haz una primero. - + Password Contraseña - + Wallet Monedero - + Encrypt Encriptar - + Invalid password to open this wallet Contraseña no válida para abrir este monedero - + Close Cerrar - + Repeat password Repetir contraseña - + Please confirm the password by entering it again Por favor confirme su contraseña introduciéndola nuevamente - + Typed passwords do not match Las contraseñas escritas no coinciden @@ -1156,17 +1186,17 @@ El cambio volverá a la clave importada. WalletEncryptionStatus - + Pin to Open PIN para abrir - + Pin to Pay PIN para pagar - + (Opened) Wallet is decrypted (Abierto) @@ -1175,95 +1205,95 @@ El cambio volverá a la clave importada. main - + Activity Actividad - + Archived wallets do not check for activities. Balance may be out of date. Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado. - + Unarchive Desarchivar - + This wallet needs a password to open. Esta cartera necesita una contraseña para abrirse. - + Password: Contraseña: - + Invalid password Contraseña invalida - + Open Abrir - + Send Enviar - + Receive Recibir - + Balance Balance - + Main balance (money), non specified Principal - + Unconfirmed balance (money) Sin confirmar - + Immature balance (money) Sin madurar - + 1 BCH is: %1 1 BCH es: %1 - + Network status Estado de la red - + Offline Sin conexión - + Add Bitcoin Cash wallet Añadir monedero de Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1272,12 +1302,12 @@ El cambio volverá a la clave importada. - + Preparing... Preparando... - + QR-Scan Escaneo de QR diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index aa6e0a6..db63d61 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -291,8 +291,8 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountCreateHDAccount - Creates a new wallet with smart creation of addresses from a single seed-phrase - Maakt een nieuwe portemonnee met slimme aanmaak van adressen van één enkele herstelzin + Create a new wallet with smart creation of addresses from a single seed-phrase + Maak een nieuwe portemonnee met slimme aanmaak van adressen van één enkele herstelzin @@ -532,7 +532,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - Transaction high risk + High risk transaction Transactie met hoog risico diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index acc0462..b2976cb 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -84,72 +84,72 @@ Nieprawidłowy PIN - + Include balance in total - Uwzględnij saldo ogółem + Uwzględnij saldo w podsumowaniu - + Hide in private mode Ukryj w trybie prywatnym - + Address List Lista adresów - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Backup details Szczegóły kopii zapasowej - + Seed-phrase Seed - + Seed format - Seed format + Format seeda - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -170,42 +170,42 @@ Adresy IP - + Total found Łącznie znaleziono - + Tried - Tried + Wypróbowano - + Punished count - Punished count + Ukarano - + Banned count - Banned count + Zbanowano - + IP-v4 count - IP-v4 count + IP-v4 - + IP-v6 count - IP-v6 count + IP-v6 - + Pardon the Banned Ułaskaw Zbanowanych - + Close Zamknij @@ -223,38 +223,38 @@ - + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - + Peer for wallet: %1 Peer dla portfela: %1 - + Disconnect Peer - Disconnect Peer + Rozłącz - + Ban Peer Zbanuj peera - + Close Zamknij @@ -264,18 +264,13 @@ Create a new empty wallet with simple multi-address capability - Create a new empty wallet with simple multi-address capability + Utwórz nowy pusty portfel z prostą funkcją wieloadresową Name Nazwa - - - Go - Dalej - Force Single Address @@ -288,13 +283,18 @@ This ensures only one private key will need to be backed up Gdy włączone, ten portfel będzie ograniczony do jednego adresu. Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego + + + Go + Dalej + NewAccountCreateHDAccount - Creates a new wallet with smart creation of addresses from a single seed-phrase - Creates a new wallet with smart creation of addresses from a single seed-phrase + Create a new wallet with smart creation of addresses from a single seed-phrase + Utwórz nowy, pusty portfel z możliwością kreowania adresów z losowego ciągu wyrazów (seeda) @@ -319,12 +319,6 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountImportAccount - - - - Name - Nazwa - Select import method @@ -344,7 +338,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Secret as text The seed-phrase or private key - Secret as text + Sekret jako tekst @@ -368,59 +362,60 @@ Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age - Check Age + Sprawdź wiek portfela - + Nothing found for wallet - Nothing found for wallet + Nie znaleziono nic dla portfela - - + + + New Wallet Name + Nazwa nowego portfela + + + + Start Rozpocznij - + Password Hasło - - Optional - Opcjonalne + + imported wallet password + hasło do importowanego portfela - - Details - Szczegóły - - - - Lookup Details + + Discover Details online check for wallet details - Szczegóły wyszukiwania + Poznaj szczegóły - - Nothing found for seed - Nothing found for seed - - - + Derivation Derywacja + + + Nothing found for seed + Nic nie znaleziono dla klucza seed + NewAccountPane @@ -429,11 +424,39 @@ Change will come back to the imported key. New HD wallet Nowy portfel HD + + + Seed-phrase based + Context: wallet type + Bazuje na seedzie + + + + Easy to backup + Context: wallet type + Łatwa kopia zapasowa + + + + Most compatible + The most compatible wallet type + Najbardziej kompatybilny + Import Existing Wallet Importuj istniejący portfel + + + Imports seed-phrase + Importuje seeda + + + + Imports private key + Importuje klucz prywatny + New Basic Wallet @@ -457,52 +480,34 @@ Change will come back to the imported key. Context: wallet type Świetny do krótkotrwałego użytku - - - Seed-phrase based - Context: wallet type - Bazuje na seedzie - - - - Easy to backup - Context: wallet type - Łatwa kopia zapasowa - - - - Most compatible - The most compatible wallet type - Najbardziej kompatybilny - - - - Imports seed-phrase - Importuje seeda - - - - Imports private key - Importuje klucz prywatny - PaymentTweakingPanel - + Add Detail Dodaj szczegóły - + Coin Selector Wybór monet - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. Aby zastąpić domyślny zestaw monet, które zostaną użyte do zapłaty transakcji, użyj 'Wyboru monet', za pomocą którego zobaczysz i wybierzesz monety z tych zawartych w portfelu. + + + Comment + Komentarz + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + Pozwala na dodanie publicznego komentarza, który będzie uwzględniony w transakcji i widoczny dla wszystkich. + ReceiveTransactionPane @@ -522,52 +527,52 @@ Change will come back to the imported key. Trwa Importowanie... - + Checking Sprawdzanie - - Transaction high risk - Transakcja o wysokim poziomie ryzyka + + High risk transaction + Transakcja o wysokim ryzyku - + Payment Seen Wykryto płatność - + Payment Accepted Płatność zaakceptowana - + Payment Settled Płatność rozliczona - + Instant payment failed. Wait for confirmation. (double spent proof received) Płatność błyskawiczna nie powiodła się. Poczekaj na potwierdzenie. (otrzymano dowód podwójnej płatności) - + Description Opis - + Amount Kwota - + Clear Wyczyść - + Done Gotowe @@ -575,147 +580,147 @@ Change will come back to the imported key. SendTransactionPane - + Confirm delete Potwierdź usunięcie - + Do you really want to delete this detail? Czy na pewno chcesz usunąć te szczegóły? - + Add Destination Dodaj odbiorcę - + Prepare Przygotuj - + Enter your PIN Wprowadź PIN - - + + Warning Uwaga! - + Payment request warnings: - Ostrzeżenie o żądaniu płatności: + Ostrzeżenia dotyczące żądania płatności: - + Transaction Details Szczegóły transakcji - + Not prepared yet Jeszcze nie przygotowano - + Copy transaction-ID Kopiuj ID transakcji - + Fee Opłata - + Transaction size Rozmiar transakcji - + %1 bytes %1 B - + Fee per byte Opłata za bajt - + %1 sat/byte fee %1 sat/byte - + Send Wyślij - + Destination Odbiorca - + Max available The maximum balance available Maksymalnie dostępne - + %1 to %2 summary text to pay X-euro to address M %1 do %2 - + Enter Bitcoin Cash Address Wprowadź adres Bitcoin Cash - - + + Copy Address Skopiuj Adres - + Amount Kwota - + Max Maks. - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + Continue Kontynuuj - + Cancel Anuluj - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -726,56 +731,81 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę + + + Public-comment + Publiczny komentarz + + + + Add a comment you want to include in the transaction, visible for everyone. + Dodaj komentarz, który chcesz zawrzeć w transakcji, widoczny dla wszystkich. + + + + Custom message, to be included in the transaction. + Komunikat użytkownika, który ma być zawarty w transakcji. + + + + Text + Tekst + + + + Size + Rozmiar + SettingsPane @@ -860,7 +890,7 @@ Change will come back to the imported key. Fused - Fused + Fused @@ -891,27 +921,27 @@ Change will come back to the imported key. Szczegóły transakcji - + First Seen - First Seen - - - - Rejected - Odrzucona + Zaobserwowano + Rejected + Odrzucono + + + Unconfirmed Niepotwierdzona - + Mined at Wykopano - + %1 blocks ago %1 blok temu @@ -921,67 +951,67 @@ Change will come back to the imported key. - + Comment Komentarz - + Fees paid Koszt transakcji - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bajtów - + Size Rozmiar - + %1 bytes %1 B - + Is Coinbase Czy Coinbase - + Copy transaction-ID Skopiuj ID transakcji - + Fused from my addresses Fuzja z moich adresów - + Sent from my addresses Wysłano z moich adresów - + Sent from addresses Wysłano z adresów - + Fused into my addresses Fuzja na mój adres - + Received at addresses Wpłynęło na adresy - + Received at my addresses Wpłynęło na moje adresy @@ -1022,7 +1052,7 @@ Change will come back to the imported key. Copy transaction-ID - Skopiuj ID transakcji + Kopiuj ID transakcji @@ -1043,119 +1073,119 @@ Change will come back to the imported key. Zabezpiecz swój portfel hasłem - + Pin to Pay PIN by płacić - + Protect your funds pin to pay Zabezpiecz swoje środki - + Fully open, except for sending funds pin to pay W pełni otwarty, z wyjątkiem funkcji wysyłania środków - + Keeps in sync pin to pay Utrzymuje synchronizację - + Pin to Open PIN by otworzyć - + Protect your entire wallet pin to open Zabezpiecz cały portfel - + Balance and history protected pin to open Zabezpieczone saldo i historia - + Requires Pin to view, sync or pay pin to open Wymaga pinu do wyświetlenia, synchronizacji lub zapłaty - + Make "%1" wallet require a pin to pay Portfel "%1" będzie wymagał podania PINu by płacić - + Make "%1" wallet require a pin to open Portfel "%1" będzie wymagał podania PINu by go otworzyć - - + + Wallet already has pin to open applied Ten portfel ma już opcję "PIN by otworzyć" - + Wallet already has pin to pay applied Ten portfel ma już opcję "PIN by płacić" - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Twój portfel zostanie częściowo zaszyfrowany, a płatności staną się niemożliwe bez hasła. Jeśli nie masz kopii zapasowej tego portfela, zrób ją najpierw. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Cały Twój portfel zostanie zaszyfrowany, otwarcie go będzie wymagało hasła. Jeśli nie masz kopii zapasowej tego portfelu, najpierw ją wykonaj. - + Password Hasło - + Wallet Portfel - + Encrypt Zaszyfruj - + Invalid password to open this wallet Nieprawidłowe hasło do otwarcia tego portfela - + Close Zamknij - + Repeat password Powtórz hasło - + Please confirm the password by entering it again Proszę potwierdzić hasło wpisując je ponownie - + Typed passwords do not match Podane hasła różnią się @@ -1163,17 +1193,17 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Open PIN by otworzyć - + Pin to Pay PIN by płacić - + (Opened) Wallet is decrypted (otwarty) @@ -1182,95 +1212,95 @@ Change will come back to the imported key. main - + Activity Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1281,12 +1311,12 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… - + QR-Scan Skanowanie QR diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 9ab2fcb..9a4040f 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -62,36 +62,6 @@ Receive Empfange - - - Miner Reward - Miner Belohnung - - - - Fused - Fusioniert - - - - Received - Empfangen - - - - Moved - Verschoben - - - - Sent - Gesendet - - - - Rejected - Abgelehnt - AccountPageListItem @@ -180,8 +150,8 @@ - Switches between still in use addresses and formerly used, new empty, addresses - Wechselt zwischen noch benutzten Adressen und früher genutzten, neuen leeren Adressen + Switches between unused and used Bitcoin addresses + Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen @@ -204,15 +174,25 @@ Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren + + + Re-scan Chain + Re-scan Chain + + + + Remove Wallet + Remove Wallet + AccountSelectorPopup @@ -240,7 +220,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -309,7 +289,7 @@ Erkunden - + ON Enabled. SHORT TEXT! ON @@ -361,112 +341,107 @@ Geldbörse importieren - - - Name - Name - - - - Force Single Address - Erzwinge einzelne Adresse - - - + Select import method Importmethode auswählen - + Camera Kamera - + OR OR - + Secret as text The seed-phrase or private key Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Next Weiter - + Address to import Zu importierende Adresse - + + Force Single Address + Erzwinge einzelne Adresse + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - Nothing found for seed - Nichts gefunden für Seed - - - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + + New Wallet Name + New Wallet Name + + + + Start Start - + Password Passwort - - Optional - Optional + + imported wallet password + imported wallet password - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Suchdetails + Discover Details - - Derivation - Ableitung + + Derivation Path + Ableitungspfad + + + + Nothing found for seed + Nichts gefunden für Seed @@ -593,6 +568,11 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. New Bitcoin Cash Wallet Neue Bitcoin Cash Geldbörse + + + New HD Wallet + Neue HD-Geldbörse + Seed-phrase based @@ -611,6 +591,26 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. The most compatible wallet type Am kompatibelsten + + + Import Existing Wallet + Import einer vorhandenen Geldbörse + + + + Imports seed-phrase + Importiert Seed-Phrase + + + + Imports private key + Importiert privaten Schlüssel + + + + New Basic Wallet + Neue Basis-Geldbörse + Private keys based @@ -629,31 +629,6 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Context: wallet type Ideal für kurze Nutzung - - - Import Existing Wallet - Import einer vorhandenen Geldbörse - - - - New HD Wallet - Neue HD-Geldbörse - - - - Imports seed-phrase - Importiert Seed-Phrase - - - - Imports private key - Importiert privaten Schlüssel - - - - New Basic Wallet - Neue Basis-Geldbörse - New Wallet @@ -760,7 +735,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussZieladresse - + Unlock Wallet Geldbörse entsperren @@ -768,25 +743,25 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH ist: %1 - + 7d 7 days 7T - + 1m 1 month 1M - + 3m 3 months 3M @@ -858,8 +833,8 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - Transaction high risk - Hoch-Risiko Transaktion + High risk transaction + High risk transaction @@ -968,6 +943,11 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussAdd a different wallet Eine andere Geldbörse hinzufügen + + + Claim a Cash Stamp + Claim a Cash Stamp + TransactionDetails @@ -1137,6 +1117,39 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussTransaktionsdetails + + TransactionListItem + + + Miner Reward + Miner Belohnung + + + + Fused + Fusioniert + + + + Received + Erhalten + + + + Moved + Verschoben + + + + Sent + Gesendet + + + + Rejected + Rejected + + UnlockWidget diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index be8b4db..ce84464 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -62,36 +62,6 @@ Receive Recibir - - - Miner Reward - Recompensa del minero - - - - Fused - Fusionado - - - - Received - Recibido - - - - Moved - Movido - - - - Sent - Enviado - - - - Rejected - Rechazado - AccountPageListItem @@ -180,8 +150,8 @@ - Switches between still in use addresses and formerly used, new empty, addresses - Alterna entre direcciones en uso, previamente usadas y direcciones sin usar + Switches between unused and used Bitcoin addresses + Alterna entre direcciones no usadas y usadas de Bitcoin @@ -204,15 +174,25 @@ Ocultar en modo privado - + Unarchive Wallet Desarchivar monedero - + Archive Wallet Archivar monedero + + + Re-scan Chain + Re-scan Chain + + + + Remove Wallet + Eliminar Monedero + AccountSelectorPopup @@ -240,7 +220,7 @@ AccountSyncState - + Status: Offline Estado: Desconectado @@ -309,7 +289,7 @@ Explorar - + ON Enabled. SHORT TEXT! ENCENDIDO @@ -361,112 +341,107 @@ Importar monedero - - - Name - Nombre - - - - Force Single Address - Forzar dirección única - - - + Select import method Seleccionar método de importación - + Camera Cámara - + OR O - + Secret as text The seed-phrase or private key Secreto como texto - + Unknown word(s) found Palabra(s) desconocidas encontradas - + Next Siguiente - + Address to import Dirección a importar - + + Force Single Address + Forzar dirección única + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - Nothing found for seed - No se encontró nada para la semilla - - - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - + + + New Wallet Name + Nuevo nombre de billetera + + + + Start Comenzar - + Password Contraseña - - Optional - Opcional + + imported wallet password + imported wallet password - - Details - Detalles - - - - Lookup Details + + Discover Details online check for wallet details - Detalles de la búsqueda + Detalles de Descubre - - Derivation - Derivación + + Derivation Path + Ruta de Derivación + + + + Nothing found for seed + No se encontró nada para la semilla @@ -593,6 +568,11 @@ El cambio volverá a la clave importada. New Bitcoin Cash Wallet Nuevo monedero Bitcoin Cash + + + New HD Wallet + Nuevo monedero-HD + Seed-phrase based @@ -611,6 +591,26 @@ El cambio volverá a la clave importada. The most compatible wallet type El más compatible + + + Import Existing Wallet + Importar un monedero existente + + + + Imports seed-phrase + Importa la frase semilla + + + + Imports private key + Importa la clave privada + + + + New Basic Wallet + Nuevo monedero básico + Private keys based @@ -629,31 +629,6 @@ El cambio volverá a la clave importada. Context: wallet type Ideal para un uso breve - - - Import Existing Wallet - Importar un monedero existente - - - - New HD Wallet - Nuevo monedero-HD - - - - Imports seed-phrase - Importa la frase semilla - - - - Imports private key - Importa la clave privada - - - - New Basic Wallet - Nuevo monedero básico - New Wallet @@ -760,7 +735,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -768,25 +743,25 @@ Esto asegura que solo una clave privada tendrá que ser respaldada PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH es: %1 - + 7d 7 days 7d - + 1m 1 month 1m - + 3m 3 months 3m @@ -858,7 +833,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - Transaction high risk + High risk transaction Transacción de alto riesgo @@ -968,6 +943,11 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Add a different wallet Añadir un monedero diferente + + + Claim a Cash Stamp + Reclamar un Cash Stamp + TransactionDetails @@ -1137,6 +1117,39 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Detalles de la transacción + + TransactionListItem + + + Miner Reward + Recompensa del minero + + + + Fused + Fusionado + + + + Received + Recibido + + + + Moved + Movido + + + + Sent + Enviado + + + + Rejected + Rechazado + + UnlockWidget diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index c10d022..3470135 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -150,8 +150,8 @@ - Switches between still in use addresses and formerly used, new empty, addresses - Schakelt tussen nog steeds gebruikte adressen en eerder gebruikte, nieuw/lege adressen + Switches between unused and used Bitcoin addresses + Schakelt tussen ongebruikt en gebruikte Bitcoin adressen @@ -174,15 +174,25 @@ Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren + + + Re-scan Chain + Keten herscannen + + + + Remove Wallet + Portemonnee verwijderen + AccountSelectorPopup @@ -823,7 +833,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - Transaction high risk + High risk transaction Transactie met hoog risico diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 42890bb..51cb0ea 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -62,36 +62,6 @@ Receive Otrzymaj - - - Miner Reward - Nagroda dla górnika - - - - Fused - Fused - - - - Received - Otrzymane - - - - Moved - Przeniesione - - - - Sent - Wysłane - - - - Rejected - Odrzucona - AccountPageListItem @@ -123,7 +93,7 @@ Seed format - Seed format + Format seeda @@ -180,8 +150,8 @@ - Switches between still in use addresses and formerly used, new empty, addresses - Przełącza się pomiędzy nadal używanymi adresami i wcześniej używanymi, nowymi pustymi adresami + Switches between unused and used Bitcoin addresses + Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami @@ -204,15 +174,25 @@ Ukryj w trybie prywatnym - + Unarchive Wallet Przywróć portfel - + Archive Wallet Zarchiwizuj portfel + + + Re-scan Chain + Skanuj ponownie + + + + Remove Wallet + Usuń portfel + AccountSelectorPopup @@ -240,7 +220,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -306,10 +286,10 @@ Explore - Odkrywaj + Eksploruj - + ON Enabled. SHORT TEXT! @@ -330,7 +310,7 @@ Dark Theme - Dark Theme + Tryb ciemny @@ -361,111 +341,106 @@ Importuj portfel - - - Name - Nazwa - - - - Force Single Address - Wymuś pojedynczy adres - - - + Select import method Wybierz metodę importu - + Camera Aparat - + OR LUB - + Secret as text The seed-phrase or private key - Secret as text + Sekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Next Dalej - + Address to import Adres do zaimportowania - + + Force Single Address + Wymuś pojedynczy adres + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - - Nothing found for seed - Nothing found for seed - - - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age - Check Age + Sprawdź wiek portfela - + Nothing found for wallet - Nothing found for wallet + Nie znaleziono nic dla portfela - - + + + New Wallet Name + Nazwa nowego portfela + + + + Start Rozpocznij - + Password Hasło - - Optional - Opcjonalne + + imported wallet password + hasło do importowanego portfela - - Details - Szczegóły - - - - Lookup Details + + Discover Details online check for wallet details - Szczegóły wyszukiwania + Poznaj szczegóły - - Derivation - Derywacja + + Derivation Path + Ścieżka derywacji + + + + Nothing found for seed + Nic nie znaleziono dla klucza seed @@ -506,7 +481,7 @@ Change will come back to the imported key. Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. - Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Zeskanowanie kodu QR z włączoną natychmiastową płatnością sprawi, że w ramach ustalonego limitu transfer zostanie zrealizowany bez potwierdzenia. @@ -592,6 +567,11 @@ Change will come back to the imported key. New Bitcoin Cash Wallet Nowy portfel Bitcoin Cash + + + New HD Wallet + Nowy portfel HD + Seed-phrase based @@ -610,6 +590,26 @@ Change will come back to the imported key. The most compatible wallet type Najbardziej kompatybilny + + + Import Existing Wallet + Importuj istniejący portfel + + + + Imports seed-phrase + Importuje seeda + + + + Imports private key + Importuje klucz prywatny + + + + New Basic Wallet + Nowy portfel podstawowy + Private keys based @@ -628,31 +628,6 @@ Change will come back to the imported key. Context: wallet type Świetny do krótkotrwałego użytku - - - Import Existing Wallet - Importuj istniejący portfel - - - - New HD Wallet - Nowy portfel HD - - - - Imports seed-phrase - Importuje seeda - - - - Imports private key - Importuje klucz prywatny - - - - New Basic Wallet - Nowy portfel podstawowy - New Wallet @@ -661,7 +636,7 @@ Change will come back to the imported key. This creates a new empty wallet with simple multi-address capability - Tworzy nowy, pusty portfel jedno- lub wieloadresowy + Tworzy nowy pusty portfel z prostą funkcją wieloadresową @@ -695,7 +670,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego This creates a new wallet that can be backed up with a seed-phrase - Tworzy to nowy portfel, który może być zachowany przy pomocy frazy seeda + Tworzy nowy portfel, który może być zachowany przy pomocy frazy seeda @@ -759,7 +734,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoAdres docelowy - + Unlock Wallet Odblokuj portfel @@ -767,25 +742,25 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH to: %1 - + 7d 7 days 7d - + 1m 1 month 1m - + 3m 3 months 3m @@ -848,7 +823,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Payment Seen - Płatność Wysłana + Płatność zaobserwowana @@ -857,8 +832,8 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - Transaction high risk - Transakcja o wysokim poziomie ryzyka + High risk transaction + Transakcja o wysokim ryzyku @@ -967,6 +942,11 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoAdd a different wallet Dodaj inny portfel + + + Claim a Cash Stamp + Odbierz Cash Stamp + TransactionDetails @@ -988,7 +968,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego First Seen - First Seen + Zaobserwowano @@ -1140,6 +1120,39 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSzczegóły transakcji + + TransactionListItem + + + Miner Reward + Nagroda dla górnika + + + + Fused + Fused + + + + Received + Otrzymano + + + + Moved + Przeniesiono + + + + Sent + Wysłano + + + + Rejected + Odrzucono + + UnlockWidget diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index b069637..1cf5f5c 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -29,5 +29,9 @@ module-peers-view_pt.qm module-peers-view_ha.qm module-send-sweep_nl.qm + module-send-sweep_es.qm + module-send-sweep_de.qm + module-send-sweep_pl.qm + module-send-sweep_en.qm diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 8bc3ea6..c5c7350 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -30,13 +30,13 @@ Building Error error during build - Błąd Przy Tworzeniu + Błąd przy tworzeniu Add Payment Detail page title - Dodaj szczegóły płatności + Dodaj szczegół płatności @@ -100,30 +100,30 @@ unset indication of empty - dezaktywuj + nie ustawiono invalid address is not correct - Nieprawidłowy + nieprawidłowy Copy Address - Skopiuj Adres + Skopiuj adres Edit Destination - Zmień Odbiorcę + Edytuj Odbiorcę Send All all money in wallet - Wyślij Wszystko + Wyślij wszystko @@ -143,7 +143,7 @@ I am certain - Na Pewno + Na pewno diff --git a/translations/module-peers-view_de.ts b/translations/module-peers-view_de.ts index 75b0680..eab2298 100644 --- a/translations/module-peers-view_de.ts +++ b/translations/module-peers-view_de.ts @@ -14,53 +14,54 @@ Statistiken - + Address network address (IP) Adresse - + Start-height: %1 Starthöhe: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Öffne Verbindung - + Validating peer Validiere Knoten - + Validated Validiert - + Good Peer + A useful peer Guter Knoten - + Peer for wallet: %1 Peer für Geldbörse: %1 - + Disconnect Peer Trenne Knoten - + Ban Peer Sperre Knoten diff --git a/translations/module-peers-view_es.ts b/translations/module-peers-view_es.ts index e2ad647..b266c5a 100644 --- a/translations/module-peers-view_es.ts +++ b/translations/module-peers-view_es.ts @@ -14,53 +14,54 @@ Estadísticas - + Address network address (IP) Dirección - + Start-height: %1 Altura de inicio: %1 - + ban-score: %1 puntaje: %1 - + Opening Connection Abriendo conexión - + Validating peer Validando par - + Validated Validado - + Good Peer + A useful peer Par bueno - + Peer for wallet: %1 Par para el monedero: %1 - + Disconnect Peer Desconectar par - + Ban Peer Banear par diff --git a/translations/module-peers-view_nl.ts b/translations/module-peers-view_nl.ts index bab104d..e51dcf4 100644 --- a/translations/module-peers-view_nl.ts +++ b/translations/module-peers-view_nl.ts @@ -14,53 +14,54 @@ Statistieken - + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Openen van verbinding - + Validating peer Controleren peer - + Validated Gecontroleerd - + Good Peer + A useful peer Goede Peer - + Peer for wallet: %1 Peer voor portemonnee: %1 - + Disconnect Peer Verbreek verbinding met peer - + Ban Peer Verban Peer diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts index f17a4f7..b1ffb14 100644 --- a/translations/module-peers-view_pl.ts +++ b/translations/module-peers-view_pl.ts @@ -6,63 +6,64 @@ Peers - Parowie + Peery Statistics - Statistics + Statystyki - + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - + Opening Connection - Opening Connection + Otwieranie połączenia - + Validating peer - Validating peer + Weryfikacja peera - + Validated - Validated + Zweryfikowano - + Good Peer - Good Peer + A useful peer + Przydatny peer - + Peer for wallet: %1 Peer dla portfela: %1 - + Disconnect Peer - Disconnect Peer + Rozłącz peer - + Ban Peer - Ban Peer + Zbanuj peera @@ -70,12 +71,12 @@ Peers View - Zobacz Parów + Widok peerów This module provides a view of network servers we connect to often called 'peers'. - Ten moduł zapewnia widok serwerów sieciowych, z którymi łączymy się nazywanych 'parami'. + Ten moduł zapewnia widok serwerów sieciowych, z którymi łączymy się nazywanych 'peerami'. @@ -88,47 +89,47 @@ IP-Address Statistics - IP-Address Statistics + Statystyki adresu IP Counts - Counts + Liczniki Total found - Total found + Łącznie znaleziono Tried - Tried + Wypróbowano Misbehaving IPs - Misbehaving IPs + Nieprawidłowe adresy IP Bad - Bad + Kiepskie Banned - Banned + Zbanowane Network - Network + Sieć Pardon the Banned - Pardon the Banned + Ułaskaw Zbanowanych diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts new file mode 100644 index 0000000..4db602c --- /dev/null +++ b/translations/module-send-sweep_de.ts @@ -0,0 +1,105 @@ + + + + + SendPage + + + Sweep coins + Sweep coins + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + + + Sweeping from address: + Sweeping from address: + + + + Found %1 coins on address. + this is a simple number + + Found %1 coins on address. + Found %1 coins on address. + + + + + Ignoring %1 tokens. + Number of CashTokens + + Ignoring %1 tokens. + Ignoring %1 tokens. + + + + + Failed to understand QR + Failed to understand QR + + + + Indexer results invalid. Please try again. + Indexer results invalid. Please try again. + + + + Transfer to: + Transfer to: + + + + Sending Payment + Sende Zahlung + + + + Payment Sent + Zahlung gesendet + + + + Failed + Fehlgeschlagen + + + + Transaction rejected by network + Transaktion wurde vom Netzwerk abgelehnt + + + + The payment has been sent to: + Followed by the address + The payment has been sent to: + + + + Close + Schließen + + + + SendSweepModuleInfo + + + Sweep & Send + Sweep & Send + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Allows sweeping a paper-wallet, moving the contents to your own wallet + + + + Sweep Paper Wallet + Sweep Paper Wallet + + + diff --git a/translations/module-send-sweep_en.ts b/translations/module-send-sweep_en.ts new file mode 100644 index 0000000..05d1115 --- /dev/null +++ b/translations/module-send-sweep_en.ts @@ -0,0 +1,103 @@ + + + + + SendPage + + + Sweep coins + + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + + + + + Sweeping from address: + + + + + Found %1 coins on address. + this is a simple number + + + + + + + Ignoring %1 tokens. + Number of CashTokens + + + + + + + Failed to understand QR + + + + + Indexer results invalid. Please try again. + + + + + Transfer to: + + + + + Sending Payment + + + + + Payment Sent + + + + + Failed + + + + + Transaction rejected by network + + + + + The payment has been sent to: + Followed by the address + + + + + Close + + + + + SendSweepModuleInfo + + + Sweep & Send + + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + + + + + Sweep Paper Wallet + + + + diff --git a/translations/module-send-sweep_es.ts b/translations/module-send-sweep_es.ts new file mode 100644 index 0000000..02a70f1 --- /dev/null +++ b/translations/module-send-sweep_es.ts @@ -0,0 +1,105 @@ + + + + + SendPage + + + Sweep coins + Barrido de monedas + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Escanear QR (WIF) para encontrar fondos + + + + Sweeping from address: + Borrando desde dirección: + + + + Found %1 coins on address. + this is a simple number + + Se encontró una moneda en la dirección. + Se encontraron monedas %1 en la dirección. + + + + + Ignoring %1 tokens. + Number of CashTokens + + Ignorando un token. + Ignorando tokens %1. + + + + + Failed to understand QR + Error al entender QR + + + + Indexer results invalid. Please try again. + Resultados del indexador no válidos. Por favor, inténtelo de nuevo. + + + + Transfer to: + Transferir a: + + + + Sending Payment + Enviando Pago + + + + Payment Sent + Pago Enviado + + + + Failed + Fallido + + + + Transaction rejected by network + Transacción rechazada por la red + + + + The payment has been sent to: + Followed by the address + El pago ha sido enviado a: + + + + Close + Cerrar + + + + SendSweepModuleInfo + + + Sweep & Send + Barrido y Enviar + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Permite barrer una cartera de papel, moviendo el contenido a su propia cartera + + + + Sweep Paper Wallet + Importar billetera de papel + + + diff --git a/translations/module-send-sweep_nl.ts b/translations/module-send-sweep_nl.ts index 98a44ea..ebd4aef 100644 --- a/translations/module-send-sweep_nl.ts +++ b/translations/module-send-sweep_nl.ts @@ -6,43 +6,51 @@ Sweep coins - Sweep coins + Munten opvegen Scan QR (WIF) to find funds Please note that WIF and QR are names - Scan QR (WIF) to find funds + Scan QR (WIF) om geld te vinden Sweeping from address: - Sweeping from address: + Opvegen van adres: - - - Failed to understand QR - Failed to understand QR - - - - Indexer results invalid. Please try again. - Indexer results invalid. Please try again. - - - + + Found %1 coins on address. - Found %1 coins on address. + this is a simple number + + %1 munt gevonden op adres. + %1 munten gevonden op adres. + + + + + Ignoring %1 tokens. + Number of CashTokens + + Negeer %1 token. + Negeer %1 tokens. + - - Ignoring %1 tokens. - Ignoring %1 tokens. + + Failed to understand QR + Kon QR niet begrijpen + + + + Indexer results invalid. Please try again. + Indexeren resultaten ongeldig. Probeer het opnieuw. Transfer to: - Transfer to: + Overmaken naar: @@ -68,7 +76,7 @@ The payment has been sent to: Followed by the address - The payment has been sent to: + Betaling is verzonden naar: @@ -81,17 +89,17 @@ Sweep & Send - Sweep & Send + Opvegen & Verzenden Allows sweeping a paper-wallet, moving the contents to your own wallet - Allows sweeping a paper-wallet, moving the contents to your own wallet + Biedt het opvegen van een papieren portemonnee, en de inhoud verplaatsen naar uw eigen portemonnee Sweep Paper Wallet - Sweep Paper Wallet + Papieren portemonnee opvegen diff --git a/translations/module-send-sweep_pl.ts b/translations/module-send-sweep_pl.ts new file mode 100644 index 0000000..7427c33 --- /dev/null +++ b/translations/module-send-sweep_pl.ts @@ -0,0 +1,109 @@ + + + + + SendPage + + + Sweep coins + Zgarnij monety + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Zeskanuj QR (WIF), aby znaleźć fundusze + + + + Sweeping from address: + Zgarnianie z adresu: + + + + Found %1 coins on address. + this is a simple number + + Znaleziono %1 monetę. + Znaleziono %1 monety. + Znaleziono %1 monet. + Znaleziono %1 monety. + + + + + Ignoring %1 tokens. + Number of CashTokens + + Ignorowanie %1 tokena. + Ignorowanie %1 tokenów. + Ignorowanie %1 tokenów. + Ignorowanie %1 tokena. + + + + + Failed to understand QR + Nie udało się zrozumieć QR + + + + Indexer results invalid. Please try again. + Wyniki indeksacji są nieprawidłowe. Spróbuj ponownie. + + + + Transfer to: + Przekaż do: + + + + Sending Payment + Wysyłanie Płatności + + + + Payment Sent + Płatność Wysłana + + + + Failed + Niepowodzenie + + + + Transaction rejected by network + Transakcja odrzucona przez sieć + + + + The payment has been sent to: + Followed by the address + Płatność została wysłana do: + + + + Close + Zamknij + + + + SendSweepModuleInfo + + + Sweep & Send + Zgarnij i wyślij + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Pozwala na zgarnięcie środków z portfela papierowego poprzez przeniesienie ich do własnego portfela. + + + + Sweep Paper Wallet + Wyczyść papierowy portfel + + + -- 2.54.0 From 5a729b4da2e17629652af62b31519327cfae2b78 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 14:12:05 +0200 Subject: [PATCH 0995/1428] Ensure that the feedback instantly opens This way we don't depend on the backend, but the actual user interaction is the one that starts the process. --- guis/Flowee/BroadcastFeedback.qml | 8 ++++++++ guis/desktop/SendTransactionPane.qml | 6 +++++- guis/mobile/PayWithQR.qml | 5 ++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 02b441b..c8e9866 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -36,6 +36,12 @@ QQC2.Control { signal closeButtonPressed; property string status: "" + function start() { + background.y = 0; + background.opacity = 1; + ControlColors.applyLightSkin(root); + } + states: [ State { name: "notStarted" @@ -199,6 +205,8 @@ QQC2.Control { onClicked: { payment.reset() transactionComment.text = "" + background.opacity = 0; + background.y = background.height + 2; root.closeButtonPressed(); } } diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 992a33d..802cfe3 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -267,7 +267,10 @@ Item { && prepareButton.portfolioUsed === portfolio.current; // also make sure we prepared for the current portfolio. onCanSendChanged: setEnabled(QQC2.DialogButtonBox.Ok, canSend) onRejected: payment.reset(); - onAccepted: payment.markUserApproved(); + onAccepted: { + payment.markUserApproved(); + broadcastFeedback.start(); + } } } } @@ -281,6 +284,7 @@ Item { } Flowee.BroadcastFeedback { + id: broadcastFeedback anchors.fill: parent } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 79473f3..0a30713 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -365,7 +365,10 @@ Page { anchors.bottomMargin: 10 width: parent.width enabled: payment.isValid && payment.txPrepared - onActivated: payment.markUserApproved() + onActivated: { + payment.markUserApproved() + broadcastPage.start(); + } visible: payment.account.isDecrypted || !payment.account.needsPinToPay } -- 2.54.0 From 48566f1fa6c6edf84913778f0c07dd1e43f5cf5b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 19:53:42 +0200 Subject: [PATCH 0996/1428] Fix documentation of property --- guis/Flowee/Progressbar.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/guis/Flowee/Progressbar.qml b/guis/Flowee/Progressbar.qml index 1782ed1..b4267c1 100644 --- a/guis/Flowee/Progressbar.qml +++ b/guis/Flowee/Progressbar.qml @@ -16,7 +16,6 @@ * along with this program. If not, see . */ import QtQuick -// import Flowee.org.pay; Rectangle { id: progressbar @@ -26,7 +25,7 @@ Rectangle { border.color: palette.midlight radius: 10 - // should be calculated to be between 0 and 100 + // should be calculated to be between 0 and 1 required property double progress; Rectangle { -- 2.54.0 From 1aa3a1461106bd99ebd74947156263f639535b7f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 19:55:53 +0200 Subject: [PATCH 0997/1428] Improve downloading of list of transactions The Sweep feature needs the 'previous' transactions to build the new transaction. This shows a progress-report per-transaction as they are downloaded. Additionally this splits the incoming electron-X reply into lines before processing. --- modules/send-sweep/QMLSweepHandler.cpp | 21 +++++++++++ modules/send-sweep/QMLSweepHandler.h | 8 ++++ modules/send-sweep/SendPage.qml | 8 +++- modules/send-sweep/TransactionsFetcher.cpp | 14 +++++-- modules/send-sweep/TransactionsFetcher.h | 2 + src/ElectronXClient.cpp | 43 ++++++++++++++-------- 6 files changed, 77 insertions(+), 19 deletions(-) diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 643aa25..08ca894 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -52,6 +52,14 @@ QMLSweepHandler::QMLSweepHandler(QObject *parent) }); connect (m_fetcher, &TransactionsFetcher::finished, this, &QMLSweepHandler::startTxBuilder); + + connect (m_fetcher, &TransactionsFetcher::fetched, this, [=](int utxoCount) { + int progress = 1000; + auto f = m_fetcher; + if (f) + progress = std::round(utxoCount / (float) m_fetcher->numOutputsFound() * 1000); + setDownloadProgress(progress); + }); } QString QMLSweepHandler::privKey() const @@ -260,6 +268,19 @@ void QMLSweepHandler::startTxBuilder(const QList &r setPrepared(true); } +int QMLSweepHandler::downloadProgress() const +{ + return m_downloadProgress; +} + +void QMLSweepHandler::setDownloadProgress(int progress) +{ + if (m_downloadProgress == progress) + return; + m_downloadProgress = progress; + emit downloadProgressChanged(); +} + QString QMLSweepHandler::sweepAddress() const { return m_sweepAddress; diff --git a/modules/send-sweep/QMLSweepHandler.h b/modules/send-sweep/QMLSweepHandler.h index 3afac23..8092a09 100644 --- a/modules/send-sweep/QMLSweepHandler.h +++ b/modules/send-sweep/QMLSweepHandler.h @@ -45,6 +45,8 @@ class QMLSweepHandler : public QObject Q_PROPERTY(double sweepTotal READ sweepTotal NOTIFY sweepTotalChanged FINAL) Q_PROPERTY(int numTokensFound READ numTokensFound NOTIFY numTokensFoundChanged FINAL) Q_PROPERTY(int numOutputsFound READ numOutputsFound NOTIFY numOutputsFoundChanged FINAL) + /// progress in 0 ... 1000 + Q_PROPERTY(int downloadProgress READ downloadProgress NOTIFY downloadProgressChanged FINAL) public: QMLSweepHandler(QObject *parent = nullptr); @@ -88,6 +90,9 @@ public: QString sweepAddress() const; void setSweepAddress(const QString &newSweepAddress); + int downloadProgress() const; + void setDownloadProgress(int newDownloadProgress); + signals: void privKeyChanged(); void errorChanged(); @@ -100,6 +105,8 @@ signals: void numOutputsFoundChanged(); void sweepAddressChanged(); + void downloadProgressChanged(); + private slots: void start(); void startTxBuilder(const QList &result); @@ -121,6 +128,7 @@ private: int m_numTokensFound = 0; int m_numOutputsFound = 0; + int m_downloadProgress = 0; // from 0 to 1000 percent. }; #endif diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index daad6c5..4b54055 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -77,6 +77,12 @@ Mobile.Page { text: qsTr("Ignoring %1 tokens.", "Number of CashTokens", sweeper.numTokensFound).arg(sweeper.numTokensFound) } + Flowee.Progressbar { + width: parent.width + visible: !sweeper.prepared && sweeper.error === SweepHandler.NoError + progress: sweeper.downloadProgress / 1000 + } + Item { id: busyIndicator visible: !sweeper.prepared && sweeper.error === SweepHandler.NoError @@ -148,7 +154,7 @@ Mobile.Page { Mobile.AccountSelectorWidget { id: walletSelector visible: !portfolio.singleAccountSetup - y: 220 + y: 320 onSelectedAccountChanged: sweeper.account = selectedAccount } Flowee.Label { diff --git a/modules/send-sweep/TransactionsFetcher.cpp b/modules/send-sweep/TransactionsFetcher.cpp index 31d2741..7585a3e 100644 --- a/modules/send-sweep/TransactionsFetcher.cpp +++ b/modules/send-sweep/TransactionsFetcher.cpp @@ -127,9 +127,11 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) abort(); } out.write(txData.begin(), txData.size()); - + out.flush(); + out.close(); bool found = false; + int totalFound = 0; for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) { if (txid == i->txid) { i->filename = out.fileName(); @@ -137,11 +139,17 @@ void TransactionsFetcher::handleResponse(int id, const QJsonObject &data) // note, do NOT add a break; here. // Multiple outputs may all spend the same txid (but different output indexes). } + if (!i->filename.isEmpty()) + ++totalFound; } if (!found) logCritical(10007) << "Received a tx from server that I didn't request" << txid; - out.flush(); - out.close(); + + if (totalFound != m_utxosDownloaded) { + assert(m_utxosDownloaded < totalFound); // can only go up... + m_utxosDownloaded = totalFound; + emit fetched(m_utxosDownloaded); + } checkAllAvailable(); } diff --git a/modules/send-sweep/TransactionsFetcher.h b/modules/send-sweep/TransactionsFetcher.h index 60b0337..83654eb 100644 --- a/modules/send-sweep/TransactionsFetcher.h +++ b/modules/send-sweep/TransactionsFetcher.h @@ -46,6 +46,7 @@ public: signals: void searchComplete(); // the numCoins/numTokens have been determined void finished(const QList &result); + void fetched(int utxoCount); protected: void handshakeCompleted() override; @@ -57,6 +58,7 @@ private: int m_coinsFound = 0; int m_tokensFound = 0; + int m_utxosDownloaded = 0; int64_t m_balance = 0; QList m_outputs; diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index dbf48e1..8a695e9 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -91,21 +91,34 @@ void ElectronXClient::socketError(QAbstractSocket::SocketError error) void ElectronXClient::socketDataAvailable() { - auto data = m_electronServer->readAll(); - logDebug(10005) << QString::fromLatin1(data); - QJsonDocument doc; - doc = QJsonDocument::fromJson(data); - if (!doc.isObject()) { - m_electronServer->close(); - return; - } - const auto o = doc.object(); - const int id = o.value("id").toInt(); - if (id == 1) { - handleVersion(o); - return; - } - handleResponse(id, o); + const auto data = m_electronServer->readAll(); + int from = 0; + int to = -1; + do { + QByteArrayView line(data); + to = line.indexOf('\n', from + 1); + if (to < 0) + to = data.size() - from; + + line = line.sliced(from, to - from); + from = to + 1; + + QJsonDocument doc; + doc = QJsonDocument::fromJson(line.toByteArray()); + if (!doc.isObject()) { + logCritical(10005) << "Not valid JSON"; + m_error = true; + emit failed(140); + m_electronServer->close(); + return; + } + const auto o = doc.object(); + const int id = o.value("id").toInt(); + if (id == 1) + handleVersion(o); + else + handleResponse(id, o); + } while (from < data.size()); } void ElectronXClient::connectToServer() -- 2.54.0 From 75da4c82f8fb57ee42460494e03eacca03cc91e6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 19:58:17 +0200 Subject: [PATCH 0998/1428] Increase version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 8d17ceb..f4671e2 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="30" android:versionName="2024.10.1"> diff --git a/src/main.cpp b/src/main.cpp index 06df810..76baee2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,7 +88,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.10.0"); + qapp.setApplicationVersion("2024.10.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From a981008852b10482062cfbb081db304fc9f06fa3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 20:50:20 +0200 Subject: [PATCH 0999/1428] Fix logic --- src/ElectronXClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index 8a695e9..07c0877 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -96,9 +96,9 @@ void ElectronXClient::socketDataAvailable() int to = -1; do { QByteArrayView line(data); - to = line.indexOf('\n', from + 1); + to = line.indexOf('\n', from); if (to < 0) - to = data.size() - from; + to = data.size(); line = line.sliced(from, to - from); from = to + 1; -- 2.54.0 From 39ef51c9b952dfeed45376beea07d4e007d88f56 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 14 Oct 2024 21:30:56 +0200 Subject: [PATCH 1000/1428] Increase minimum boost version This follows the upstream flowee-libs doing the same. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7241b1..7ca4518 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ else () find_package(Qt6 COMPONENTS DBus LinguistTools) find_package(ZXing REQUIRED) endif() -find_package(Boost 1.67.0 REQUIRED filesystem chrono thread) +find_package(Boost 1.78.0 REQUIRED filesystem chrono thread) include_directories(${Boost_INCLUDE_DIRS}) function(download_file url path) -- 2.54.0 From 6bf8bd109046b42a86b3c2f99fdf597169967ea2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 11:50:07 +0200 Subject: [PATCH 1001/1428] Make discovery work with more services. Use a buffer to store the json in, allowing us to process the result even if it is much larger than expected. Seems there are a lot of onion servers, which 'bloats' the result to the point that it didn't go in one callback. --- src/IndexerServices.cpp | 26 +++++++++++++++++++++----- src/IndexerServices_p.h | 2 ++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index fda2a07..4cb66ef 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -151,7 +151,7 @@ void IndexerServices::save() out.close(); std::filesystem::rename(filebaseStr + "~", filebaseStr); } catch (const std::exception &e) { - logFatal(10006) << "Failed to create electrum.dat file. Permissions issue?" << e; + logCritical(10006) << "Failed to create electrum.dat file. Permissions issue?" << e; } } @@ -290,8 +290,10 @@ void IndexerServices::maybeFindServices() FetchIndexerServicePeers::FetchIndexerServicePeers(const QString &server, QObject *parent) : QObject(parent), - m_remote(new QTcpSocket(this)) + m_remote(new QTcpSocket(this)), + m_pool(100000) // 100KB for the peers.subscribe should be enough, no? { + logDebug(10006) << "Connect to host:" << server; m_remote->connectToHost(server, 50001); connect (m_remote, SIGNAL(connected()), this, SLOT(connectionEstablished())); @@ -307,6 +309,7 @@ std::deque FetchIndexerServicePeers::servicesFound() c void FetchIndexerServicePeers::connectionEstablished() { + logDebug(10006) << "ConnectionEstablished"; QString call("{\"jsonrpc\":\"2.0\"," "\"method\":\"server.peers.subscribe\"," "\"params\":[], \"id\": 314}"); @@ -327,9 +330,21 @@ void FetchIndexerServicePeers::socketError(QAbstractSocket::SocketError error) void FetchIndexerServicePeers::socketDataAvailable() { - auto data = m_remote->readAll(); + auto length = m_remote->read(m_pool.data(), m_pool.capacity()); + const bool isFullMessage = m_pool.data()[length - 1] == '\n'; + m_pool.markUsed(length); + if (!isFullMessage) { + if (m_pool.capacity() < 100) { // message too big. + m_remote->close(); + m_failScore = 500; + emit finished(); + } + return; + } + + auto data = m_pool.commit(); QJsonDocument doc; - doc = QJsonDocument::fromJson(data); + doc = QJsonDocument::fromJson(QByteArray(data.begin(), data.size())); if (!doc.isObject()) { m_remote->close(); m_failScore = 500; // invalid json @@ -367,7 +382,7 @@ void FetchIndexerServicePeers::socketDataAvailable() auto versionNumbers = dat.mid(1).split('.'); while (versionNumbers.size() < 3) versionNumbers.append("0"); if (versionNumbers.size() < 5) { - for (const auto &ver : versionNumbers) { + for (const auto &ver : std::as_const(versionNumbers)) { uint32_t intValue = ver.toInt(); indexer.protocolVersion = indexer.protocolVersion << 8; if (intValue < 256) @@ -393,6 +408,7 @@ void FetchIndexerServicePeers::socketDataAvailable() Q_UNUSED(ip); // the above throws if not a real IP (but an onion for instance) so we filter our // input based on that simple metric. + logDebug(10006) << "Received an address"; newServices.push_back(indexer); } catch (...) {} } diff --git a/src/IndexerServices_p.h b/src/IndexerServices_p.h index 4167dc8..b8899a9 100644 --- a/src/IndexerServices_p.h +++ b/src/IndexerServices_p.h @@ -22,6 +22,7 @@ #include "IndexerServices.h" #include #include +#include class FetchIndexerServicePeers : public QObject { @@ -46,6 +47,7 @@ private: QTcpSocket *m_remote; std::deque m_result; int m_failScore = 0; + Streaming::BufferPool m_pool; }; #endif -- 2.54.0 From e4d5b1653837742fcb425e3bc73c716e7b70c8ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 14:25:54 +0200 Subject: [PATCH 1002/1428] Be more agressive in dealing with bad EC peers --- src/IndexerServices.cpp | 31 +++++++++++++++++++++++++++---- src/IndexerServices_p.h | 3 +++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index 4cb66ef..d631a9e 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -249,7 +249,8 @@ void IndexerServices::maybeFindServices() m_fetcher = new FetchIndexerServicePeers(known.ip, this); connect (m_fetcher, &FetchIndexerServicePeers::finished, this, [=]() { QMutexLocker locker(&m_mutex); - assert(m_fetcher); + if (!m_fetcher) + return; if (m_fetcher->failScore()) { // the remote didn't follow the protocol properly, down prioritize. for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { if (iter->ip == known.ip) { @@ -293,13 +294,16 @@ FetchIndexerServicePeers::FetchIndexerServicePeers(const QString &server, QObjec m_remote(new QTcpSocket(this)), m_pool(100000) // 100KB for the peers.subscribe should be enough, no? { - logDebug(10006) << "Connect to host:" << server; - m_remote->connectToHost(server, 50001); - connect (m_remote, SIGNAL(connected()), this, SLOT(connectionEstablished())); connect (m_remote, SIGNAL(disconnected()), this, SLOT(disconnected())); connect (m_remote, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); connect (m_remote, SIGNAL(readyRead()), this, SLOT(socketDataAvailable())); + + logDebug(10006) << "Connect to host:" << server; + m_remote->connectToHost(server, 50001); + + // watchdog timer. + QTimer::singleShot(4000, this, SLOT(timeout())); } std::deque FetchIndexerServicePeers::servicesFound() const @@ -321,11 +325,15 @@ void FetchIndexerServicePeers::connectionEstablished() void FetchIndexerServicePeers::disconnected() { logFatal(10006) << "disconnected"; + emit finished(); } void FetchIndexerServicePeers::socketError(QAbstractSocket::SocketError error) { logFatal(10006) << "Peer gave error:" << error; + m_remote->close(); + m_failScore = 100; + emit finished(); } void FetchIndexerServicePeers::socketDataAvailable() @@ -419,6 +427,21 @@ void FetchIndexerServicePeers::socketDataAvailable() } } +void FetchIndexerServicePeers::timeout() +{ + if (m_remote->state() == QAbstractSocket::HostLookupState) { + // DNS slowness. + // nothing specific to our remote. + } + else if (m_remote->state() == QAbstractSocket::ConnectingState) { + logCritical(10006) << "Watchdog; simple connect too slow, remote unavailable"; + // ok, it should really have finished connecting already. + m_failScore = 2; // remote may simply not be listening + m_remote->close(); + emit finished(); + } +} + int FetchIndexerServicePeers::failScore() const { return m_failScore; diff --git a/src/IndexerServices_p.h b/src/IndexerServices_p.h index b8899a9..f643669 100644 --- a/src/IndexerServices_p.h +++ b/src/IndexerServices_p.h @@ -43,6 +43,9 @@ private slots: void socketError(QAbstractSocket::SocketError error); void socketDataAvailable(); + // if the remote doesn't respond or is just slow. + void timeout(); + private: QTcpSocket *m_remote; std::deque m_result; -- 2.54.0 From 3b0398dd24291a8cce94fffc3b919ebd7cb78403 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 14:40:18 +0200 Subject: [PATCH 1003/1428] Make small amounts of punisment count. The fetching of a good peer now prioritizes the peers that have the least punishment. --- src/IndexerServices.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index d631a9e..d6c50db 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -168,9 +168,20 @@ EndPoint IndexerServices::service() const } } if (!eligible.empty()) { - int index = GetRand(eligible.size()); - assert((int) eligible.size() > index); - const auto &result = eligible.at(index); + int best = -1; + // try 5 random ones in order to avoid picking one with a worse punishment. + for (int attempt = 0; attempt < 5; ++attempt) { + int index = GetRand(eligible.size()); + if (best == -1) { + best = index; + } else if (index != best) { + const auto &a = eligible.at(best); + const auto &b = eligible.at(index); + if (b.punishment < a.punishment) + best = index; + } + } + const auto &result = eligible.at(best); ep.hostname = result.hostname.toStdString(); ep.announcePort = result.securePort; ep.peerPort = result.securePort; -- 2.54.0 From d7f11b40d48235942f43651c4dbfde024a2b7edc Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 15:07:27 +0200 Subject: [PATCH 1004/1428] Add watchdog here too. As electroncash.de showed, we need a timeout watchdog for cases like the server being badly setup. (connect on ipv6 never receives any answer). --- src/ElectronXClient.cpp | 18 ++++++++++++++++++ src/ElectronXClient.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/ElectronXClient.cpp b/src/ElectronXClient.cpp index 07c0877..07deb86 100644 --- a/src/ElectronXClient.cpp +++ b/src/ElectronXClient.cpp @@ -20,6 +20,7 @@ #include #include #include +#include constexpr const char *USERAGENT = "net/useragent"; @@ -121,11 +122,28 @@ void ElectronXClient::socketDataAvailable() } while (from < data.size()); } +void ElectronXClient::watchdogTimeout() +{ + if (m_electronServer->state() == QAbstractSocket::HostLookupState) { + // DNS slowness. + // nothing specific to our remote. No point in reporting an error + } + else if (m_electronServer->state() == QAbstractSocket::ConnectingState) { + logCritical(10005) << "ElectronXClient watchdog; connect too slow, remote unavailable"; + // ok, it should really have finished connecting already. + m_electronServer->close(); + m_error = true; + emit failed(2); + } +} + void ElectronXClient::connectToServer() { logCritical(10005) << "ElectronXClient connecting to" << m_serviceAddress; m_electronServer->connectToHostEncrypted( QString::fromStdString(m_serviceAddress.hostname), m_serviceAddress.announcePort); + + QTimer::singleShot(4000, this, SLOT(watchdogTimeout())); } diff --git a/src/ElectronXClient.h b/src/ElectronXClient.h index cca0755..f2e79d7 100644 --- a/src/ElectronXClient.h +++ b/src/ElectronXClient.h @@ -48,6 +48,8 @@ private slots: void socketError(QAbstractSocket::SocketError error); void socketDataAvailable(); + void watchdogTimeout(); + protected: virtual void handshakeCompleted() = 0; virtual void handleResponse(int id, const QJsonObject &data) = 0; -- 2.54.0 From c2e838d1ef2e210264c4cd065d3901e2949fd38b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 15:08:46 +0200 Subject: [PATCH 1005/1428] Remove GUI debug logging. --- guis/desktop/NewAccountImportAccount.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 00117b8..bbde1a1 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -237,7 +237,6 @@ Item { isMainButton: true; onClicked: { -console.log("start it"); // setting new values here will start the check. privKeyImportHelper.secretType = Wallet.PrivateKey privKeyImportHelper.secret = secretText.text @@ -245,7 +244,6 @@ console.log("start it"); ImportHelper { id: privKeyImportHelper onCheckingChanged: { -console.log("checking: " + checking); if (checking) return; emptyPrivKeyWarningLabel.visible = false; -- 2.54.0 From 3ead4b6100d17048663171601eafe788c894493e Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 15:50:06 +0200 Subject: [PATCH 1006/1428] Update default wallet on 'replace'. When importing a new wallet while the initial wallet still is not user- owned, this new wallet effectively replaces the old one. This means that we still are a single-account setup, even though there are two wallets. In that usecase we should point to the new one. --- src/PortfolioDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PortfolioDataProvider.cpp b/src/PortfolioDataProvider.cpp index 7f8fa58..8427d6c 100644 --- a/src/PortfolioDataProvider.cpp +++ b/src/PortfolioDataProvider.cpp @@ -55,7 +55,7 @@ PortfolioDataProvider::PortfolioDataProvider(QObject *parent) : QObject(parent) updateIsSingleAccount(); emit accountsChanged(); - if (m_currentAccount > m_accounts.size()) + if (m_currentAccount > m_accounts.size() || m_isSingleAccount) selectDefaultWallet(); }); connect (app, SIGNAL(totalBalanceConfigChanged()), this, SIGNAL(totalBalanceChanged())); -- 2.54.0 From 2e146593716aabea2f7461fa9d316c0467f575ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Oct 2024 15:50:29 +0200 Subject: [PATCH 1007/1428] Move to the first tab on finishing import. --- guis/desktop/NewAccountImportAccount.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index bbde1a1..98b1007 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -457,6 +457,7 @@ Item { } } newAccountsPane.visible = false; + tabbar.currentIndex = 0; } } -- 2.54.0 From 1b5e2e4bf5eab8d7c2372ef2524d7e1f8200094c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Oct 2024 22:59:55 +0200 Subject: [PATCH 1008/1428] UX: only show 'sent to' for outgoing transactions. --- guis/mobile/TransactionInfoSmall.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 997890b..55094e0 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -123,7 +123,13 @@ ColumnLayout { id: receiverName Layout.alignment: Qt.AlignRight visible: text !== "" - text: infoObject == null ? "" : infoObject.receiver + text: { + if (infoObject == null) + return ""; + if (model.fundsIn === 0) + return ""; // skip showing this for 'received' payments. + return infoObject.receiver; + } font.pixelSize: paymentTypeLabel.font.pixelSize * 0.8 } -- 2.54.0 From f0c250e71b3b3b44ff8a1ccc66b509a3ab2e4476 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Oct 2024 23:04:13 +0200 Subject: [PATCH 1009/1428] Fix unneeded COW --- src/ModuleInfo.cpp | 2 +- src/ModuleInfo.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 2ff1957..089cd42 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -65,7 +65,7 @@ void ModuleInfo::setDescription(const QString &newDescription) m_description = newDescription; } -QList ModuleInfo::sections() const +const QList &ModuleInfo::sections() const { return m_sections; } diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index f48632a..1009bc8 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -67,7 +67,7 @@ public: * Sections define where a module is used, add them to "plug" your module into the UI */ void addSection(ModuleSection *section); - QList sections() const; + const QList §ions() const; bool enabled() const; -- 2.54.0 From 9f69241bbb574192dee57f4dc182e36248da9fe1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Oct 2024 23:09:20 +0200 Subject: [PATCH 1010/1428] API level rename of 'send' to 'action' As we get more modules it turns out that the 'send' tab as originally envisioned isn't really representative of how we're evolving. Various items end up being about doing 'stuff' in general. Including creating a transaction to receive. Only in a very loose way can we say those are 'send' items. So, without actually any user-visible changes, this renames the enum in the module manager and module-section to make it about the more accurate "action menu". --- guis/mobile/ExploreModules.qml | 2 +- guis/mobile/StartupScreen.qml | 2 +- modules/build-transaction/BuildTransactionModuleInfo.cpp | 2 +- modules/example/ExampleModuleInfo.cpp | 2 +- modules/send-sweep/SendSweepModuleInfo.cpp | 2 +- src/ModuleInfo.cpp | 2 +- src/ModuleManager.cpp | 4 ++-- src/ModuleSection.h | 8 ++++---- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index e1193b6..559779a 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -160,7 +160,7 @@ Page { property QtObject section: { // find the section our icon represents. for (let s of modelData.sections) { - if (s.isSendMethod) + if (s.isActionTabItem) return s; } return null; // module doesn't have such a section. diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index ab82eda..a18bf55 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -190,7 +190,7 @@ Page { for (var mod of ModuleManager.registeredModules) { if (mod.id === "sendSweepModule") { for (var section of mod.sections) { - if (section.isSendMethod) { + if (section.isActionTabItem) { if (section.enabled) return section; break; diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index a24bf6e..1323c3a 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -23,7 +23,7 @@ ModuleInfo * BuildTransactionModuleInfo::build() module->setTitle(tr("Create Transactions")); module->setDescription(tr("This module allows building more powerful transactions in one simple user interface.")); - auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); + auto sendButton = new ModuleSection(ModuleSection::ActionTabItem, module); sendButton->setText(tr("Build Transaction")); // notice that the directory it is in is registered in the data.qrc header sendButton->setStartQMLFile("qrc:/build-transaction/PayToOthers.qml"); diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index cf17323..ac352ec 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -28,7 +28,7 @@ ModuleInfo * ExampleModuleInfo::build() // A section is a definition on where in the application this module is able // to be plugged-in. - auto sendButtonExample = new ModuleSection(ModuleSection::SendMethod, info); + auto sendButtonExample = new ModuleSection(ModuleSection::ActionTabItem, info); sendButtonExample->setText(tr("Example Module")); sendButtonExample->setSubtext(tr("This is some helptext")); // notice that the directory it is in, is registered in the example-data.qrc file diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index ffa9ef5..ca83420 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -27,7 +27,7 @@ ModuleInfo * SendSweepModuleInfo::build() info->setTitle(tr("Sweep & Send")); info->setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); - auto sendButton = new ModuleSection(ModuleSection::SendMethod, info); + auto sendButton = new ModuleSection(ModuleSection::ActionTabItem, info); sendButton->setText(tr("Sweep Paper Wallet")); sendButton->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); info->addSection(sendButton); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 089cd42..0c5686b 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -89,7 +89,7 @@ bool ModuleInfo::hasUISections() const for (auto section : m_sections) { if (section->isMainMenuMethod()) return true; - if (section->isSendMethod()) + if (section->isActionTabItem()) return true; } return false; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 34ff7a0..6debe82 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -87,7 +87,7 @@ ModuleManager::ModuleManager(QObject *parent) for (auto *s : m->sections()) { connect (s, &ModuleSection::enabledChanged, this, [=]() { switch (s->type()) { - case ModuleSection::SendMethod: + case ModuleSection::ActionTabItem: emit sendMenuSectionsChanged(); case ModuleSection::MainMenuItem: emit mainMenuSectionsChanged(); @@ -243,7 +243,7 @@ QList ModuleManager::sendMenuSections() const QList answer; for (const auto *m : m_modules) { for (auto *s : m->sections()) { - if (s->enabled() && s->type() == ModuleSection::SendMethod) + if (s->enabled() && s->type() == ModuleSection::ActionTabItem) answer.append(s); } } diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 415ac85..ea7ef97 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -33,13 +33,13 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) - Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) + Q_PROPERTY(bool isActionTabItem READ isActionTabItem CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: /// The placement in the main app of this section. enum SectionType { - SendMethod, ///< A specific way to send coin, shown in list of send-methods. + ActionTabItem, ///< An list-item on the 'action' tab (aka send-tab). MainMenuItem, ///< A text-button in the main menu CustomSectionType, ///< Not normally shown type, but fetchable by id. @@ -73,8 +73,8 @@ public: QString startQMLFile() const; SectionType type() const; - bool isSendMethod() const { - return m_type == SendMethod; + bool isActionTabItem() const { + return m_type == ActionTabItem; } bool isMainMenuMethod() const { return m_type == MainMenuItem; -- 2.54.0 From d8e082fed11056489c483e52b676002821b2888e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Oct 2024 23:36:09 +0200 Subject: [PATCH 1011/1428] Simplify: don't import local dir --- guis/Flowee/TextPasteDecorator.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/TextPasteDecorator.qml b/guis/Flowee/TextPasteDecorator.qml index e1d53f3..747ef54 100644 --- a/guis/Flowee/TextPasteDecorator.qml +++ b/guis/Flowee/TextPasteDecorator.qml @@ -16,7 +16,6 @@ * along with this program. If not, see . */ import QtQuick -import "../Flowee" as Flowee import Flowee.org.pay /* @@ -83,7 +82,7 @@ Item { anchors.verticalCenter: parent.verticalCenter } - Flowee.Label { + Label { id: labelText anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right @@ -104,6 +103,6 @@ Item { } } - Flowee.ObjectShaker { id: shaker } + ObjectShaker { id: shaker } } } -- 2.54.0 From 148cbadcd5e68e2a4257ff53d8223e1bdf4543d3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Oct 2024 23:39:13 +0200 Subject: [PATCH 1012/1428] UX: make 'paste' button always be there To amke it more clear that the QR scanning screen also always allows clipboard input, make the paste button always be there and just make it have a constrasting background when the clipboard content is acceptable. We also add a 'shake' feedback when the paste doesn't work, and naturall the paste button is available for clicking even if it doesn't look 'enabled' which will then just result in a shake and 'failed' feedback. --- guis/Flowee/QRScanner.qml | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index ccd85df..059a6a9 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -109,21 +109,39 @@ FocusScope { } } - Rectangle { + Item { id: pasteFrame x: 50 anchors.top: topBar.bottom anchors.topMargin: 6 - visible: cbh.text !== "" - radius: 6 width: pasteButton.width height: pasteButton.height - color: palette.base + + Rectangle { + // hiding this rect has a great effect in + // light mode to make paste look disabled. + color: palette.base + anchors.fill: parent + visible: cbh.text !== "" + radius: 6 + } ImageButton { id: pasteButton source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); text: qsTr("Paste") - onClicked: pasteFeedback.visible = !CameraController.pasteData(cbh.text); + opacity: { + // in dark mode the contrast is too great when the button + // should look disabled. So we lower it when needed. + if (Pay.useDarkSkin && cbh.text === "") + return 0.5; + return 1; + } + onClicked: { + var err = pasteFeedback.visible = !CameraController.pasteData(cbh.text); + pasteFeedback.visible = err; + if (err) + shaker.start(); + } } ClipboardHelper { @@ -143,7 +161,7 @@ FocusScope { Rectangle { id: pasteFeedback - color: palette.toolTipBase + color: mainWindow.errorRedBg border.color: palette.toolTipText border.width: 2 width: errorLabel.width + 10 @@ -156,15 +174,19 @@ FocusScope { id: errorLabel anchors.centerIn: parent text: qsTr("Failed") - color: palette.toolTipText } Timer { - interval: 4000 + interval: 7000 running: parent.visible onTriggered: parent.visible = false } } + ObjectShaker { + id: shaker + target: pasteButton + } } + Rectangle { id: flashFrame anchors.top: pasteFrame.top -- 2.54.0 From ffb85bb403a87dfd3e272f1f939140df66b50e7b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 20 Oct 2024 21:21:05 +0200 Subject: [PATCH 1013/1428] Add ability to filter history On the mobile client this allows the user to check per wallet which kind of transactions to show or hide. We allow selection of send/receive/both. We allow the hiding of cash-fusion transactions. --- guis/mobile/AccountHistory.qml | 38 +++++++++ guis/mobile/FilterPopup.qml | 137 +++++++++++++++++++++++++++++++++ guis/mobile/PopupOverlay.qml | 4 +- src/WalletEnums.h | 5 +- src/WalletHistoryModel.cpp | 6 ++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 guis/mobile/FilterPopup.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 3aa9c57..b378fae 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -171,6 +171,7 @@ ListView { section.property: "grouping" section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart section.delegate: Item { + id: delegateRoot height: label.height + 15 width: root.width Rectangle { @@ -185,6 +186,43 @@ ListView { font.pixelSize: mainWindow.font.pixelSize * 1.1 text: portfolio.current.transactions.groupingPeriod(section); } + MouseArea { anchors.fill: parent } // eat all taps + + property bool isTopSection: { + var dummy = y; // ensure recalc on move + var delegateInListCoords = mapToItem(root, 0, 0); + if (delegateInListCoords.y < 50) + return true; + + var headerItem = root.headerItem; + if (headerItem == null) + return false; + + // p2 = header-item bottom in list-view coords. + var p2 = headerItem.mapToItem(root, 0, headerItem.height); + return p2.y + 10 - delegateInListCoords.y > 0; + } + Rectangle { + color: "blue" + height: parent.height + width: 50 + anchors.right: parent.right + anchors.rightMargin: 70 + opacity: isTopSection ? 1 : 0 + visible: opacity > 0 + Behavior on opacity { NumberAnimation { } } + + MouseArea { + anchors.fill: parent + enabled: isTopSection + onClicked: popupOverlay.open(filterPopup, this) + } + + Component { + id: filterPopup + FilterPopup { } + } + } } delegate: Item { id: transactionDelegate diff --git a/guis/mobile/FilterPopup.qml b/guis/mobile/FilterPopup.qml new file mode 100644 index 0000000..2b9bf74 --- /dev/null +++ b/guis/mobile/FilterPopup.qml @@ -0,0 +1,137 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import "../Flowee" as Flowee +import Flowee.org.pay; + +// the widget that shows up on the AccountHistory +// allowing one to change the timeline filters +Item { + implicitHeight: coreColumn.height + + function toggleFlag(flag, on) { + var cf = portfolio.current.transactions.includeFlags + if (((cf & flag) > 0) === on) // nothing to do. + return; + if (on) + cf += flag; + else + cf -= flag; + portfolio.current.transactions.includeFlags = cf + } + + Column { + id: coreColumn + spacing: 10 + Flowee.Label { + text: qsTr("Filter") + } + width: parent.width + Flowee.CheckBox { + width: parent.width + text: "Show Anonimity Transactions" + checked: portfolio.current.transactions.includeFlags & Wallet.IncludeCFs > 0 + onToggled: toggleFlag(Wallet.IncludeCFs, checked); + } + Item { + width: parent.width + height: 100 + Rectangle { + width: 60 + height: 60 + radius: 30 + x: 10 + Flowee.Label { + anchors.centerIn: parent + text: "S + R" + } + border.width: 3 + border.color: palette.highlight + property bool checked: { + var flags = portfolio.current.transactions.includeFlags; + var wanted = Wallet.IncludeSentTransactions + | Wallet.IncludeReceivedTransactions; + return (flags & wanted) === wanted; + } + color: checked ? palette.midlight : "#00000000" + MouseArea { + anchors.fill: parent + enabled: !parent.checked + onClicked: { + toggleFlag(Wallet.IncludeSentTransactions, true); + toggleFlag(Wallet.IncludeReceivedTransactions, true); + } + } + } + Rectangle { + width: 60 + height: 60 + radius: 30 + x: 80 + Flowee.Label { + anchors.centerIn: parent + text: "R" + } + border.width: 3 + border.color: palette.highlight + property bool checked: { + var flags = portfolio.current.transactions.includeFlags; + var filter = Wallet.IncludeSentTransactions + | Wallet.IncludeReceivedTransactions; + return (flags & filter) === Wallet.IncludeReceivedTransactions; + } + color: checked ? palette.midlight : "#00000000" + MouseArea { + anchors.fill: parent + enabled: !parent.checked + onClicked: { + toggleFlag(Wallet.IncludeSentTransactions, false); + toggleFlag(Wallet.IncludeReceivedTransactions, true); + } + } + } + Rectangle { + width: 60 + height: 60 + radius: 30 + x: 150 + Flowee.Label { + anchors.centerIn: parent + text: "S" + } + border.width: 3 + border.color: palette.highlight + property bool checked: { + var flags = portfolio.current.transactions.includeFlags; + var filter = Wallet.IncludeSentTransactions + | Wallet.IncludeReceivedTransactions; + return (flags & filter) === Wallet.IncludeSentTransactions; + } + color: checked ? palette.midlight : "#00000000" + MouseArea { + anchors.fill: parent + enabled: !parent.checked + onClicked: { + toggleFlag(Wallet.IncludeSentTransactions, true); + toggleFlag(Wallet.IncludeReceivedTransactions, false); + } + } + } + } + } +} diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index a6d90ac..fdb66b2 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -32,6 +32,9 @@ FocusScope { * @param overlayComponent is a component that we place on top of the * \a 'target' item, but inside of the popup to avoid dimming. * @returns the item instance of the sourceComponent template + * + * Note, make sure that the sourceComponent sets an implicitHeight, + * which is used in the popup. */ function open(sourceComponent, target, overlayComponent) { thePopup.palette = mainWindow.palette @@ -69,7 +72,6 @@ FocusScope { id: loader width: parent.width onLoaded: { - // heightMonitor.target = item; thePopup.visible = true; root.forceActiveFocus(); } diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 1e97d7e..54d2f3a 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -48,8 +48,11 @@ public: IncludeRejected = 1, IncludeUnconfirmed = 2, IncludeConfirmed = 4, + IncludeCFs = 8, + IncludeSentTransactions = 0x10, + IncludeReceivedTransactions = 0x20, - IncludeAll = IncludeRejected | IncludeUnconfirmed | IncludeConfirmed + IncludeAll = 0x3f }; Q_DECLARE_FLAGS(Includes, Include) Q_FLAG(Includes) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 982aa23..4d17496 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -389,10 +389,16 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) assert(QThread::currentThread() == thread()); if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeCFs) && wtx.isCashFusionTx) + return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) && wtx.isRejected()) return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) && !wtx.isUnconfirmed()) return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeSentTransactions) && !wtx.inputToWTX.empty()) + return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions) && wtx.inputToWTX.empty()) + return false; return true; } -- 2.54.0 From 871e5b1abd32330cd97ea366cfc449f7facaf5ef Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Oct 2024 13:28:00 +0200 Subject: [PATCH 1014/1428] Make the filter-popup UX nice. This also makes us hide the cf check on a wallet that doesn't have any. This also adds a checkbox to filter on transactions without comment --- guis/mobile/FilterPopup.qml | 140 +++++++++++++++++++++++++----------- src/AccountInfo.cpp | 5 ++ src/AccountInfo.h | 6 ++ src/Wallet.cpp | 9 ++- src/Wallet.h | 5 ++ src/WalletEnums.h | 3 +- src/WalletHistoryModel.cpp | 2 + 7 files changed, 128 insertions(+), 42 deletions(-) diff --git a/guis/mobile/FilterPopup.qml b/guis/mobile/FilterPopup.qml index 2b9bf74..907c7fc 100644 --- a/guis/mobile/FilterPopup.qml +++ b/guis/mobile/FilterPopup.qml @@ -16,13 +16,15 @@ * along with this program. If not, see . */ import QtQuick +import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; // the widget that shows up on the AccountHistory // allowing one to change the timeline filters Item { - implicitHeight: coreColumn.height + implicitHeight: coreColumn.height + 10 + width: parent.width function toggleFlag(flag, on) { var cf = portfolio.current.transactions.includeFlags @@ -35,40 +37,51 @@ Item { portfolio.current.transactions.includeFlags = cf } - Column { + ColumnLayout { id: coreColumn + width: parent.width spacing: 10 Flowee.Label { - text: qsTr("Filter") - } - width: parent.width - Flowee.CheckBox { - width: parent.width - text: "Show Anonimity Transactions" - checked: portfolio.current.transactions.includeFlags & Wallet.IncludeCFs > 0 - onToggled: toggleFlag(Wallet.IncludeCFs, checked); + text: qsTr("Filter Transactions") } Item { width: parent.width - height: 100 + implicitHeight: 80 Rectangle { - width: 60 - height: 60 - radius: 30 + width: 80 + height: 80 + radius: 40 x: 10 - Flowee.Label { - anchors.centerIn: parent - text: "S + R" - } + border.width: 3 - border.color: palette.highlight + color: checked ? palette.alternateBase : palette.base + border.color: checked ? palette.midlight : palette.mid + Image { + source: "qrc:/tx-send" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 35 + height: 35 + smooth: true + x: 35 + y: 9 + opacity: parent.checked ? 1 : 0.5 + } + Image { + source: "qrc:/tx-receiving" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 35 + height: 35 + smooth: true + y: 36 + x: 10 + opacity: parent.checked ? 1 : 0.5 + } + + property bool checked: { var flags = portfolio.current.transactions.includeFlags; var wanted = Wallet.IncludeSentTransactions | Wallet.IncludeReceivedTransactions; return (flags & wanted) === wanted; } - color: checked ? palette.midlight : "#00000000" MouseArea { anchors.fill: parent enabled: !parent.checked @@ -79,23 +92,30 @@ Item { } } Rectangle { - width: 60 - height: 60 - radius: 30 - x: 80 - Flowee.Label { - anchors.centerIn: parent - text: "R" - } + width: 80 + height: 80 + radius: 40 + x: 100 + border.width: 3 - border.color: palette.highlight + color: checked ? palette.alternateBase : palette.base + border.color: checked ? palette.midlight : palette.mid + + Image { + source: "qrc:/tx-receiving" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 50 + height: 50 + smooth: true + y: 11 + x: 12 + opacity: parent.checked ? 1 : 0.5 + } property bool checked: { var flags = portfolio.current.transactions.includeFlags; var filter = Wallet.IncludeSentTransactions | Wallet.IncludeReceivedTransactions; return (flags & filter) === Wallet.IncludeReceivedTransactions; } - color: checked ? palette.midlight : "#00000000" MouseArea { anchors.fill: parent enabled: !parent.checked @@ -106,23 +126,30 @@ Item { } } Rectangle { - width: 60 - height: 60 - radius: 30 - x: 150 - Flowee.Label { - anchors.centerIn: parent - text: "S" - } + width: 80 + height: 80 + radius: 40 + x: 190 + border.width: 3 - border.color: palette.highlight + color: checked ? palette.alternateBase : palette.base + border.color: checked ? palette.midlight : palette.mid + + Image { + source: "qrc:/tx-send" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 50 + height: 50 + smooth: true + x: 15 + y: 11 + opacity: parent.checked ? 1 : 0.5 + } property bool checked: { var flags = portfolio.current.transactions.includeFlags; var filter = Wallet.IncludeSentTransactions | Wallet.IncludeReceivedTransactions; return (flags & filter) === Wallet.IncludeSentTransactions; } - color: checked ? palette.midlight : "#00000000" MouseArea { anchors.fill: parent enabled: !parent.checked @@ -133,5 +160,38 @@ Item { } } } + Item { width: 1; height: 6 } // spacer + Rectangle { + id: cfCheckbox + width: 80 + height: 80 + radius: 6 + color: checked ? palette.alternateBase : palette.base + border.width: 3 + border.color: checked ? palette.midlight : palette.mid + visible: portfolio.current.hasAnonimityTransactions + + property bool checked: (portfolio.current.transactions.includeFlags + & Wallet.IncludeCFs) > 0; + + Image { + source: "qrc:/cf.svg"; + width: 60 + height: 60 + smooth: true + anchors.centerIn: parent + opacity: cfCheckbox.checked ? 1 : 0.5 + } + MouseArea { + anchors.fill: parent + onClicked: toggleFlag(Wallet.IncludeCFs, !cfCheckbox.checked); + } + } + Flowee.CheckBox { + width: parent.width + text: qsTr("Has a comment", "This is a statement about a transaction"); + checked: (portfolio.current.transactions.includeFlags & Wallet.IncludeComments) > 0 + onToggled: toggleFlag(Wallet.IncludeComments, checked); + } } } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index a5e668a..719c2a0 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -230,6 +230,11 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +bool AccountInfo::hasAnonimityTransactions() const +{ + return m_wallet->walletStoresCashFusions(); +} + int AccountInfo::accountStartBlockHeight() const { return m_accountStartBlockHeight; diff --git a/src/AccountInfo.h b/src/AccountInfo.h index cde145f..b18c078 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -75,6 +75,8 @@ class AccountInfo : public QObject Q_PROPERTY(bool countBalance READ countBalance WRITE setCountBalance NOTIFY neverEmitted) Q_PROPERTY(bool allowInstaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY instaPayAllowedChanged) Q_PROPERTY(bool isPrivate READ isPrivate WRITE setIsPrivate NOTIFY neverEmitted) + /// wallet stores cash-fusion transactions. + Q_PROPERTY(bool hasAnonimityTransactions READ hasAnonimityTransactions NOTIFY hasAnonimityTransactionsChanged FINAL) public: AccountInfo(Wallet *wallet, QObject *parent = nullptr); @@ -190,6 +192,9 @@ public: // the first blockheight ever used for this account int accountStartBlockHeight() const; + bool hasAnonimityTransactions() const; + void setHasAnonimityTransactions(bool newHasAnonimityTransactions); + signals: void balanceChanged(); void utxosChanged(); @@ -206,6 +211,7 @@ signals: void instaPayLimitChanged(const QString ¤cyCode); void neverEmitted(); // to silence the lambs^Warnings void accountStartBlockHeightChanged(); + void hasAnonimityTransactionsChanged(); // for the benefit of the portfolio data provider void isPrivateChanged(); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 5c13e00..30de4b4 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -319,6 +319,11 @@ void Wallet::deriveHDKeys(int mainChain, int changeChain, uint32_t startHeight) saveSecrets(); } +bool Wallet::walletStoresCashFusions() const +{ + return m_walletStoresCashFusions; +} + bool Wallet::walletIsImporting() const { return m_walletIsImporting; @@ -462,8 +467,10 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: ++m_bloomFilterMisses; continue; } - if (wtx.isCashFusionTx) // improve behavior of bloom filters + if (!m_walletStoresCashFusions && wtx.isCashFusionTx) {// improve behavior of bloom filters m_walletStoresCashFusions = true; + emit walletStoresCFsChanged(); + } } else { // we already seen it before. diff --git a/src/Wallet.h b/src/Wallet.h index 6c432d9..57a53b6 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -388,6 +388,10 @@ public: boost::filesystem::path walletDir() const; + /// returns if fusions were detected in this wallet. + /// see signal: walletStoresCFsChanged(); + bool walletStoresCashFusions() const; + public slots: void delayedSave(); @@ -410,6 +414,7 @@ signals: void transactionRemoved(int txIndex); void encryptionChanged(); void encryptionSeedChanged(); + void walletStoresCFsChanged(); // warning, may be emitted in other threads. // \internal void startDelayedSave(); diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 54d2f3a..ed41c0c 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -51,8 +51,9 @@ public: IncludeCFs = 8, IncludeSentTransactions = 0x10, IncludeReceivedTransactions = 0x20, + IncludeComments = 0x40, // include transactions that have a comment - IncludeAll = 0x3f + IncludeAll = 0x7f }; Q_DECLARE_FLAGS(Includes, Include) Q_FLAG(Includes) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 4d17496..2b078f0 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -399,6 +399,8 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions) && wtx.inputToWTX.empty()) return false; + if (!m_includeFlags.testFlag(WalletEnums::IncludeComments) && wtx.userComment.isEmpty()) + return false; return true; } -- 2.54.0 From 1e923a6c09523e27b51a94bd53e8d44eee85591d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Oct 2024 00:03:53 +0200 Subject: [PATCH 1015/1428] Remove old note --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 5320b42..8b270ca 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,8 @@ of the main free software product and thus this benefits all. BUILDING Flowee Pay uses libraries from Flowee, you need to -either install the main flowee package via your package manager +either install the main flowee-libs package via your package manager or compile it before you compile Pay. -The minimum version required for the Flowee libraries is 2022.07.0 For ubuntu getting the latest is a matter of calling: -- 2.54.0 From ca63943def8ec83256efc12a73909cafb5a1b3b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Oct 2024 00:03:58 +0200 Subject: [PATCH 1016/1428] Refactor, move button to open. --- guis/mobile/AccountHistory.qml | 40 ---------------------------------- guis/mobile/FilterPopup.qml | 4 ++-- guis/mobile/MainView.qml | 11 +++++++++- guis/mobile/MainViewBase.qml | 23 ++++++++++++++----- guis/mobile/PopupOverlay.qml | 7 +++++- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index b378fae..d67b5bc 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -174,10 +174,6 @@ ListView { id: delegateRoot height: label.height + 15 width: root.width - Rectangle { - color: palette.light - anchors.fill: parent - } Flowee.Label { id: label x: 10 @@ -187,42 +183,6 @@ ListView { text: portfolio.current.transactions.groupingPeriod(section); } MouseArea { anchors.fill: parent } // eat all taps - - property bool isTopSection: { - var dummy = y; // ensure recalc on move - var delegateInListCoords = mapToItem(root, 0, 0); - if (delegateInListCoords.y < 50) - return true; - - var headerItem = root.headerItem; - if (headerItem == null) - return false; - - // p2 = header-item bottom in list-view coords. - var p2 = headerItem.mapToItem(root, 0, headerItem.height); - return p2.y + 10 - delegateInListCoords.y > 0; - } - Rectangle { - color: "blue" - height: parent.height - width: 50 - anchors.right: parent.right - anchors.rightMargin: 70 - opacity: isTopSection ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { NumberAnimation { } } - - MouseArea { - anchors.fill: parent - enabled: isTopSection - onClicked: popupOverlay.open(filterPopup, this) - } - - Component { - id: filterPopup - FilterPopup { } - } - } } delegate: Item { id: transactionDelegate diff --git a/guis/mobile/FilterPopup.qml b/guis/mobile/FilterPopup.qml index 907c7fc..d033846 100644 --- a/guis/mobile/FilterPopup.qml +++ b/guis/mobile/FilterPopup.qml @@ -45,7 +45,8 @@ Item { text: qsTr("Filter Transactions") } Item { - width: parent.width + width: 80 * 3 + 20 + Layout.alignment: Qt.AlignHCenter implicitHeight: 80 Rectangle { width: 80 @@ -160,7 +161,6 @@ Item { } } } - Item { width: 1; height: 6 } // spacer Rectangle { id: cfCheckbox width: 80 diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 9b1e94f..679e6b8 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,9 +19,15 @@ import QtQuick import "../Flowee" as Flowee MainViewBase { + showFilterIcon: currentIndex === 0 + AccountHistory { anchors.fill: parent PopupOverlay { id: popupOverlay } + Component { + id: filterPopup + FilterPopup { } + } } SendTransactionsTab { anchors.fill: parent @@ -31,4 +37,7 @@ MainViewBase { anchors.leftMargin: 10 anchors.rightMargin: 10 } + + // only visible on AccountHistory for now. + onFilterIconClicked: popupOverlay.open(filterPopup, null) } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 852a97f..ffff18a 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -32,6 +32,10 @@ QQC2.Control { // This trick means any child items are actually added to the 'stack' item's children. default property alias content: stack.children property int currentIndex: 0 + property bool showFilterIcon: false + signal filterIconClicked; + + onCurrentIndexChanged: setOpacities() Component.onCompleted: setOpacities() @@ -107,7 +111,7 @@ QQC2.Control { color: "#fcfcfc" clip: true anchors.left: logo.right - anchors.right: searchIcon.left + anchors.right: filterIcon.left anchors.margins: 12 anchors.baseline: logo.baseline @@ -122,13 +126,20 @@ QQC2.Control { } } - QQC2.Label { - id: searchIcon - color: "#fcfcfc" - text: "" // placeholder for the magnifying glass search feature to be done in future + Rectangle { + id: filterIcon + color: "red" + height: 60 + width: 60 anchors.right: parent.right anchors.rightMargin: 10 - anchors.baseline: logo.baseline + visible: root.showFilterIcon + // anchors.baseline: logo.baseline + + MouseArea { + anchors.fill: parent + onClicked: root.filterIconClicked(); + } } AccountSelectorPopup { diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index fdb66b2..467e2d6 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -40,7 +40,11 @@ FocusScope { thePopup.palette = mainWindow.palette thePopup.width = root.width - 18 thePopup.x = (width - thePopup.width) / 2 - thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); + if (target === null) { + thePopup.sourceRect = Qt.rect(0, 0, 0, 0); + } else { + thePopup.sourceRect = root.mapFromItem(target, 0, 0, target.width, target.height); + } overlayLoader.sourceComponent = overlayComponent; loader.sourceComponent = sourceComponent; // last, as it starts the loading return loader.item; @@ -104,6 +108,7 @@ FocusScope { return; } + loader.y = 0; if (root.height - rect.bottom >= h) // fits below thePopup.y = rect.bottom; -- 2.54.0 From 0d6bfb35db4da913cc7e13ffd2e7830dc9265821 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Oct 2024 19:44:14 +0200 Subject: [PATCH 1017/1428] Add icon for the filter action --- guis/mobile.qrc | 1 + guis/mobile/MainViewBase.qml | 24 +++++++++++++----------- guis/mobile/images/filter-light.svg | 4 ++++ guis/mobile/images/filter.svg | 4 ++++ 4 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 guis/mobile/images/filter-light.svg create mode 100644 guis/mobile/images/filter.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index cff7083..cd4cc51 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -40,6 +40,7 @@ mobile/images/num-keyboard.svg mobile/images/module-mainmenu.svg mobile/images/module-mainmenu-light.svg + mobile/images/filter-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index ffff18a..56df4dd 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ QQC2.Control { color: palette.light } - // This trick means any child items are actually added to the 'stack' item's children. + // This trick means any child items are actually added as the 'stack' item's children. default property alias content: stack.children property int currentIndex: 0 property bool showFilterIcon: false @@ -112,7 +112,7 @@ QQC2.Control { clip: true anchors.left: logo.right anchors.right: filterIcon.left - anchors.margins: 12 + anchors.margins: 10 anchors.baseline: logo.baseline MouseArea { @@ -125,19 +125,21 @@ QQC2.Control { } } } - - Rectangle { + Image { id: filterIcon - color: "red" - height: 60 - width: 60 - anchors.right: parent.right - anchors.rightMargin: 10 + source: "qrc:/filter-light.svg" visible: root.showFilterIcon - // anchors.baseline: logo.baseline + anchors.right: parent.right + anchors.rightMargin: 6 + smooth: true + width: 25 + height: 25 + anchors.bottom: parent.bottom + anchors.bottomMargin: 9 MouseArea { anchors.fill: parent + anchors.margins: -10 onClicked: root.filterIconClicked(); } } diff --git a/guis/mobile/images/filter-light.svg b/guis/mobile/images/filter-light.svg new file mode 100644 index 0000000..38bc1b6 --- /dev/null +++ b/guis/mobile/images/filter-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/guis/mobile/images/filter.svg b/guis/mobile/images/filter.svg new file mode 100644 index 0000000..044f2de --- /dev/null +++ b/guis/mobile/images/filter.svg @@ -0,0 +1,4 @@ + + + + -- 2.54.0 From 8f220f98b79395cb3f72ee8a2081d683c10f4a00 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 00:01:10 +0200 Subject: [PATCH 1018/1428] Save the filter preferences to disk. --- src/WalletHistoryModel.cpp | 75 ++++++++++++++++++++++++++++++++++++++ src/WalletHistoryModel.h | 4 ++ 2 files changed, 79 insertions(+) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 2b078f0..73fba46 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -19,11 +19,17 @@ #include "FloweePay.h" #include +#include #include #include #include #include +#include +#include +#include +#include +#include /* * Attempt to add a transaction to this group. @@ -99,6 +105,13 @@ WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) connect(wallet, SIGNAL(transactionConfirmed(int)), SLOT(transactionChanged(int)), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionChanged(int)), SLOT(transactionChanged(int)), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionRemoved(int)), SLOT(removeTransaction(int)), Qt::QueuedConnection); + + loadPreferences(); +} + +WalletHistoryModel::~WalletHistoryModel() +{ + savePreferences(); } int WalletHistoryModel::rowCount(const QModelIndex &parent) const @@ -384,6 +397,67 @@ void WalletHistoryModel::createMap() } } +enum SaveFileTypes { + IncludeFlagsSaveType = 1 // uint +}; + +void WalletHistoryModel::loadPreferences() +{ + QFile prefs(QString::fromStdString((m_wallet->walletDir() / "history.conf").string())); + if (!prefs.open(QIODevice::ReadOnly)) + return; + + auto pool = Streaming::pool(prefs.size()); + prefs.read(pool->data(), prefs.size()); + auto data = pool->commit(prefs.size()); + prefs.close(); + Streaming::MessageParser parser(data); + while (parser.next() == Streaming::FoundTag) { + if (parser.tag() == IncludeFlagsSaveType) { + m_includeFlags = QFlags::fromInt(parser.intData()); + } + } +} + +void WalletHistoryModel::savePreferences() +{ + auto pool = Streaming::pool(20); + Streaming::MessageBuilder builder(pool); + builder.add(IncludeFlagsSaveType, (uint64_t) m_includeFlags.toInt()); + auto buf = builder.buffer(); + + auto filename = QString::fromStdString((m_wallet->walletDir() / "history.conf").string()); + // hash the new file and check if its different lest we can skip saving + QFile origFile(filename); + if (origFile.open(QIODevice::ReadOnly)) { + CRIPEMD160 fileHasher; + auto origContent = origFile.readAll(); + fileHasher.write(origContent.data(), origContent.size()); + char fileHash[CRIPEMD160::OUTPUT_SIZE]; + fileHasher.finalize(fileHash); + + CRIPEMD160 memHasher; + memHasher.write(buf.begin(), buf.size()); + char memHash[CRIPEMD160::OUTPUT_SIZE]; + memHasher.finalize(memHash); + if (memcmp(fileHash, memHash, CRIPEMD160::OUTPUT_SIZE) == 0) { + // no changes, so don't write. + return; + } + } + + try { + std::string filebaseStr(filename.toStdString()); + std::ofstream out(filebaseStr + "~"); + out.write(buf.begin(), buf.size()); + out.flush(); + out.close(); + std::filesystem::rename(filebaseStr + "~", filebaseStr); + } catch (const std::exception &e) { + logFatal() << "Failed to create data file. Permissions issue?" << e; + } +} + bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) const { assert(QThread::currentThread() == thread()); @@ -491,6 +565,7 @@ void WalletHistoryModel::setIncludeFlags(const QFlags &fla return; m_recreateTriggered = true; QTimer::singleShot(0, this, SLOT(createMap())); + QTimer::singleShot(1000, this, SLOT(savePreferences())); } void WalletHistoryModel::freezeModel(bool on) diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 14b4f36..06870ff 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -40,6 +40,7 @@ class WalletHistoryModel : public QAbstractListModel public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); + ~WalletHistoryModel(); enum { TxId = Qt::UserRole, @@ -108,6 +109,7 @@ protected slots: void removeTransaction(int txIndex); void transactionChanged(int txIndex); void createMap(); + void savePreferences(); private: /// returns true if the filters indicate the transaction should stay. @@ -117,6 +119,8 @@ private: int txIndexFromRow(int row) const; + void loadPreferences(); + /* * Our internal data-model. * This is a filter of the wallet-internal 'txIndex'. So we map index-in-vector to -- 2.54.0 From 5e9b2770aac6616d21372db88baaa66454f9fa01 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 11:45:57 +0200 Subject: [PATCH 1019/1428] Add red-flash on incorrect password --- guis/mobile/NumericKeyboardWidget.qml | 6 +++++- guis/mobile/UnlockWidget.qml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index adbebd0..3a066a2 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -46,6 +46,10 @@ Item { return Math.min(height, realPixelSize * 4) } + function flashErrorFeedback() { + errorFeedback.opacity = 0.7 + } + Rectangle { // when the typed items are not allowd // by the back-end, we flash this red area for a very brief time @@ -172,7 +176,7 @@ Item { fail = !editor.backspacePressed(); if (fail) { anim.duration = 40 - errorFeedback.opacity = 0.7 + root.flashErrorFeedback(); dataInput.shake(); } else { diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index 8504e53..dc76960 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -38,6 +38,7 @@ Item { function passwordIncorrect() { shaker.start(); moveFocusTimer.start(); + keyboard.flashErrorFeedback(); } /// clears the password typed, if needed. function acceptedPassword() { -- 2.54.0 From c9a80d50589b2de70ffae281b8ea73cceff37b62 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 12:28:39 +0200 Subject: [PATCH 1020/1428] Skip saving on delete Since we use the wallet to find details of the saving, this is dangerous in case the wallet gets deleted before the model on shutdown. --- src/WalletHistoryModel.cpp | 5 ----- src/WalletHistoryModel.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 73fba46..b62a980 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -109,11 +109,6 @@ WalletHistoryModel::WalletHistoryModel(Wallet *wallet, QObject *parent) loadPreferences(); } -WalletHistoryModel::~WalletHistoryModel() -{ - savePreferences(); -} - int WalletHistoryModel::rowCount(const QModelIndex &parent) const { assert(QThread::currentThread() == thread()); diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index 06870ff..fa57738 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -40,7 +40,6 @@ class WalletHistoryModel : public QAbstractListModel public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); - ~WalletHistoryModel(); enum { TxId = Qt::UserRole, -- 2.54.0 From e6011e5b01d2367eac219b156ac334c9f135f410 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 12:50:31 +0200 Subject: [PATCH 1021/1428] Invert filter meaning for nicer UX --- guis/mobile/FilterPopup.qml | 8 ++++---- src/WalletEnums.h | 2 +- src/WalletHistoryModel.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/guis/mobile/FilterPopup.qml b/guis/mobile/FilterPopup.qml index d033846..29b055d 100644 --- a/guis/mobile/FilterPopup.qml +++ b/guis/mobile/FilterPopup.qml @@ -42,7 +42,7 @@ Item { width: parent.width spacing: 10 Flowee.Label { - text: qsTr("Filter Transactions") + text: qsTr("Transactions Filter") } Item { width: 80 * 3 + 20 @@ -189,9 +189,9 @@ Item { } Flowee.CheckBox { width: parent.width - text: qsTr("Has a comment", "This is a statement about a transaction"); - checked: (portfolio.current.transactions.includeFlags & Wallet.IncludeComments) > 0 - onToggled: toggleFlag(Wallet.IncludeComments, checked); + text: qsTr("Only with a comment", "This is a statement about a transaction"); + checked: !(portfolio.current.transactions.includeFlags & Wallet.IncludeTxWithoutComment) > 0 + onToggled: toggleFlag(Wallet.IncludeTxWithoutComment, !checked); } } } diff --git a/src/WalletEnums.h b/src/WalletEnums.h index ed41c0c..21ba96b 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -51,7 +51,7 @@ public: IncludeCFs = 8, IncludeSentTransactions = 0x10, IncludeReceivedTransactions = 0x20, - IncludeComments = 0x40, // include transactions that have a comment + IncludeTxWithoutComment = 0x40, // include transactions that have no comment IncludeAll = 0x7f }; diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index b62a980..fc3cd69 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -468,7 +468,7 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions) && wtx.inputToWTX.empty()) return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeComments) && wtx.userComment.isEmpty()) + if (!m_includeFlags.testFlag(WalletEnums::IncludeTxWithoutComment) && wtx.userComment.isEmpty()) return false; return true; } -- 2.54.0 From ad8287da1e025015fbc0902f1906429e2d6dd37f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 12:57:53 +0200 Subject: [PATCH 1022/1428] Add filterpopup.qml also sort the list alphabetically --- guis/mobile.qrc | 63 +++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index cd4cc51..e0cc26f 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -45,44 +45,45 @@ mobile/defaults.ini ControlColors.js mobile/About.qml - mobile/MenuOverlay.qml - mobile/NewAccount.qml - mobile/Page.qml + mobile/AccountHistory.qml + mobile/AccountPageListItem.qml + mobile/AccountSelectorPopup.qml + mobile/AccountSelectorWidget.qml + mobile/AccountsList.qml + mobile/AccountSyncState.qml + mobile/CurrencySelector.qml + mobile/EditableLabel.qml + mobile/ExploreModules.qml + mobile/FilterPopup.qml + mobile/GuiSettings.qml + mobile/ImportWalletPage.qml + mobile/InstaPayConfigButton.qml + mobile/InstaPayConfigPage.qml + mobile/Loading.qml + mobile/LockApplication.qml mobile/MainViewBase.qml mobile/MainView.qml - mobile/AccountHistory.qml - mobile/Loading.qml - mobile/TextButton.qml - mobile/AccountSyncState.qml - mobile/SendTransactionsTab.qml + mobile/MenuOverlay.qml + mobile/NewAccount.qml + mobile/NumericKeyboardWidget.qml + mobile/Page.qml + mobile/PageTitledBox.qml mobile/PayWithQR.qml - mobile/SlideToApprove.qml - mobile/ReceiveTab.qml - mobile/GuiSettings.qml - mobile/AccountsList.qml - mobile/AccountPageListItem.qml - mobile/VisualSeparator.qml mobile/PopupOverlay.qml - mobile/TransactionDetails.qml - mobile/TransactionInfoSmall.qml - mobile/AccountSelectorPopup.qml - mobile/CurrencySelector.qml - mobile/StartupScreen.qml - mobile/ImportWalletPage.qml mobile/PriceDetails.qml mobile/PriceInputWidget.qml - mobile/NumericKeyboardWidget.qml - mobile/AccountSelectorWidget.qml - mobile/PageTitledBox.qml - mobile/EditableLabel.qml + mobile/ReceiveTab.qml mobile/SelectDefaultAccountPage.qml - mobile/InstaPayConfigPage.qml - mobile/InstaPayConfigButton.qml - mobile/ExploreModules.qml - mobile/UnlockWidget.qml - mobile/UnlockWalletPanel.qml - mobile/UnlockApplication.qml - mobile/LockApplication.qml + mobile/SendTransactionsTab.qml + mobile/SlideToApprove.qml + mobile/StartupScreen.qml + mobile/TextButton.qml + mobile/TransactionDetails.qml + mobile/TransactionInfoSmall.qml mobile/TransactionListItem.qml + mobile/UnlockApplication.qml + mobile/UnlockWalletPanel.qml + mobile/UnlockWidget.qml + mobile/VisualSeparator.qml -- 2.54.0 From 745e78e0c62a739f0b5fcd7a3abe4c7b8ac4acf6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 13:19:11 +0200 Subject: [PATCH 1023/1428] UX: add a nice background behind the section --- guis/mobile/AccountHistory.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index d67b5bc..c9633fa 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -171,9 +171,12 @@ ListView { section.property: "grouping" section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart section.delegate: Item { - id: delegateRoot height: label.height + 15 width: root.width + Rectangle { + color: palette.light + anchors.fill: parent + } Flowee.Label { id: label x: 10 -- 2.54.0 From 2ce52044b1616395a5c25df39f0b8067cb97894e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Oct 2024 19:00:18 +0200 Subject: [PATCH 1024/1428] [UX] Add filter-overlay with filtred-count --- guis/mobile/MainViewBase.qml | 51 ++++++++++++++++++++++++++++++++++-- src/WalletHistoryModel.cpp | 17 ++++++++++++ src/WalletHistoryModel.h | 3 +++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 56df4dd..2b59b94 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -131,17 +131,64 @@ QQC2.Control { visible: root.showFilterIcon anchors.right: parent.right anchors.rightMargin: 6 + anchors.bottom: parent.bottom + anchors.bottomMargin: 9 smooth: true width: 25 height: 25 - anchors.bottom: parent.bottom - anchors.bottomMargin: 9 MouseArea { anchors.fill: parent anchors.margins: -10 onClicked: root.filterIconClicked(); } + + // and a little counter for the number of filters active + // to make it easier to see if stuff may be hidden. + // Notice that the MainViewBase has one instance for the + // entire application, while the filters are per acccount. + // So we need a bit of extra work to notice changes as the + // two 'Connections' objects below show. + + property int numFilters: calcNumFilters(); + function calcNumFilters() { + var filterCount = 0; + var currentAccount = portfolio.current + if (currentAccount != null) + filterCount = currentAccount.transactions.filterCount; + return filterCount; + } + Connections { + target: portfolio + function onCurrentChanged() { + filterChangedConnection.target = portfolio.current.transactions + filterIcon.numFilters = filterIcon.calcNumFilters(); + } + } + Connections { + id: filterChangedConnection + target: portfolio.current.transactions + function onIncludeFlagsChanged() { + filterIcon.numFilters = filterIcon.calcNumFilters(); + } + } + + Rectangle { + color: "#1d6828" + width: 18 + height: 18 + radius: 9 + anchors.bottom: parent.bottom + anchors.bottomMargin: -8 + x: -4 + visible: filterIcon.numFilters > 0 + + Text { + anchors.centerIn: parent + text: filterIcon.numFilters + color: "#fcfcfc" + } + } } AccountSelectorPopup { diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index fc3cd69..9c81ee9 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -590,6 +590,23 @@ bool WalletHistoryModel::isModelFrozen() const return m_rowsSilentlyInserted >= 0; } +int WalletHistoryModel::filterCount() const +{ + int c = 0; + if (!m_includeFlags.testFlag(WalletEnums::IncludeCFs)) + ++c; + if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected)) + ++c; + if (!m_includeFlags.testFlag(WalletEnums::IncludeSentTransactions)) + ++c; + if (!m_includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions)) + ++c; + if (!m_includeFlags.testFlag(WalletEnums::IncludeTxWithoutComment)) + ++c; + + return c; +} + uint32_t WalletHistoryModel::secsSinceEpochFor(int blockHeight) const { assert(QThread::currentThread() == thread()); diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index fa57738..da17dca 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -30,6 +30,7 @@ class WalletHistoryModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(int lastSyncIndicator READ lastSyncIndicator WRITE setLastSyncIndicator RESET resetLastSyncIndicator NOTIFY lastSyncIndicatorChanged) Q_PROPERTY(QFlags includeFlags READ includeFlags WRITE setIncludeFlags NOTIFY includeFlagsChanged) + Q_PROPERTY(int filterCount READ filterCount NOTIFY includeFlagsChanged) /** * The view sometimes wants to ensure that content in the list does not move. For instance * when a popup related to one of the list-items is showing. @@ -90,6 +91,8 @@ public: void freezeModel(bool on); bool isModelFrozen() const; + int filterCount() const; + signals: void lastSyncIndicatorChanged(); void includeFlagsChanged(); -- 2.54.0 From a3af22ea8404097545cf572a9141317b8e3a0f30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Oct 2024 14:29:43 +0200 Subject: [PATCH 1025/1428] Add Android support for bitcoincash scheme If you open a bitcoincash based url in a webbrowers, you now will see flowee pay being offered as a way to handle it. After clicking it you will instantly land in the payment page with the address filled in. --- android/AndroidManifest.xml | 21 +++++++--- android/java/org/flowee/pay/MainActivity.java | 41 +++++++++++++++++++ src/main_utils_android.cpp | 21 ++++++---- 3 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 android/java/org/flowee/pay/MainActivity.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index f4671e2..30dcda5 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -22,16 +22,25 @@ android:fullBackupOnly="false" android:icon="@drawable/icon"> + android:name="org.flowee.pay.MainActivity" + android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" + android:label="Flowee Pay" + android:launchMode="singleTop" + android:screenOrientation="unspecified" + android:exported="true" > + + + + + + + + + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java new file mode 100644 index 0000000..ec90c00 --- /dev/null +++ b/android/java/org/flowee/pay/MainActivity.java @@ -0,0 +1,41 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.flowee.pay; + +import org.qtproject.qt.android.bindings.QtActivity; +import android.os.Bundle; +import android.content.Intent; + +public class MainActivity extends QtActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + Intent i = getIntent(); + if (i.getAction() == Intent.ACTION_VIEW) { + // lets find out what the user wants to 'view'. + String data = i.getDataString(); + // a payment method. We like one. + if (data.startsWith("bitcoincash:")) { + APPLICATION_PARAMETERS = i.getDataString(); + } + } + super.onCreate(savedInstanceState); + } + +} diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 5d95959..40ef7dc 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -32,16 +32,22 @@ struct CommandLineParserData { - QStringList payRequest; + QString payRequest; }; CommandLineParserData* createCLD(QGuiApplication &app) { auto dat = new CommandLineParserData(); - // disable this for now, the argument has some weird stuff passed in - // upon normal starting of the app. - // Need to figure out how to do this the android style... - // dat->payRequest = app.arguments(); + const auto args = app.arguments(); + for (int i = 1; i < args.size(); ++i) { + const auto &arg = args.at(i); + if (arg.isEmpty()) + continue; + if (arg.startsWith("-")) + continue; + dat->payRequest = arg; + break; + } return dat; } @@ -108,8 +114,9 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { FloweePay *app = FloweePay::instance(); - if (cld->payRequest.size() == 1) - app->setPaymentProtocolRequest(cld->payRequest.first()); + if (!cld->payRequest.isEmpty()) + app->setPaymentProtocolRequest(cld->payRequest); + NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); netData->startTimer(); -- 2.54.0 From c86d6745d98ea3c0306ba743f51711e0618166a2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Oct 2024 14:31:11 +0200 Subject: [PATCH 1026/1428] Make build failures stop the script. No point in printing a cheerful message after a build failure. --- android/build-pay.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index c878fab..f9d6dbc 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -96,9 +96,8 @@ export QT_ANDROID_KEYSTORE_KEY_PASS=longPassword --release \ --input /home/builds/build/android-pay_mobile-deployment-settings.json \ --output /home/builds/build/android-build \ - --sign /home/builds/src/android/selfsigned.keystore floweepay - -echo -n "-- COPYING: " + --sign /home/builds/src/android/selfsigned.keystore floweepay && \ +echo -n "-- COPYING: " && \ cp -v android-build//build/outputs/apk/release/android-build-release-signed.apk floweepay.apk HERE chmod 755 .sign -- 2.54.0 From a8a2cf8329af1b0401915aca927826dcf65948d4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Oct 2024 22:09:40 +0200 Subject: [PATCH 1027/1428] Upgrade module config saving This moves the saving of the enabled-ness of a module to be for the entire module instead of for each section. To allow a module to be 'configurable' (for disabling section among others) we add a virtual method to the ModuleInfo object to allow a module to save anything it wants. --- src/ModuleInfo.cpp | 25 ++++++++++++++++---- src/ModuleInfo.h | 7 ++++++ src/ModuleManager.cpp | 54 ++++++++++++++++++++++++++++--------------- src/ModuleManager.h | 2 -- 4 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 0c5686b..c7203aa 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -22,6 +22,17 @@ ModuleInfo::ModuleInfo(QObject *parent) { } +void ModuleInfo::loadSettings(const Streaming::ConstBuffer &data) +{ + Q_UNUSED(data); +} + +Streaming::ConstBuffer ModuleInfo::saveSettings(std::shared_ptr &pool) const +{ + Q_UNUSED(pool); + return Streaming::ConstBuffer(); +} + QString ModuleInfo::id() const { return m_id; @@ -72,11 +83,15 @@ const QList &ModuleInfo::sections() const bool ModuleInfo::enabled() const { - for (auto s : m_sections) { - if (s->enabled()) - return true; - } - return false; + return m_enabled; +} + +void ModuleInfo::setEnabled(bool on) +{ + if (m_enabled == on) + return; + m_enabled = on; + emit enabledChanged(); } QString ModuleInfo::iconSource() const diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 1009bc8..c8ae7b6 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -19,6 +19,8 @@ #define MODULE_INFO_H #include +#include +#include #include "ModuleSection.h" @@ -44,6 +46,9 @@ class ModuleInfo : public QObject public: explicit ModuleInfo(QObject *parent = nullptr); + virtual void loadSettings(const Streaming::ConstBuffer &data); + virtual Streaming::ConstBuffer saveSettings(std::shared_ptr &pool) const; + /** * The module's unique ID. This is used in save-files * and similar, and never shown to a user. @@ -70,6 +75,7 @@ public: const QList §ions() const; bool enabled() const; + void setEnabled(bool on); /** * Sets the path to the icon for this module. @@ -90,6 +96,7 @@ private: QString m_description; QString m_translationUnit; QString m_iconSource; + bool m_enabled = false; QList m_sections; }; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 6debe82..251d570 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -36,8 +36,10 @@ enum ModuleConfigSaveTags { ModuleId = 1, - ModuleSectionId, - ModuleSectionEnabled + LegacyModuleSectionId, // Deprecated (since 2024-10) + LegacyModuleSectionEnabled, // Deprecated (since 2024-10) + ModuleEnabled, // bool + ModuleSaveData // byte-array for a module to save local config. }; @@ -51,12 +53,11 @@ ModuleManager::ModuleManager(QObject *parent) boost::filesystem::create_directories(boost::filesystem::path(path.toStdString())); m_configFile = path + '/' + "modules.conf"; - // Default modules to be enabled - auto pool = Streaming::pool(100); + // Default module to be enabled + auto pool = Streaming::pool(20); Streaming::MessageBuilder builder(pool); builder.add(ModuleId, "sendSweepModule"); - builder.add(ModuleSectionId, 0); - builder.add(ModuleSectionEnabled, true); + builder.add(ModuleEnabled, true); auto data = builder.buffer(); auto configFile = m_configFile.toStdString(); @@ -145,13 +146,13 @@ void ModuleManager::load() Streaming::BufferPool pool(dataSize); in.read(pool.data(), dataSize); Streaming::MessageParser parser(pool.commit(dataSize)); - const ModuleInfo *info = nullptr; + ModuleInfo *info = nullptr; int type = -1; while (parser.next() == Streaming::FoundTag) { if (parser.tag() == ModuleId) { info = nullptr; type = -1; - for (const auto *module : std::as_const(m_modules)) { + for (auto *module : std::as_const(m_modules)) { const QString wantedId = QString::fromUtf8(parser.stringData()); if (module->id() == wantedId) { info = module; @@ -159,11 +160,14 @@ void ModuleManager::load() } } } - else if (parser.tag() == ModuleSectionId) { + // legacy support + else if (parser.tag() == LegacyModuleSectionId) { type = parser.intData(); } - else if (parser.tag() == ModuleSectionEnabled) { + // legacy support + else if (parser.tag() == LegacyModuleSectionEnabled) { if (info) { + info->setEnabled(info->enabled() || parser.boolData()); for (auto *s : info->sections()) { if (s->type() == type) { s->setEnabled(parser.boolData()); @@ -172,6 +176,14 @@ void ModuleManager::load() } } } + else if (parser.tag() == ModuleEnabled) { + if (info) + info->setEnabled(parser.boolData()); + } + else if (parser.tag() == ModuleSaveData) { + if (info) + info->loadSettings(parser.bytesDataBuffer()); + } } } @@ -184,20 +196,24 @@ void ModuleManager::save() const return; } int saveFileSize = 100; + auto pool = std::make_shared(10000); + QList saveData; for (const auto *m : m_modules) { - saveFileSize += m->id().size() * 3; - saveFileSize += m->sections().size() * 5; + saveFileSize += m->id().size() * 3 + 3; + saveData.append(m->saveSettings(pool)); + saveFileSize += saveData.back().size() + 12; } - auto pool = std::make_shared(saveFileSize); + pool->reserve(saveFileSize); Streaming::MessageBuilder builder(pool); - for (const auto *m : m_modules) { + for (int i = 0; i < m_modules.size(); ++i) { + const auto *m = m_modules.at(i); builder.add(ModuleId, m->id().toStdString()); - for (const auto *s : m->sections()) { - builder.add(ModuleSectionId, s->type()); - if (s->enabled()) - builder.add(ModuleSectionEnabled, s->enabled()); - } + if (m->enabled()) + builder.add(ModuleEnabled, true); + auto saveFile = saveData.at(i); + if (!saveFile.isEmpty()) + builder.add(ModuleSaveData, saveFile); } auto data = builder.buffer(); diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 4fd0567..3474043 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -22,8 +22,6 @@ #include "ModuleInfo.h" -#include - class ModuleManager : public QObject { Q_OBJECT -- 2.54.0 From afa23dba9bd9c02987e103df2a04bb89c31c51b9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Oct 2024 22:39:31 +0200 Subject: [PATCH 1028/1428] Also move control of 'enabled' to the module dev. The virtua method allows full freedom for module devs to interpret the user enabling or disabling the module and any of it child sections. --- guis/mobile/ExploreModules.qml | 7 +---- modules/send-sweep/SendSweepModuleInfo.cpp | 30 ++++++++++++++-------- modules/send-sweep/SendSweepModuleInfo.h | 9 ++++++- src/ModuleInfo.cpp | 4 +++ src/ModuleInfo.h | 14 +++++++--- src/ModuleManager.cpp | 12 +++------ src/ModuleSection.h | 2 +- 7 files changed, 48 insertions(+), 30 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 559779a..0919339 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -141,12 +141,7 @@ Page { Flowee.CheckBox { anchors.verticalCenter: parent.verticalCenter checked: modelData.enabled - onClicked: { - var newValue = checked; - for (let s of modelData.sections) { - s.enabled = newValue; - } - } + onClicked: modelData.enabled = checked; } // one icon per section-type diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index ca83420..75e59ab 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -22,17 +22,25 @@ ModuleInfo * SendSweepModuleInfo::build() { - ModuleInfo *info = new ModuleInfo(); - info->setId("sendSweepModule"); - info->setTitle(tr("Sweep & Send")); - info->setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); - - auto sendButton = new ModuleSection(ModuleSection::ActionTabItem, info); - sendButton->setText(tr("Sweep Paper Wallet")); - sendButton->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); - info->addSection(sendButton); - qmlRegisterType("Flowee.org.pay.SendSweep", 1, 0, "SweepHandler"); + return new SendSweepModuleInfo(); +} - return info; +SendSweepModuleInfo::SendSweepModuleInfo() + : m_actionTabItem(new ModuleSection(ModuleSection::ActionTabItem, this)) +{ + setId("sendSweepModule"); + setTitle(tr("Sweep & Send")); + setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); + + m_actionTabItem->setText(tr("Sweep Paper Wallet")); + m_actionTabItem->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); + addSection(m_actionTabItem); +} + +void SendSweepModuleInfo::setEnabled(bool on) +{ + m_enabled = on; + m_actionTabItem->setEnabled(on); + emit enabledChanged(); } diff --git a/modules/send-sweep/SendSweepModuleInfo.h b/modules/send-sweep/SendSweepModuleInfo.h index e6c13e7..fc8463f 100644 --- a/modules/send-sweep/SendSweepModuleInfo.h +++ b/modules/send-sweep/SendSweepModuleInfo.h @@ -19,13 +19,20 @@ #include -class SendSweepModuleInfo : public QObject +class SendSweepModuleInfo : public ModuleInfo { Q_OBJECT public: static ModuleInfo *build(); + SendSweepModuleInfo(); + + void setEnabled(bool on) override; + static const char *translationUnit() { return "module-send-sweep"; } + +private: + ModuleSection *m_actionTabItem; }; diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index c7203aa..8aba13f 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -92,6 +92,10 @@ void ModuleInfo::setEnabled(bool on) return; m_enabled = on; emit enabledChanged(); + + for (auto *s : std::as_const(m_sections)) { + s->setEnabled(on); + } } QString ModuleInfo::iconSource() const diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index c8ae7b6..a6a0e35 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -35,7 +35,7 @@ class ModuleInfo : public QObject { Q_OBJECT - Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged FINAL) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) /// returns true if there is at least one section that is user-visible Q_PROPERTY(bool hasUISections READ hasUISections CONSTANT FINAL) Q_PROPERTY(QString title READ title CONSTANT FINAL) @@ -74,8 +74,14 @@ public: void addSection(ModuleSection *section); const QList §ions() const; + /** + * Called from loading of settings. + * The default will make all sections change their enabled to \a on + * + * Please make sure to emit enabledChanged if needed. + */ + virtual void setEnabled(bool on); bool enabled() const; - void setEnabled(bool on); /** * Sets the path to the icon for this module. @@ -90,13 +96,15 @@ public: signals: void enabledChanged(); +protected: + bool m_enabled = false; + private: QString m_id; QString m_title; QString m_description; QString m_translationUnit; QString m_iconSource; - bool m_enabled = false; QList m_sections; }; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 251d570..3435142 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -126,18 +126,14 @@ void ModuleManager::load(const char *translationUnit, const std::functionsetParent(this); // take ownership - m_modules.append(info); + if (info->enabled()) + logCritical() << "ModuleInfo starts 'enabled', denying user choice. Cowerdly refusing to register it"; + else + m_modules.append(info); } void ModuleManager::load() { - // enforce default off, unless the user explicitly asks. - for (const auto *m : std::as_const(m_modules)) { - for (auto *s : m->sections()) { - s->setEnabled(false); - } - } - std::ifstream in(m_configFile.toStdString()); if (!in.is_open()) return; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index ea7ef97..c3ebd45 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -99,7 +99,7 @@ private: QString m_text; QString m_subtext; QString m_startQMLfile; - bool m_enabled = true; + bool m_enabled = false; QStringList m_requiredModules; }; -- 2.54.0 From 0fc17ea5f817e662fc27fcca2afc0a379eb6795c Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Oct 2024 11:33:11 +0200 Subject: [PATCH 1029/1428] Add bch-wif scheme support for mobile This allows a user to click on a link in a browser with the bch-wif scheme and we'll handle that with a sweep page on startup. To avoid this being just-another-special case we introduce a new module (read: plugin) concept that is a Start-screen type. The idea is that we can have a generic handling of this type in various parts of the app without it being specifically about the wif handling. Notice that it doesn't matter if the user has the module enabled, which just operates the display of the menu option to start it manually. --- android/AndroidManifest.xml | 1 + android/java/org/flowee/pay/MainActivity.java | 2 +- guis/mobile/main.qml | 11 +++++++++ modules/send-sweep/SendPage.qml | 11 ++++++++- modules/send-sweep/SendSweepModuleInfo.cpp | 6 +++++ src/ModuleManager.cpp | 10 ++++++++ src/ModuleManager.h | 2 ++ src/ModuleSection.cpp | 10 ++++++++ src/ModuleSection.h | 11 +++++++++ src/main_utils.cpp | 24 +++++++++++++++++-- src/main_utils_android.cpp | 19 +++++++++++++-- 11 files changed, 101 insertions(+), 6 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 30dcda5..5ba1f15 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -39,6 +39,7 @@ + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index ec90c00..b77dfb2 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -31,7 +31,7 @@ public class MainActivity extends QtActivity // lets find out what the user wants to 'view'. String data = i.getDataString(); // a payment method. We like one. - if (data.startsWith("bitcoincash:")) { + if (data.startsWith("bitcoincash:") || data.startsWith("bch-wif:")) { APPLICATION_PARAMETERS = i.getDataString(); } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 9c9c734..0dc470f 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -45,6 +45,17 @@ ApplicationWindow { thePile.push("./StartupScreen.qml"); else if (Pay.paymentProtocolRequest !== "") thePile.push("./PayWithQR.qml") + else { + // find if there is any fullscreen plugin enabled + for (var mod of ModuleManager.registeredModules) { + for (var section of mod.sections) { + if (section.isStartScreenType && section.enabled) { + thePile.push(section.qml, { "extraData": section.extraData } ) + return; + } + } + } + } } } Component.onCompleted: updateFontSize(); diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 4b54055..0a1d0f1 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -27,11 +27,14 @@ Mobile.Page { id: root headerText: qsTr("Sweep coins") + // set by main.qml, sourced from the extraData field on the ModuleSection + property string extraData: "" + Item { // data QRScanner { id: scanner scanType: QRScanner.PrivateKeyWIF - autostart: true + autostart: root.extraData === "" helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult @@ -47,6 +50,12 @@ Mobile.Page { SweepHandler { id: sweeper account: pay.portfolio.current + /* + This page can be opened as part of the StartScreen feature, + in that case we should have received the externally supplied privKey + in the 'extraData' property. + */ + privKey: root.extraData } } diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index 75e59ab..49eee72 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -36,6 +36,12 @@ SendSweepModuleInfo::SendSweepModuleInfo() m_actionTabItem->setText(tr("Sweep Paper Wallet")); m_actionTabItem->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); addSection(m_actionTabItem); + + // this one is used to have a full screen page at application start, + // but we only enable this when the 'bch-wif' scheme was used to start Flowee Pay + auto introScreenSection = new ModuleSection(ModuleSection::StartScreenType, this); + introScreenSection->setStartQMLFile(m_actionTabItem->startQMLFile()); + addSection(introScreenSection); } void SendSweepModuleInfo::setEnabled(bool on) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 3435142..86f81f4 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -283,7 +283,17 @@ ModuleSection *ModuleManager::sectionOnPlugin(const QString &pluginId, const QSt && s->sectionId() == sectionId) return s; } + break; } } return nullptr; } + +void ModuleManager::allSections(const std::function &handler) +{ + for (const auto *m : std::as_const(m_modules)) { + for (auto *s : m->sections()) { + handler(m->id(), s); + } + } +} diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 3474043..5664543 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -54,6 +54,8 @@ public: Q_INVOKABLE ModuleSection* sectionOnPlugin(const QString &pluginId, const QString §ionId) const; + void allSections(const std::function &handler); + signals: void sendMenuSectionsChanged(); void mainMenuSectionsChanged(); diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index aefcc35..99e6f38 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -71,6 +71,16 @@ void ModuleSection::setSectionId(const QString &id) m_sectionId = id; } +QString ModuleSection::extraData() const +{ + return m_extraData; +} + +void ModuleSection::setExtraData(const QString &data) +{ + m_extraData = data; +} + void ModuleSection::setStartQMLFile(const QString &filename) { m_startQMLfile = filename; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index c3ebd45..5155987 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -33,14 +33,17 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) + Q_PROPERTY(QString extraData READ extraData FINAL) Q_PROPERTY(bool isActionTabItem READ isActionTabItem CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) + Q_PROPERTY(bool isStartScreenType READ isStartScreenType CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: /// The placement in the main app of this section. enum SectionType { ActionTabItem, ///< An list-item on the 'action' tab (aka send-tab). MainMenuItem, ///< A text-button in the main menu + StartScreenType, ///< A screen displayed on top of the main app at startup. CustomSectionType, ///< Not normally shown type, but fetchable by id. // BuildTxComponent, ///< Inside the 'build-transactions' this adds a new buildingblock. @@ -79,6 +82,9 @@ public: bool isMainMenuMethod() const { return m_type == MainMenuItem; } + bool isStartScreenType() const { + return m_type == StartScreenType; + } bool enabled() const; /** @@ -90,6 +96,9 @@ public: QString sectionId() const; void setSectionId(const QString &id); + QString extraData() const; + void setExtraData(const QString &data); + signals: void enabledChanged(); @@ -101,6 +110,8 @@ private: QString m_startQMLfile; bool m_enabled = false; + QString m_extraData; + QStringList m_requiredModules; }; diff --git a/src/main_utils.cpp b/src/main_utils.cpp index be02e05..1ab4c63 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "ModuleManager.h" #include "qqmlcontext.h" #include #include @@ -176,8 +177,27 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { FloweePay *app = FloweePay::instance(); - if (cld->payRequest.size() == 1) - app->setPaymentProtocolRequest(cld->payRequest.first()); + if (cld->payRequest.size() == 1) { + QString arg = cld->payRequest.first(); + if (arg.startsWith("bitcoincash:")) { + app->setPaymentProtocolRequest(arg); + } +#ifndef NDEBUG + // this only makes sense on Android (or some other mobile platform) + // but for testing reasons we include this in debug builds. + else if (arg.startsWith("bch-wif:")) { + auto *mm = qobject_cast(engine.rootContext()->objectForName("ModuleManager")); + assert(mm); + // find and force the display of the sendSweepModule which can handle this + mm->allSections([arg](const QString &moduleId, ModuleSection *s) { + if (s->type() == ModuleSection::StartScreenType) + s->setEnabled(moduleId == "sendSweepModule"); + if (s->enabled()) + s->setExtraData(arg.mid(8)); // mid to cut off the prefix + }); + } +#endif + } NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 40ef7dc..4146d84 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -18,6 +18,7 @@ #include "FloweePay.h" #include "PortfolioDataProvider.h" #include "NetDataProvider.h" +#include "ModuleManager.h" #include @@ -114,8 +115,22 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { FloweePay *app = FloweePay::instance(); - if (!cld->payRequest.isEmpty()) - app->setPaymentProtocolRequest(cld->payRequest); + if (!cld->payRequest.isEmpty()) { + if (cld->payRequest.startsWith("bitcoincash:")) { + app->setPaymentProtocolRequest(cld->payRequest); + } else if (cld->payRequest.startsWith("bch-wif:")) { + auto *mm = qobject_cast(engine.rootContext()->objectForName("ModuleManager")); + assert(mm); + // find and force the display of the sendSweepModule which can handle this + QString arg = cld->payRequest; + mm->allSections([arg](const QString &moduleId, ModuleSection *s) { + if (s->type() == ModuleSection::StartScreenType) + s->setEnabled(moduleId == "sendSweepModule"); + if (s->enabled()) + s->setExtraData(arg.mid(8)); // mid to cut off the prefix + }); + } + } NetDataProvider *netData = new NetDataProvider(&engine); app->p2pNet()->addP2PNetListener(netData); -- 2.54.0 From 8e79d9ef9ba3d9f7f4b5c8076ff383c0e9d9b292 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Oct 2024 11:41:27 +0200 Subject: [PATCH 1030/1428] Make startup screen show the 'cash stamp' button always. Even if the related module is not enabled. --- guis/mobile/StartupScreen.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index a18bf55..1781a2f 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -190,11 +190,8 @@ Page { for (var mod of ModuleManager.registeredModules) { if (mod.id === "sendSweepModule") { for (var section of mod.sections) { - if (section.isActionTabItem) { - if (section.enabled) - return section; - break; - } + if (section.isActionTabItem) + return section; } break; } -- 2.54.0 From 19a19df5d36b76d4376f83eff9a88162e87db848 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Oct 2024 11:43:23 +0200 Subject: [PATCH 1031/1428] No longer make sweep module enabled by default With webpage based scanning and the landing page button, the access is there. No need to have a button always on the 'send' tab. --- src/ModuleManager.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 86f81f4..c7ac86d 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -52,19 +52,6 @@ ModuleManager::ModuleManager(QObject *parent) auto path = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); boost::filesystem::create_directories(boost::filesystem::path(path.toStdString())); m_configFile = path + '/' + "modules.conf"; - - // Default module to be enabled - auto pool = Streaming::pool(20); - Streaming::MessageBuilder builder(pool); - builder.add(ModuleId, "sendSweepModule"); - builder.add(ModuleEnabled, true); - - auto data = builder.buffer(); - auto configFile = m_configFile.toStdString(); - std::ofstream outFile(configFile); - outFile.write(data.begin(), data.size()); - outFile.flush(); - outFile.close(); } #ifdef TARGET_OS_Android -- 2.54.0 From 1203da046dd1dc3c11b4ba8f8702792a896e0a24 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 25 Oct 2024 11:49:13 +0200 Subject: [PATCH 1032/1428] cleanup includes --- src/main_utils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 1ab4c63..0e79d23 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -16,17 +16,17 @@ * along with this program. If not, see . */ #include "ModuleManager.h" -#include "qqmlcontext.h" +#include "FloweePay.h" +#include "NetDataProvider.h" +#include "PortfolioDataProvider.h" #include -#include +#include #include #include #include #include #include -#include -#include #ifdef NETWORK_LOGGER # include "NetworkLogClient.h" -- 2.54.0 From 6b9473e30449a74abb7f8e753c2700e4d4048cf7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Oct 2024 15:25:59 +0100 Subject: [PATCH 1033/1428] Fix indent. --- src/FloweePay.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 0407ab0..b37e720 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -176,8 +176,8 @@ FloweePay::FloweePay() } else if (state == Qt::ApplicationActive) { /* - * We are brought back to the foreground. - * + * We are brought back to the foreground. + * * On different iterations of Android the behavior can differ quite substantially * when it comes to being allowed to continue using resources while not being active. * As such there is no real way to know what being inactive has done to our network -- 2.54.0 From 7e60f1fdea13e5ac2a49c7f7dc7ba9340627aa7a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Oct 2024 21:06:21 +0100 Subject: [PATCH 1034/1428] Refactor Intent: disconnect from app lifetime The idea of using Flowee Pay to open a payment screen, or a sweep screen, was so far married to the executable lifetime due to it being passed as a command line argument. This does not reflect reality, on neither desktop nor on mobile as multi-tasking is possible and we should allow that. As a result the new object "Intent" has been introduced with the usecase specific properties. Setting those properties at any time during the lifetime of the app now pushes the correct page to the stack on mobile. Desktop is in need of more love in this department. --- android/java/org/flowee/pay/MainActivity.java | 31 +++++--- guis/desktop/SendTransactionPane.qml | 4 +- guis/desktop/main.qml | 10 ++- guis/mobile/PayWithQR.qml | 6 +- guis/mobile/main.qml | 46 ++++++++++- modules/send-sweep/SendPage.qml | 18 +++-- modules/send-sweep/SendSweepModuleInfo.cpp | 3 +- src/CMakeLists.txt | 1 + src/FloweePay.cpp | 13 ---- src/FloweePay.h | 9 --- src/ModuleSection.cpp | 10 --- src/ModuleSection.h | 6 -- src/PaymentIntent.cpp | 52 +++++++++++++ src/PaymentIntent.h | 40 ++++++++++ src/main.cpp | 7 +- src/main_utils.cpp | 48 +++++------- src/main_utils_android.cpp | 76 +++++++++---------- 17 files changed, 245 insertions(+), 135 deletions(-) create mode 100644 src/PaymentIntent.cpp create mode 100644 src/PaymentIntent.h diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index b77dfb2..b011f38 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -23,19 +23,32 @@ import android.content.Intent; public class MainActivity extends QtActivity { - @Override - public void onCreate(Bundle savedInstanceState) + public native void setPaymentIntentOnCPP(String data); + + // the C++ should call this one when it finished startup. + public void onQtAppStarted() { - Intent i = getIntent(); - if (i.getAction() == Intent.ACTION_VIEW) { + filterAndForward(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) + { + super.onNewIntent(intent); + filterAndForward(intent); + } + + private void filterAndForward(Intent intent) + { + if (intent.getAction() == Intent.ACTION_VIEW) { // lets find out what the user wants to 'view'. - String data = i.getDataString(); - // a payment method. We like one. - if (data.startsWith("bitcoincash:") || data.startsWith("bch-wif:")) { - APPLICATION_PARAMETERS = i.getDataString(); + String data = intent.getDataString(); + if (data != null + && (data.startsWith("bitcoincash:") || data.startsWith("bch-wif:"))) { + // a payment method. We like that. + setPaymentIntentOnCPP(data); } } - super.onCreate(savedInstanceState); } } diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 802cfe3..fe4c80a 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -37,10 +37,10 @@ Item { Afterwards we reset the property to avoid the next opening of this screen repeating the payment. */ - var paymentProtcolUrl = Pay.paymentProtocolRequest; + var paymentProtcolUrl = Intent.paymentUrl; if (paymentProtcolUrl !== "") { payment.targetAddress = paymentProtcolUrl; - Pay.paymentProtocolRequest = ""; + Intent.paymentUrl = ""; } } diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 10bb3d9..403bf1e 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -69,12 +69,14 @@ ApplicationWindow { receivePane.source = "./ReceiveTransactionPane.qml" sendTransactionPane.source = "./SendTransactionPane.qml" - if (!portfolio.current.isUserOwned) { // Open on receive tab if the wallet is effectively empty - tabbar.currentIndex = 2; - } - else if (Pay.paymentProtocolRequest !== "") { + if (Intent.paymentUrl !== "") { + // respond to payment intent is prio one. tabbar.currentIndex = 1; } + else if (!portfolio.current.isUserOwned) { + // Open on receive tab if the wallet is effectively empty + tabbar.currentIndex = 2; + } else { tabbar.currentIndex = 0; } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 0a30713..79e2318 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -70,7 +70,7 @@ Page { QRScanner { id: scanner scanType: QRScanner.PaymentDetails - autostart: Pay.paymentProtocolRequest === "" + autostart: Intent.paymentUrl === "" onFinished: { var rc = scanResult if (rc === "") { // scanning interrupted @@ -115,11 +115,11 @@ Page { screen repeating the payment. */ - var paymentProtcolUrl = Pay.paymentProtocolRequest; + var paymentProtcolUrl = Intent.paymentUrl; if (paymentProtcolUrl !== "") { scanner.autostart = false; payment.targetAddress = paymentProtcolUrl; - Pay.paymentProtocolRequest = ""; + Intent.paymentUrl = ""; root.allowEditAmount = payment.paymentAmount <= 0; } } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 0dc470f..02dea75 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -42,15 +42,13 @@ ApplicationWindow { portfolio.limitedArchiveView = true; thePile.replace("./MainView.qml"); if (!portfolio.current.isUserOwned) - thePile.push("./StartupScreen.qml"); - else if (Pay.paymentProtocolRequest !== "") - thePile.push("./PayWithQR.qml") + thePile.pushSpecialPage("./StartupScreen.qml"); else { // find if there is any fullscreen plugin enabled for (var mod of ModuleManager.registeredModules) { for (var section of mod.sections) { if (section.isStartScreenType && section.enabled) { - thePile.push(section.qml, { "extraData": section.extraData } ) + thePile.pushSpecialPage(section.qml) return; } } @@ -68,6 +66,20 @@ ApplicationWindow { function onFontScalingChanged() { updateFontSize(); } function onUseDarkSkinChanged() { ControlColors.applySkin(mainWindow); } } + Connections { + target: Intent + function onPaymentUrlChanged() { + if (Intent.paymentUrl != "") + thePile.pushSpecialPage("./PayWithQR.qml", true) + } + function onSweepKeyChanged() { + if (Intent.sweepKey != "") { + var s = ModuleManager.sectionOnPlugin("sendSweepModule", "main"); + if (s) + thePile.pushSpecialPage(s.qml, true); + } + } + } property color floweeSalmon: "#ff9d94" property color floweeBlue: "#0b1088" @@ -85,6 +97,32 @@ ApplicationWindow { initialItem: "./Loading.qml" onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); enabled: !menuOverlay.open + property int tempPageIndex: -1 + property bool exitAfterPopTemp: false + onDepthChanged: { + if (tempPageIndex > depth) { + tempPageIndex = -1; + if (exitAfterPopTemp) + mainWindow.close(); + } + } + // the idea behind 'special' pages are that there can be no more than one + // so pushing a second just replaces the previous one. + // Argument 'existAfter', when it is set to true will cause the application + // to close after the page is removed. Which is expected behavior on Android + // activities. + function pushSpecialPage(item, exitAfter) { + exitAfterPopTemp = false; + while (tempPageIndex >= 0 && depth >= tempPageIndex) { + pop(); + } + + push(item); + tempPageIndex = depth; + if (typeof(exitAfter) === "boolean") + exitAfterPopTemp = exitAfter; + } + Keys.onPressed: (event)=> { if (depth > 1 && (event.key === Qt.Key_Escape || event.key === Qt.Key_Back)) { diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 0a1d0f1..31fe6a3 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -27,14 +27,11 @@ Mobile.Page { id: root headerText: qsTr("Sweep coins") - // set by main.qml, sourced from the extraData field on the ModuleSection - property string extraData: "" - Item { // data QRScanner { id: scanner scanType: QRScanner.PrivateKeyWIF - autostart: root.extraData === "" + autostart: Intent.sweepKey === "" helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult @@ -50,12 +47,17 @@ Mobile.Page { SweepHandler { id: sweeper account: pay.portfolio.current + /* - This page can be opened as part of the StartScreen feature, - in that case we should have received the externally supplied privKey - in the 'extraData' property. + This page can be opened as part of the Intent feature, */ - privKey: root.extraData + Component.onCompleted: { + var fromIntent = Intent.sweepKey + if (fromIntent !== "") { + Intent.sweepKey = ""; // prepare for next usage. + sweeper.privKey = fromIntent; + } + } } } diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index 49eee72..a5775bb 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -39,8 +39,9 @@ SendSweepModuleInfo::SendSweepModuleInfo() // this one is used to have a full screen page at application start, // but we only enable this when the 'bch-wif' scheme was used to start Flowee Pay - auto introScreenSection = new ModuleSection(ModuleSection::StartScreenType, this); + auto introScreenSection = new ModuleSection(ModuleSection::CustomSectionType, this); introScreenSection->setStartQMLFile(m_actionTabItem->startQMLFile()); + introScreenSection->setSectionId("main"); addSection(introScreenSection); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e683a8..9cec80b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,6 +30,7 @@ set (PAY_SOURCES NotificationManager.cpp Payment.cpp PaymentBackend.cpp + PaymentIntent.cpp PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b37e720..7587f8f 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -995,19 +995,6 @@ void FloweePay::setSkinFollowsPlatform(bool newSkinFollowsPlatform) #endif } -QString FloweePay::paymentProtocolRequest() const -{ - return m_paymentProtocolRequest; -} - -void FloweePay::setPaymentProtocolRequest(const QString &ppr) -{ - if (m_paymentProtocolRequest == ppr) - return; - m_paymentProtocolRequest = ppr; - emit paymentProtocolRequestChanged(); -} - FloweePay::ApplicationProtection FloweePay::appProtection() const { return m_appProtection; diff --git a/src/FloweePay.h b/src/FloweePay.h index 6e39474..3656eff 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -72,8 +72,6 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) - // unique helper property.. - Q_PROPERTY(QString paymentProtocolRequest READ paymentProtocolRequest WRITE setPaymentProtocolRequest NOTIFY paymentProtocolRequestChanged FINAL) Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) public: enum UnitOfBitcoin { @@ -367,9 +365,6 @@ public: ApplicationProtection appProtection() const; void setAppProtection(ApplicationProtection newAppProtection); - QString paymentProtocolRequest() const; - void setPaymentProtocolRequest(const QString &newPaymentProtocolRequest); - IndexerServices *indexerServices() const; /// removes wallet from the list of wallets this app knows about. @@ -397,7 +392,6 @@ signals: void totalBalanceConfigChanged(); void privateModeChanged(); void appProtectionChanged(); - void paymentProtocolRequestChanged(); void skinFollowsPlatformChanged(); private slots: @@ -454,9 +448,6 @@ private: bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true - // the string passed in the app in order to instantly start paying. - QString m_paymentProtocolRequest; - #ifdef TARGET_OS_Android // when the app is no longer the front app we record the time in order to // know how much time has passed when we get active again. diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index 99e6f38..aefcc35 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -71,16 +71,6 @@ void ModuleSection::setSectionId(const QString &id) m_sectionId = id; } -QString ModuleSection::extraData() const -{ - return m_extraData; -} - -void ModuleSection::setExtraData(const QString &data) -{ - m_extraData = data; -} - void ModuleSection::setStartQMLFile(const QString &filename) { m_startQMLfile = filename; diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 5155987..71e45aa 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -33,7 +33,6 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) - Q_PROPERTY(QString extraData READ extraData FINAL) Q_PROPERTY(bool isActionTabItem READ isActionTabItem CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) Q_PROPERTY(bool isStartScreenType READ isStartScreenType CONSTANT) @@ -96,9 +95,6 @@ public: QString sectionId() const; void setSectionId(const QString &id); - QString extraData() const; - void setExtraData(const QString &data); - signals: void enabledChanged(); @@ -110,8 +106,6 @@ private: QString m_startQMLfile; bool m_enabled = false; - QString m_extraData; - QStringList m_requiredModules; }; diff --git a/src/PaymentIntent.cpp b/src/PaymentIntent.cpp new file mode 100644 index 0000000..fc9bd90 --- /dev/null +++ b/src/PaymentIntent.cpp @@ -0,0 +1,52 @@ +#include "PaymentIntent.h" + +PaymentIntent::PaymentIntent(QObject *parent) + : QObject{parent} +{ + +} + +void PaymentIntent::setPaymentIntent(const QString &pi) +{ + setPaymentUrl(QString()); + setSweepKey(QString()); + if (pi.startsWith("bitcoincash:")) { + setPaymentUrl(pi); + } else if (pi.startsWith("bch-wif:")) { + setSweepKey(pi.mid(8)); // mid to cut off the prefix + } +} + +QString PaymentIntent::paymentUrl() const +{ + return m_paymentUrl; +} + +void PaymentIntent::setPaymentUrl(const QString &newPaymentUrl) +{ + if (m_paymentUrl == newPaymentUrl) + return; + m_paymentUrl = newPaymentUrl; + emit paymentUrlChanged(); +} + +QString PaymentIntent::sweepKey() const +{ + return m_sweepKey; +} + +void PaymentIntent::setSweepKey(const QString &newSweepAddress) +{ + if (m_sweepKey == newSweepAddress) + return; + m_sweepKey = newSweepAddress; + emit sweepKeyChanged(); +} + +void PaymentIntent::emitSignals() +{ + if (!m_sweepKey.isEmpty()) + emit sweepKeyChanged(); + if (!m_paymentUrl.isEmpty()) + emit paymentUrlChanged(); +} diff --git a/src/PaymentIntent.h b/src/PaymentIntent.h new file mode 100644 index 0000000..3ca0862 --- /dev/null +++ b/src/PaymentIntent.h @@ -0,0 +1,40 @@ +#ifndef PAYMENTINTENT_H +#define PAYMENTINTENT_H + +#include + +class PaymentIntent : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString paymentUrl READ paymentUrl WRITE setPaymentUrl NOTIFY paymentUrlChanged FINAL) + Q_PROPERTY(QString sweepKey READ sweepKey WRITE setSweepKey NOTIFY sweepKeyChanged FINAL) +public: + explicit PaymentIntent(QObject *parent = nullptr); + + void setPaymentIntent(const QString &string); + + QString paymentUrl() const; + void setPaymentUrl(const QString &newPaymentUrl); + + QString sweepKey() const; + void setSweepKey(const QString &newSweepAddress); + + /* To make QML register the properties, emit relevant change signals. + * This is a bit of a hack to avoid race conditions during startup of + * the app. + * The QML will only start listening after a init time, any + * properties set will just be here but a on_Changed callback + * won't happen. To avoid lots of QML code, we simple allow emitting them again. + */ + void emitSignals(); + +signals: + void paymentUrlChanged(); + void sweepKeyChanged(); + +private: + QString m_paymentUrl; + QString m_sweepKey; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 76baee2..f23753e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,6 +69,7 @@ struct ECC_State #ifdef TARGET_OS_Android # include "main_utils_android.cpp" #else +#include "PaymentIntent.h" # include "main_utils.cpp" #endif @@ -81,7 +82,7 @@ CommandLineParserData* createCLD(QGuiApplication &app); void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld); std::unique_ptr handleStaticChain(CommandLineParserData *cld); void initLogger(CommandLineParserData *cld); - +void setupCallbacks(PaymentIntent *pi); int main(int argc, char *argv[]) { @@ -92,6 +93,9 @@ int main(int argc, char *argv[]) qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); + PaymentIntent paymentIntent; + setupCallbacks(&paymentIntent); + qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); qmlRegisterType("Flowee.org.pay", 1, 0, "BitcoinValue"); qmlRegisterType("Flowee.org.pay", 1, 0, "NewWalletConfig"); @@ -141,6 +145,7 @@ int main(int argc, char *argv[]) auto app = FloweePay::instance(); engine.addImageProvider(QLatin1String("qr"), new QRCreator(QRCreator::URLEncoded)); engine.addImageProvider(QLatin1String("qr-raw"), new QRCreator(QRCreator::RawString)); + engine.rootContext()->setContextProperty("Intent", &paymentIntent); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); MenuModel menuModel(&modules); diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 0e79d23..47f03c7 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -18,6 +18,7 @@ #include "ModuleManager.h" #include "FloweePay.h" #include "NetDataProvider.h" +#include "PaymentIntent.h" #include "PortfolioDataProvider.h" #include @@ -69,7 +70,6 @@ struct CommandLineParserData FloweePay::selectChain(chain); if (parser.isSet(offline)) FloweePay::instance()->setOffline(true); - payRequest = parser.positionalArguments(); } QCommandLineParser parser; @@ -82,13 +82,25 @@ struct CommandLineParserData QCommandLineOption offline; QCommandLineOption headers; P2PNet::Chain chain = P2PNet::MainChain; - - QStringList payRequest; }; +static PaymentIntent *g_payIntent = nullptr; + +void setupCallbacks(PaymentIntent *pi) +{ + g_payIntent = pi; + // TODO +} + CommandLineParserData* createCLD(QGuiApplication &app) { - return new CommandLineParserData(app); + auto *cld = new CommandLineParserData(app); + auto args = cld->parser.positionalArguments(); + if (args.size() == 1) { + assert(g_payIntent); + g_payIntent->setPaymentIntent(args.first()); + } + return cld; } void initLogger(CommandLineParserData *cld) @@ -176,30 +188,8 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { - FloweePay *app = FloweePay::instance(); - if (cld->payRequest.size() == 1) { - QString arg = cld->payRequest.first(); - if (arg.startsWith("bitcoincash:")) { - app->setPaymentProtocolRequest(arg); - } -#ifndef NDEBUG - // this only makes sense on Android (or some other mobile platform) - // but for testing reasons we include this in debug builds. - else if (arg.startsWith("bch-wif:")) { - auto *mm = qobject_cast(engine.rootContext()->objectForName("ModuleManager")); - assert(mm); - // find and force the display of the sendSweepModule which can handle this - mm->allSections([arg](const QString &moduleId, ModuleSection *s) { - if (s->type() == ModuleSection::StartScreenType) - s->setEnabled(moduleId == "sendSweepModule"); - if (s->enabled()) - s->setExtraData(arg.mid(8)); // mid to cut off the prefix - }); - } -#endif - } - NetDataProvider *netData = new NetDataProvider(&engine); + FloweePay *app = FloweePay::instance(); app->p2pNet()->addP2PNetListener(netData); const bool isOffline = cld->parser.isSet(cld->offline); @@ -216,5 +206,9 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c EndPoint(cld->parser.value(cld->connect).toStdString(), port)); } + // to make sure that the QML gets a 'change' callback after loading is completed. + if (g_payIntent) + g_payIntent->emitSignals(); + app->startNet(); // lets go! } diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 4146d84..3643964 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -19,6 +19,7 @@ #include "PortfolioDataProvider.h" #include "NetDataProvider.h" #include "ModuleManager.h" +#include "PaymentIntent.h" #include @@ -26,6 +27,7 @@ #include #include #include +#include #ifdef NETWORK_LOGGER # include "NetworkLogClient.h" @@ -33,23 +35,31 @@ struct CommandLineParserData { - QString payRequest; }; +static PaymentIntent *g_payIntent = nullptr; + +void JNI_setPaymentIntent(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QJniObject intentObject(data); + g_payIntent->setPaymentIntent(intentObject.toString()); +} + +void setupCallbacks(PaymentIntent *pi) +{ + g_payIntent = pi; + const JNINativeMethod methods[] = { + {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)} + }; + QJniEnvironment env; + env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 1); +} + CommandLineParserData* createCLD(QGuiApplication &app) { - auto dat = new CommandLineParserData(); - const auto args = app.arguments(); - for (int i = 1; i < args.size(); ++i) { - const auto &arg = args.at(i); - if (arg.isEmpty()) - continue; - if (arg.startsWith("-")) - continue; - dat->payRequest = arg; - break; - } - return dat; + return new CommandLineParserData(); } void initLogger(CommandLineParserData *cld) @@ -71,12 +81,13 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. /* - * On Android the way to get bigger files into the app is to use the 'assets' subsystem. - * Android is smart and doesn't actually install those files, just provides an interface - * to the still compressed archive. + * On Android the way to get bigger files into the app is to use the + * 'assets' subsystem. + * Android is smart and doesn't actually install those files, just + * provides an interface to the still compressed archive. * - * Unfortunately, that means we can't memory map them and as such we need to do a one-time - * copy of those files to our private homedir. + * Unfortunately, that means we can't memory map them and as such we + * need to do a one-time copy of those files to our private homedir. */ QFileInfo target("staticHeaders"); QFileInfo orig("assets:/blockheaders"); @@ -106,38 +117,27 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) if (!QFile::exists(infoFilePath)) Blockchain::createStaticHeaders(blockheaders->fileName().toStdString(), infoFilePath.toStdString()); - Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), blockheaders->size(), - infoFilePath.toStdString()); + Blockchain::setStaticChain(blockheaders->map(0, blockheaders->size()), + blockheaders->size(), infoFilePath.toStdString()); blockheaders->close(); return blockheaders; } void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) { - FloweePay *app = FloweePay::instance(); - if (!cld->payRequest.isEmpty()) { - if (cld->payRequest.startsWith("bitcoincash:")) { - app->setPaymentProtocolRequest(cld->payRequest); - } else if (cld->payRequest.startsWith("bch-wif:")) { - auto *mm = qobject_cast(engine.rootContext()->objectForName("ModuleManager")); - assert(mm); - // find and force the display of the sendSweepModule which can handle this - QString arg = cld->payRequest; - mm->allSections([arg](const QString &moduleId, ModuleSection *s) { - if (s->type() == ModuleSection::StartScreenType) - s->setEnabled(moduleId == "sendSweepModule"); - if (s->enabled()) - s->setExtraData(arg.mid(8)); // mid to cut off the prefix - }); - } - } - NetDataProvider *netData = new NetDataProvider(&engine); + FloweePay *app = FloweePay::instance(); app->p2pNet()->addP2PNetListener(netData); netData->startTimer(); PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); + + if (g_payIntent) { + auto myActivity = QJniObject(QNativeInterface::QAndroidApplication::context()); + myActivity.callObjectMethod("onQtAppStarted", "()V"); + } + app->startNet(); // lets go! } -- 2.54.0 From cabaece73391632ea4c18557e8d9526cd7678a36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 27 Oct 2024 21:52:31 +0100 Subject: [PATCH 1035/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 5ba1f15..1a28935 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="31" android:versionName="2024.10.2"> diff --git a/src/main.cpp b/src/main.cpp index f23753e..59691eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.10.1"); + qapp.setApplicationVersion("2024.10.2"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 6dc315cc776026e3ad48f2d9fbd26742bac48fd3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Oct 2024 10:24:58 +0100 Subject: [PATCH 1036/1428] Fix race condition, add autostartSkipped signal This fixes the issue that the "sweep from browser" would consume the property before the QR scanner had time to decide it should skip scanning due to that property existing. --- modules/send-sweep/SendPage.qml | 22 ++++++++++++---------- src/QRScanner.cpp | 2 ++ src/QRScanner.h | 8 +++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 31fe6a3..9e3ec8a 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -42,16 +42,13 @@ Mobile.Page { root.forceActiveFocus(); sweeper.privKey = rc; } - } - - SweepHandler { - id: sweeper - account: pay.portfolio.current - - /* - This page can be opened as part of the Intent feature, - */ - Component.onCompleted: { + onAutostartSkipped: { + /* + * This page can be opened as part of the Intent feature, + * if it has then we need to set the privatekey and at + * the same time clear out the property to prepare for + * next usage. + */ var fromIntent = Intent.sweepKey if (fromIntent !== "") { Intent.sweepKey = ""; // prepare for next usage. @@ -59,6 +56,11 @@ Mobile.Page { } } } + + SweepHandler { + id: sweeper + account: pay.portfolio.current + } } Column { diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index d42f085..7eb9018 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -116,6 +116,8 @@ void QRScanner::completed() { if (m_autostart) start(); + else + emit autostartSkipped(); } QString QRScanner::helpText() const diff --git a/src/QRScanner.h b/src/QRScanner.h index b4d3b74..408f178 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -81,9 +81,15 @@ signals: void scanResultChanged(); void autostartChanged(); void isScanningChanged(); - void helpTextChanged(); + /** + * This signal is emitted when autostart has been skipped. + * Autostart only happens once for the lifetime of this QML item, + * the autostart property will never be evaluated again. + */ + void autostartSkipped(); + private: ScanType m_scanType; ResultSource m_resultSource = Camera; -- 2.54.0 From 191c22b5498144dd66498722c88cc052dd8d3316 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Oct 2024 11:26:19 +0100 Subject: [PATCH 1037/1428] Return zero fees if we don't have info --- src/TransactionInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TransactionInfo.cpp b/src/TransactionInfo.cpp index b060d52..8ec74fd 100644 --- a/src/TransactionInfo.cpp +++ b/src/TransactionInfo.cpp @@ -32,7 +32,7 @@ int TransactionInfo::txSize() const double TransactionInfo::fees() const { if (m_inputs.empty()) - return -1; // aka unknown. + return 0; qint64 fees = 0; for (const auto i : m_inputs) { /* @@ -43,7 +43,7 @@ double TransactionInfo::fees() const * So at first one we don't have that we can give up and just return the magic 'unknown' value. */ if (i == nullptr) - return -1; + return 0; fees += i->value(); } for (const auto o : m_outputs) { -- 2.54.0 From 65eb981d8354e285dda2cbf55d8617154feb8a8f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Oct 2024 13:39:57 +0100 Subject: [PATCH 1038/1428] Revise import wallet pages The vast vast majority of wallets imported will not have a password. So we de-prioritize that and make the user aware of the password field should they check the contents without one. This moves the 'wallet name' again to the top for all types as the most observed mistake is that people type a wallet-name in the password field and then are confused why there is nothing there. (and additionally annoyed that the name of the wallet is auto-generated). Other fixes includes spacing and alignment. Keyboard focus on desktop. --- guis/desktop/NewAccountImportAccount.qml | 95 +++++++++++++++--------- guis/mobile/ImportWalletPage.qml | 73 ++++++++++-------- 2 files changed, 100 insertions(+), 68 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 98b1007..303f57d 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,12 +43,26 @@ Item { PropertyChanges { target: inputColumn; opacity: 0.65 } PropertyChanges { target: privKeyColumn; opacity: 1 } PropertyChanges { target: seedDetailsColumn; opacity: 0 } + StateChangeScript { script: { + if (accountName.visible) + accountName.forceActiveFocus(); + else + ageButton.forceActiveFocus(); + } + } }, State { name: "seedDetailsState" PropertyChanges { target: inputColumn; opacity: 0.65 } PropertyChanges { target: privKeyColumn; opacity: 0 } PropertyChanges { target: seedDetailsColumn; opacity: 1 } + StateChangeScript { script: { + if (accountName2.visible) + accountName2.forceActiveFocus(); + else + seedCheckButton.forceActiveFocus(); + } + } } ] state: "inputState" @@ -194,7 +208,6 @@ Item { anchors.leftMargin: 10 width: columnWidth height: parent.height - visible: opacity > 0.3 enabled: visible ColumnLayout { @@ -214,6 +227,25 @@ Item { } } } + + Flowee.GroupBox { + title: qsTr("New Wallet Name") + collapsable: false + Layout.fillWidth: true + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + Layout.fillWidth: true + } + } + Flowee.CheckBox { id: singleAddress Layout.fillWidth: true @@ -222,7 +254,6 @@ Item { checked: true } - Flowee.GroupBox { title: qsTr("Oldest Transaction") collapsable: false @@ -280,24 +311,6 @@ Item { visible: false } - Flowee.GroupBox { - title: qsTr("New Wallet Name") - collapsable: false - Layout.fillWidth: true - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } - Flowee.TextField { - id: accountName - Layout.fillWidth: true - } - } - Flowee.BigButton { id: privImportStartButton @@ -333,24 +346,34 @@ Item { anchors.leftMargin: 10 width: columnWidth height: parent.height - visible: opacity > 0.3 + spacing: 10 enabled: visible Flowee.GroupBox { - title: qsTr("Password") + title: qsTr("New Wallet Name") width: parent.width collapsable: false + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } Flowee.TextField { - id: passwordField + id: accountName2 Layout.fillWidth: true - placeholderText: qsTr("imported wallet password") } } + + Flowee.BigButton { id: seedCheckButton text: qsTr("Discover Details", "online check for wallet details") enabled: !seedImportHelper.checking isMainButton: true; + x: 20 onClicked: { // setting new values here will start the check. @@ -367,6 +390,7 @@ Item { if (resultCount === 0) { // empty seedCheckButton.isMainButton = false; emptySeedWarningLabel.visible = true; + passwordBox.collapsed = false; } else if (resultCount >= 1) { // TODO what to do if there are more then 1? @@ -412,26 +436,23 @@ Item { Flowee.Label { id: emptySeedWarningLabel color: mainWindow.errorRed - text: qsTr("Nothing found for seed") + text: qsTr("Nothing found for seed. Does it have a password?") visible: false width: parent.width + wrapMode: Text.Wrap } Flowee.GroupBox { - title: qsTr("New Wallet Name") + id: passwordBox + title: qsTr("Password") width: parent.width - collapsable: false - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } + collapsable: true + collapsed: true Flowee.TextField { - id: accountName2 + id: passwordField Layout.fillWidth: true + placeholderText: qsTr("imported wallet password") + onTotalTextChanged: seedCheckButton.isMainButton = true; } } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 8db1c28..eab100c 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -301,6 +301,23 @@ Page { } } } + + PageTitledBox { + title: qsTr("New Wallet Name") + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } + Flowee.TextField { + id: accountName + width: parent.width + } + } + Flowee.CheckBox { id: singleAddress Layout.fillWidth: true @@ -362,22 +379,6 @@ Page { visible: false } - PageTitledBox { - title: qsTr("New Wallet Name") - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } - Flowee.TextField { - id: accountName - width: parent.width - } - } - Flowee.BigButton { id: privImportStartButton text: qsTr("Start") @@ -424,18 +425,30 @@ Page { x: width + 10 width: parent.width height: parent.height + spacing: 10 function takeFocus() { - seedCheckButton.forceActiveFocus(); + if (nameField.visible) + nameField.forceActiveFocus(); + else + seedCheckButton.forceActiveFocus(); } PageTitledBox { - title: qsTr("Password") + id: nameField + title: qsTr("New Wallet Name") width: parent.width + visible: { + // don't ask for a name when the user imports a + // wallet the first thing in a new instance. + var all = portfolio.rawAccounts; + if (all.length === 1 && !all[0].isUserOwned) + return false; + return true; + } Flowee.TextField { - id: passwordField + id: accountName2 width: parent.width - placeholderText: qsTr("imported wallet password") } } @@ -460,6 +473,7 @@ Page { if (resultCount === 0) { // empty seedCheckButton.isMainButton = false; emptySeedWarningLabel.visible = true; + passwordBox.visible = true; } else if (resultCount >= 1) { // TODO what to do if there are more then 1? @@ -504,25 +518,22 @@ Page { Flowee.Label { id: emptySeedWarningLabel color: mainWindow.errorRed - text: qsTr("Nothing found for seed") + text: qsTr("Nothing found for seed. Does it have a password?") visible: false width: parent.width + wrapMode: Text.Wrap } PageTitledBox { - title: qsTr("New Wallet Name") + id: passwordBox + title: qsTr("Password") width: parent.width - visible: { - // don't ask for a name when the user imports a - // wallet the first thing in a new instance. - var all = portfolio.rawAccounts; - if (all.length === 1 && !all[0].isUserOwned) - return false; - return true; - } + visible: false Flowee.TextField { - id: accountName2 + id: passwordField width: parent.width + placeholderText: qsTr("imported wallet password") + onTotalTextChanged: seedCheckButton.isMainButton = true; } } -- 2.54.0 From c97890401e7aee505945b8266ff4aea21c895b17 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 28 Oct 2024 14:47:34 +0100 Subject: [PATCH 1039/1428] Show seed-phrase-password in backup details pages. --- guis/desktop/AccountDetails.qml | 8 ++++++++ guis/mobile/AccountPageListItem.qml | 9 +++++++++ src/AccountInfo.cpp | 5 +++++ src/AccountInfo.h | 3 +++ 4 files changed, 25 insertions(+) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 6d2c670..7451311 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -312,6 +312,14 @@ Item { wrapMode: TextEdit.WordWrap padding: 0 } + Flowee.Label { + text: qsTr("Password") + ":" + visible: root.account.mnemonicPwd !== "" + } + Flowee.Label { + text: root.account.mnemonicPwd + visible: text !== "" + } Flowee.Label { text: qsTr("Seed format") + ":" visible: root.account.isElectrumMnemonic diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 1b311ca..6305d41 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -143,6 +143,15 @@ QQC2.Control { } } } + Flowee.Label { + text: qsTr("Password") + font.bold: true + visible: root.account.mnemonicPwd !== "" + } + Flowee.LabelWithClipboard { + text: root.account.mnemonicPwd + visible: text != "" + } } PageTitledBox { diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 719c2a0..fe9e44a 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -473,6 +473,11 @@ QString AccountInfo::hdWalletMnemonic() const return m_wallet->hdWalletMnemonic(); } +QString AccountInfo::hdWalletMnemonicPwd() const +{ + return m_wallet->hdWalletMnemonicPwd(); +} + bool AccountInfo::isElectrumMnemonic() const { return m_wallet->isElectrumMnemonic(); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index b18c078..6fa1664 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -61,6 +61,7 @@ class AccountInfo : public QObject Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) Q_PROPERTY(QString mnemonic READ hdWalletMnemonic NOTIFY encryptionChanged) + Q_PROPERTY(QString mnemonicPwd READ hdWalletMnemonicPwd NOTIFY encryptionChanged) Q_PROPERTY(bool isElectrumMnemonic READ isElectrumMnemonic NOTIFY encryptionChanged) Q_PROPERTY(QString hdDerivationPath READ hdDerivationPath NOTIFY encryptionChanged) Q_PROPERTY(QString xpub READ xpub NOTIFY encryptionChanged) @@ -140,6 +141,8 @@ public: bool isHDWallet() const; /// Return the mnemonic seed that is the basis of this wallet. QString hdWalletMnemonic() const; + /// Return the password for the seed. + QString hdWalletMnemonicPwd() const; /// Return true if the mnemonic seed is in Electrum format bool isElectrumMnemonic() const; /// Return the derivation base path that is the basis of this wallet. -- 2.54.0 From 26b48c5c49095c4bc484dba0accb7d34b1722845 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Oct 2024 12:09:19 +0100 Subject: [PATCH 1040/1428] Add Android shortcut: Scan QML On Android an app can ship with (static) shortcuts. We use this feature to allow the user to create a new icon which still starts Flowee Pay, but it instantly opens the payment screen on the QR scanner. --- android/AndroidManifest.xml | 2 +- android/java/org/flowee/pay/MainActivity.java | 4 + android/res/values-nl/strings.xml | 5 ++ android/res/values/strings.xml | 5 ++ android/res/xml/shortcuts.xml | 13 +++ guis/mobile/main.qml | 6 ++ src/CMakeLists.txt | 2 +- src/PaymentIntent.cpp | 52 ----------- src/UserIntent.cpp | 86 +++++++++++++++++++ src/{PaymentIntent.h => UserIntent.h} | 31 ++++++- src/main.cpp | 6 +- src/main_utils.cpp | 16 ++-- src/main_utils_android.cpp | 25 ++++-- 13 files changed, 176 insertions(+), 77 deletions(-) create mode 100644 android/res/values-nl/strings.xml create mode 100644 android/res/values/strings.xml create mode 100644 android/res/xml/shortcuts.xml delete mode 100644 src/PaymentIntent.cpp create mode 100644 src/UserIntent.cpp rename src/{PaymentIntent.h => UserIntent.h} (54%) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 1a28935..bfa4fe4 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -43,7 +43,7 @@ - + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index b011f38..6c505af 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -24,6 +24,7 @@ import android.content.Intent; public class MainActivity extends QtActivity { public native void setPaymentIntentOnCPP(String data); + public native void startScan(); // the C++ should call this one when it finished startup. public void onQtAppStarted() @@ -49,6 +50,9 @@ public class MainActivity extends QtActivity setPaymentIntentOnCPP(data); } } + else if (intent.getAction() == Intent.ACTION_QUICK_VIEW) { + startScan(); + } } } diff --git a/android/res/values-nl/strings.xml b/android/res/values-nl/strings.xml new file mode 100644 index 0000000..55d489b --- /dev/null +++ b/android/res/values-nl/strings.xml @@ -0,0 +1,5 @@ + + + Lees van QR + + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100644 index 0000000..20e8c6e --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Scan QR + + diff --git a/android/res/xml/shortcuts.xml b/android/res/xml/shortcuts.xml new file mode 100644 index 0000000..8af59ce --- /dev/null +++ b/android/res/xml/shortcuts.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 02dea75..df369bd 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -79,6 +79,12 @@ ApplicationWindow { thePile.pushSpecialPage(s.qml, true); } } + function onStartPaymentScannerChanged() { + if (Intent.startPaymentScanner) { + Intent.startPaymentScanner = false; + thePile.pushSpecialPage("./PayWithQR.qml", false) + } + } } property color floweeSalmon: "#ff9d94" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9cec80b..cc21e55 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,7 @@ set (PAY_SOURCES NotificationManager.cpp Payment.cpp PaymentBackend.cpp - PaymentIntent.cpp + UserIntent.cpp PaymentRequest.cpp PaymentDetailOutput.cpp PaymentDetailInputs.cpp diff --git a/src/PaymentIntent.cpp b/src/PaymentIntent.cpp deleted file mode 100644 index fc9bd90..0000000 --- a/src/PaymentIntent.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "PaymentIntent.h" - -PaymentIntent::PaymentIntent(QObject *parent) - : QObject{parent} -{ - -} - -void PaymentIntent::setPaymentIntent(const QString &pi) -{ - setPaymentUrl(QString()); - setSweepKey(QString()); - if (pi.startsWith("bitcoincash:")) { - setPaymentUrl(pi); - } else if (pi.startsWith("bch-wif:")) { - setSweepKey(pi.mid(8)); // mid to cut off the prefix - } -} - -QString PaymentIntent::paymentUrl() const -{ - return m_paymentUrl; -} - -void PaymentIntent::setPaymentUrl(const QString &newPaymentUrl) -{ - if (m_paymentUrl == newPaymentUrl) - return; - m_paymentUrl = newPaymentUrl; - emit paymentUrlChanged(); -} - -QString PaymentIntent::sweepKey() const -{ - return m_sweepKey; -} - -void PaymentIntent::setSweepKey(const QString &newSweepAddress) -{ - if (m_sweepKey == newSweepAddress) - return; - m_sweepKey = newSweepAddress; - emit sweepKeyChanged(); -} - -void PaymentIntent::emitSignals() -{ - if (!m_sweepKey.isEmpty()) - emit sweepKeyChanged(); - if (!m_paymentUrl.isEmpty()) - emit paymentUrlChanged(); -} diff --git a/src/UserIntent.cpp b/src/UserIntent.cpp new file mode 100644 index 0000000..5a36cad --- /dev/null +++ b/src/UserIntent.cpp @@ -0,0 +1,86 @@ +#include "UserIntent.h" + +#include + +UserIntent::UserIntent(QObject *parent) + : QObject{parent} +{ + // ensure that the emitSignals is always called on the Qt thread. + connect (this, SIGNAL(needsEmit()), this, SLOT(emitSignals()), Qt::QueuedConnection); + // Same for processNewIntents + connect (this, SIGNAL(newIntentString()), this, SLOT(processNewIntents()), Qt::QueuedConnection); +} + +void UserIntent::setPaymentIntent(const QString &pi) +{ + m_intentString = pi; + if (QThread::currentThread() != thread()) { + emit newIntentString(); + return; + } + processNewIntents(); +} + +void UserIntent::processNewIntents() +{ + auto pi = m_intentString; + m_intentString.clear(); + setPaymentUrl(QString()); + setSweepKey(QString()); + if (pi.startsWith("bitcoincash:")) { + setPaymentUrl(pi); + } else if (pi.startsWith("bch-wif:")) { + setSweepKey(pi.mid(8)); // mid to cut off the prefix + } +} + +bool UserIntent::startPaymentScanner() const +{ + return m_startPaymentScanner; +} + +void UserIntent::setStartPaymentScanner(bool on) +{ + if (m_startPaymentScanner == on) + return; + m_startPaymentScanner = on; + emit startPaymentScannerChanged(); +} + +QString UserIntent::paymentUrl() const +{ + return m_paymentUrl; +} + +void UserIntent::setPaymentUrl(const QString &newPaymentUrl) +{ + if (m_paymentUrl == newPaymentUrl) + return; + m_paymentUrl = newPaymentUrl; + emit paymentUrlChanged(); +} + +QString UserIntent::sweepKey() const +{ + return m_sweepKey; +} + +void UserIntent::setSweepKey(const QString &newSweepAddress) +{ + if (m_sweepKey == newSweepAddress) + return; + m_sweepKey = newSweepAddress; + emit sweepKeyChanged(); +} + +void UserIntent::emitSignals() +{ + if (QThread::currentThread() != thread()) { + emit needsEmit(); // calls this method again, but from the Gui thread. + return; + } + if (!m_sweepKey.isEmpty()) + emit sweepKeyChanged(); + if (!m_paymentUrl.isEmpty()) + emit paymentUrlChanged(); +} diff --git a/src/PaymentIntent.h b/src/UserIntent.h similarity index 54% rename from src/PaymentIntent.h rename to src/UserIntent.h index 3ca0862..d42f758 100644 --- a/src/PaymentIntent.h +++ b/src/UserIntent.h @@ -1,15 +1,22 @@ -#ifndef PAYMENTINTENT_H -#define PAYMENTINTENT_H +#ifndef USERINTENT_H +#define USERINTENT_H #include -class PaymentIntent : public QObject +/** + * This class enables OS integration. + * The front end consumes the data from this class while platform specific handlers + * set it, using the UserIntents instance as a clearinghouse abstracting away the + * platform differences. + */ +class UserIntent : public QObject { Q_OBJECT Q_PROPERTY(QString paymentUrl READ paymentUrl WRITE setPaymentUrl NOTIFY paymentUrlChanged FINAL) Q_PROPERTY(QString sweepKey READ sweepKey WRITE setSweepKey NOTIFY sweepKeyChanged FINAL) + Q_PROPERTY(bool startPaymentScanner READ startPaymentScanner WRITE setStartPaymentScanner NOTIFY startPaymentScannerChanged FINAL) public: - explicit PaymentIntent(QObject *parent = nullptr); + explicit UserIntent(QObject *parent = nullptr); void setPaymentIntent(const QString &string); @@ -19,6 +26,10 @@ public: QString sweepKey() const; void setSweepKey(const QString &newSweepAddress); + bool startPaymentScanner() const; + void setStartPaymentScanner(bool on); + +public slots: /* To make QML register the properties, emit relevant change signals. * This is a bit of a hack to avoid race conditions during startup of * the app. @@ -28,13 +39,25 @@ public: */ void emitSignals(); +private slots: + void processNewIntents(); + signals: void paymentUrlChanged(); void sweepKeyChanged(); + void startPaymentScannerChanged(); + + // internal signal + void needsEmit(); + // internal signal + void newIntentString(); private: QString m_paymentUrl; QString m_sweepKey; + bool m_startPaymentScanner = false; + + QString m_intentString; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 59691eb..9e37171 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,7 +69,7 @@ struct ECC_State #ifdef TARGET_OS_Android # include "main_utils_android.cpp" #else -#include "PaymentIntent.h" +#include "UserIntent.h" # include "main_utils.cpp" #endif @@ -82,7 +82,7 @@ CommandLineParserData* createCLD(QGuiApplication &app); void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld); std::unique_ptr handleStaticChain(CommandLineParserData *cld); void initLogger(CommandLineParserData *cld); -void setupCallbacks(PaymentIntent *pi); +void setupCallbacks(UserIntent *pi); int main(int argc, char *argv[]) { @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); - PaymentIntent paymentIntent; + UserIntent paymentIntent; setupCallbacks(&paymentIntent); qmlRegisterType("Flowee.org.pay", 1, 0, "Wallet"); diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 47f03c7..6b60582 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -18,7 +18,7 @@ #include "ModuleManager.h" #include "FloweePay.h" #include "NetDataProvider.h" -#include "PaymentIntent.h" +#include "UserIntent.h" #include "PortfolioDataProvider.h" #include @@ -84,11 +84,11 @@ struct CommandLineParserData P2PNet::Chain chain = P2PNet::MainChain; }; -static PaymentIntent *g_payIntent = nullptr; +static UserIntent *g_userIntent = nullptr; -void setupCallbacks(PaymentIntent *pi) +void setupCallbacks(UserIntent *pi) { - g_payIntent = pi; + g_userIntent = pi; // TODO } @@ -97,8 +97,8 @@ CommandLineParserData* createCLD(QGuiApplication &app) auto *cld = new CommandLineParserData(app); auto args = cld->parser.positionalArguments(); if (args.size() == 1) { - assert(g_payIntent); - g_payIntent->setPaymentIntent(args.first()); + assert(g_userIntent); + g_userIntent->setPaymentIntent(args.first()); } return cld; } @@ -207,8 +207,8 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c } // to make sure that the QML gets a 'change' callback after loading is completed. - if (g_payIntent) - g_payIntent->emitSignals(); + if (g_userIntent) + g_userIntent->emitSignals(); app->startNet(); // lets go! } diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 3643964..837330d 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -19,7 +19,7 @@ #include "PortfolioDataProvider.h" #include "NetDataProvider.h" #include "ModuleManager.h" -#include "PaymentIntent.h" +#include "UserIntent.h" #include @@ -37,24 +37,33 @@ struct CommandLineParserData { }; -static PaymentIntent *g_payIntent = nullptr; +static UserIntent *g_userIntent = nullptr; void JNI_setPaymentIntent(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(env); Q_UNUSED(thiz); QJniObject intentObject(data); - g_payIntent->setPaymentIntent(intentObject.toString()); + g_userIntent->setPaymentIntent(intentObject.toString()); } -void setupCallbacks(PaymentIntent *pi) +void JNI_startQRScanner(JNIEnv *env, jobject thiz) { - g_payIntent = pi; + Q_UNUSED(env); + Q_UNUSED(thiz); + g_userIntent->setStartPaymentScanner(true); +} + + +void setupCallbacks(UserIntent *pi) +{ + g_userIntent = pi; const JNINativeMethod methods[] = { - {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)} + {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)}, + {"startScan", "()V", reinterpret_cast(JNI_startQRScanner)} }; QJniEnvironment env; - env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 1); + env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 2); } CommandLineParserData* createCLD(QGuiApplication &app) @@ -134,7 +143,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c engine.rootContext()->setContextProperty("net", netData); engine.rootContext()->setContextProperty("portfolio", portfolio); - if (g_payIntent) { + if (g_userIntent) { auto myActivity = QJniObject(QNativeInterface::QAndroidApplication::context()); myActivity.callObjectMethod("onQtAppStarted", "()V"); } -- 2.54.0 From f053b34ded473fa3b2908bc59bf82aab99557fcb Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Oct 2024 12:09:33 +0100 Subject: [PATCH 1041/1428] Cleanup imports --- guis/mobile/AccountHistory.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index c9633fa..bcfbe53 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -17,7 +17,6 @@ */ import QtQuick import QtQuick.Controls as QQC2 -import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; -- 2.54.0 From 815e21d23349a1ea433ee410835a6c56ca91bf53 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Oct 2024 12:48:36 +0100 Subject: [PATCH 1042/1428] Make a specific icon for the QR shortcut --- android/res/drawable-hdpi/logo.png | Bin 4846 -> 5068 bytes android/res/drawable-hdpi/logo_port.png | Bin 4978 -> 5196 bytes android/res/drawable-hdpi/qrscanicon.png | Bin 0 -> 1376 bytes android/res/drawable-ldpi/qrscanicon.png | Bin 0 -> 1203 bytes android/res/drawable-mdpi/qrscanicon.png | Bin 0 -> 1507 bytes android/res/drawable-xhdpi/qrscanicon.png | Bin 0 -> 1593 bytes android/res/drawable-xxhdpi/qrscanicon.png | Bin 0 -> 1809 bytes android/res/drawable-xxxhdpi/qrscanicon.png | Bin 0 -> 2123 bytes android/res/xml/shortcuts.xml | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 android/res/drawable-hdpi/qrscanicon.png create mode 100644 android/res/drawable-ldpi/qrscanicon.png create mode 100644 android/res/drawable-mdpi/qrscanicon.png create mode 100644 android/res/drawable-xhdpi/qrscanicon.png create mode 100644 android/res/drawable-xxhdpi/qrscanicon.png create mode 100644 android/res/drawable-xxxhdpi/qrscanicon.png diff --git a/android/res/drawable-hdpi/logo.png b/android/res/drawable-hdpi/logo.png index 4e9eb7cb489fb15f24cee45bc7a3a37a99b74501..d196fb10c58acb8e05446ddd36924127e8bebb9d 100644 GIT binary patch delta 1390 zcmaE-dPaSMO8te@2+uT6Pb~%x1_lN$1_nkJ21W)3h9Cw821X<{6C*PNGXn!d8v_G_ z5F;~K4M?37BP*EQ!@$5G4P{SZU|`UIssZUSWMpFCWnf^K!@$5`#K^=T!oa|=ih+S4 zvz>*Z9%RTq1_>S|f?5t2wl%JNFlghwQF{gH-t;b;pnWOR1 zOM|re>dOQKd4gn*=xIHQSRK&9wJ>cJ-@8H`JEY`Y2Ku5@>qrdwOh`MSfw}>CkI? z)aw&1*6ZpQ&V8mZDdK>V3&(`Oqim-(2rLd&KI$^-o3PwJ#^d^vyw$F=NmxD$Y~(um zvHAbO_jzXzJ!zROQt!twSAW}ILnfB;Mz^!u_V?%RE@Wbv*QmYr>G~^;GyN}R>*`jq ze92=Lzpm?hk6Ew0;h%b}k%~Llg)ib<2YyS|2J^kx&FZu|FaK=q96JWdS6ZdaGBQky zT~Eb*t|$+$&i_9z^V@gkyGx5M?#Mb}#=yWAof#5Q65;D(m7Jfem$-QfQ!SH9YO=4GgUe48WQyato|{^HVa@DqRvw zQf-wG$qHHZ=BaFajFS(r#i%5wm?S0}C0pnkTNRMWwC+Vi8q$Q@LS{fvo zrlm}FV0RTZ(KR&CH8w;sWd?gT2g?HSg3XiJ1)kQw;F9N7Wto0xx&;FR;{{I_$B>A_ zZ*RQLI%FWh_F%?~umYG~%%9@{I=y!TS*pXg1Q)9)AUc)ljazmwZy;U5w9 zPa@N{EebrilfV9CrDblW(&K$9&n3%*YCY!7KD$J4ug5&ebib4NDnE}nteYJjF_AZQ z*YuS7;MCdKx*87)pB@>v7q$&baiXPx{TX`t#gRG?vKAO;ewy-}~D0OX>`lM+WmQD$RDg zc7EaJmH)Oy=q4}EJv_7IekapUiJ*CUi~0V{)%f=3P`6B!XPR9iljI49f6+l~YMGI$ o`yc-o))A^xX>Dm~u_^w?5M=*p$>TLrDxiYM)78&qol`;+0Hf^sqW}N^ delta 1303 zcmX@3{!Vp*O8v2_kcg6?#Bzm#qWrYXoK%I9%7Rpd)QZeB28N1TxdD!RhZT5sf9|qv zJkqFkeqprwTlVUIOiMy{pA}L&a$?4kw|}daMYB&<(d-UeZEAhw$d1Z|m5X?n=`^!* zwYy!fvts!sR&(4YtM_;He7@$Lrt43g^mwnhCj6kvpRcDi$)Yb(S z#yXRJIQ%=C>HdqsV_xK)f{C+37#J8^CNnb1)L-sW5_Pohut-_qC+6rXlP%g6pm>FA zt<{W}U#z}TlSIVb4{m+%;CRy2gGVF7*~1+L{xB4l76z3}6#741Bg2t2QsrLzV zALR?PnlaU!mE=c4Cq_3GXF?qs~gx!2mKW0qSssfcZ4;Zzd265z&a zmbB&7k_eB>b%&1IzxkzFr1?avd15kSXvTzC`ycXu-d)r3srRk&e8_U)>mv*K#SY)~ zF08RW_kH7I6aR(>#sy2a|Gmm)@Y}O$?^@UXhJU#a9_?DIS$*IspTU=u6U*LsInHug z$gpSrMf0Z(2cs91pMAY{YyV|-1?SkMc@53Z32QuVzP5Y+?&X`e_l?*7t7F*jS#*@e z_`)m(2HuLykcg59UmvUF&9j*rnN*TY(+m^S3=?(JEYgg0O;SvfbQ4okO?3^D%#)2% z49yITEiETIurAd!voN$UGPE={FfcJOH?UCRQczHE_w)_03QH|2&dkrVRWi~uG|)3P znasx)=xA(ZU}9xx5n^a!WnyY&WT|anWMyCg)>M&OVC9>il9^WNl30>zt7K$gghln{ z1#Enbng+swwwMpPm1uXNzfUb!fe*WBtHZ$+xoQ(`U|x{{Hj(`JE0rEU7X` ztK4Uzn9t4;RbAM|`FGaGo^|~X%uoNjG;hIi7Vzr2 z?G$~Lx8}1_7Q4rldrD8f%3~B>ALP&UAgz?wPhF9%M%eLgS3z z>*|cwMrU4cGnkyD65-tCFS&bS!+PsREls|9^CFe0b(3u7M8tT0G-RH7=X2bLi-Bro zPZG}gJ&&opCBs-jYx_)V=*MJHyRcH`W!GiysqN!rbflY-g%%w2?c> z_g|Q_XJx%`)Ta|Ci&Nb1g?*CqxU?#@-`^%b{wBX3|H-Sbw`L31N|*_1AM=h$;f@V7 z%=vo#YngCltn{@de|?{v+^<+!I+ORyf8m>R<1Hpm_M5atyT?-h?__8Hs!NmqPkM53 w@{To=tS8=W)LGzuNpaqnj~_p7JjHMQ{;Sh&l~Mx+1_lOCS2jPFbxsLQ0B@8?(EtDd diff --git a/android/res/drawable-hdpi/logo_port.png b/android/res/drawable-hdpi/logo_port.png index 74515081f27587e5b0aecbf25a2fae9ba8b2de3f..bb547e1073b0efd5332b28e364a50fbaf05dc49c 100644 GIT binary patch delta 1519 zcmeyQc1B}@O8te@2+uT6Pb~%x1_lN$1_nkJ21W)3h9Cw821X<{6C*PNGXn!d8v_G_ z5F;~K4M?37BP*EQ!@$5G4P{SZU|`UIssZUSWMpFCWnf^K!@$5`#K^=T!oa|=ih+S4 zvz>*Z9%RTq1_xlq%ts6%&DDd>v7mY=4gEM z(jaZV`Z57Qo*>yHdRmVnRtL0jEey-Jdhp35{)L{-lO8?N*dZqKCt{7&4vW{X9%yki z$t#@W^Xt+Jdh~yiQ?~?v-|vs_?md1d`(XX_nOS8=7(+Mrlo%+Pn8pGM9WH-0|4QT<1}J>7$pv-p}{=%Ot9LD%z>b znZIZ{(jPNDw(fCE$&K^I%5G}5?lOvEUAa2j4Yeh;K8h5E1e%`uo*o-!kzW{gI`rBe z_4-7M^}70nbDt?pia4O;!Z9K6DBGzG0*gbHkGjnICM@@l@womZZ?)@e5|+;b8@Wz? zZ2o`necstaPg-V+)cY~a)!+8lkcp+d(e3QE{r$PS3z=BvHEOSYy8cSzO#e&Sy1G>? zU-Fp6uj~5WW7aEg_@^Fgq~gwX;fpxef!~s~!F(@vvpTKL%RgH?$Bserl~yUUj11Fa z*HdwyE6T&G^Z(Dw{Pvys?$V-*JF-rgF);8&XNE+SMELqxCFkerC2pR?RLi7doRnyu zY-nn#n_^;-sB2gr^8y-eER=CIap}M3GNAZ?Y5NvtaWOXfB#gq zU&BeR|9Nt==Hog3JdR(lZe>(9x$15{$Jon_le61(*6Q}?!%s3C!}pt>KhA&s*N51Z zKI<%H`zEZtT>t6FhE2P4#4kE5QMq>L;;F?Yx1}{=pD$g#DTMQI+2d`7RvM>+jIyuQ zCzk|I>r^e5NquUsbyaMgpzkd2BF%leX|n`kHJ;mRU2{rXwX^ZC%n<9O+u*Y9F2%$^6;XSm&%(!r6cYjyM5 z(ZhOPA^wwlBHN9+CrqB?CCs&8$}5&R<^4j}L*HIY|Mw~GW6|RkB5Ga^KXdYCHJ_S4 zYuzN3!>6=8B-ea-6;pc7;4*JJzLq zGi@)2wygDfx!}kev!;n#rc~7LtiDrwRBY+HfEL$9FY~w}9_gyyd(*w>#`|RpjABCW zq*h+md-%{QyzaidpNKS%ruwfB57(@IF8D6oPo96)cKydw5^m`}{d892`{}?_GG^PR ztapp-2};|Ty1#JVkI8}i`aEJh^KMJb(cGwce&K$bRUul_1-Isz-cb+Kay`>g`@XEm z@~Zja;JE@l)!%c(xrMgtZ!cf;@zlk$Z!c?~l@9Xc(tfWMcEeF}-5FoS9|z*4UM}Jh zQ!|Z-+@JVwztLx}^5@N5&Wo13^_*jvE-P{4-g@;rY%@f*=!8YDeQ{w~?{4+BM!T5->i!~r zz3WOXSiVjAp;@?f*8A^PvTTJV=TDtH@lJD1_`=Lzx2Ff1O)?C+DDh~bn{IE5^8%9> zA9dH9o%UJ$8t?iI91>fWy`IBS|9;nxZ*oU}b9`1)d7-n&u%Sm_!n;nZ$usPi%v?R? zWXk<8hxTC-yhQiXqppuC~|EG&Y9jPd|(_j8wuKYd2 z!ob8?qFv0h@@BtGaXRxX?C87j7J~+ZMKdPMOg-Zn#I^77YdOY!_l{Of`@hrPeg3TN z5&{C9kL%AEyRcItBGFPo0^$AsI|J&q}fd@xhcc=9~;K7sC| zd|_5Iruy_&%(7UOW%ZOhvOn}(^qj3;y<6X%jF&j~TKja&a?2(av5hR8N+MSR+*r+$ zw!B&r;c>a{&~f`Wzf_AfpJ+8tOlAztm=J6KL;laZYg#__zEz$NSuT8iWFf!U;k(|2 zHP+|8Z+vXx-|)b=VCnY1SJ@1HdsgjT>)PM&FZaQtU28R~4?N{F_>yvB*&8p%SxyTX z_RPO%{^n&uUuh(wvzs#=S9J@5Hq1icMjmOQ`cJJT4eDn6c@!EfN4EsHcj(<)sOOHyr>j0}vhsNTGQ zjgN8jM2-XwmgBs(w9xgLyUiBoZaa1&Yu|tI*=$X-Y&3GJH{SbLKh^BydVa=v8G>D%oj>p&P_;xjkg%j;|J)RTzh*fA>=b|8fdEd5b*1LZ$Ro?nvu$uq=6s@OCmtTu+s&@^P zFPfX9x7wv@Y0#w13$yPmUftb#$u+cn($-UwW!>7^Y!ga#XBJ;ME_L{XmA&-ujcIeH zeU+b-mvZi~UtV;Q4+`TI3awY~arc{df0oUC)=7s#I4Af&TgK_} z@50N;D(@$4>x}rsrZjoyy}6(7Nv7>eJUZuz`G!+Ra(-mK_qmT^mAfyj#{6Eu;3aX!G4X z^VfnNe9L^_oj$eg^ycHNlj6;GJ8$#mndn^mqu|oj)gtj~?)}ztuk+VEdAT96=v&r_ zvuC>3oO78`WBBA+x6bCz^LY9G>{g7oVOyv2{!Mkqk+iC^>C4^guX=?VExqgaNxXZ- z1f}F9|M?1ZzdrrC)Q^MBo_nX6irxM@cRT-hzJK=Zo#*GakV%K0)K0eKQ(!MHzRd8) z;CHZl+k}%>czD%roAzopr0A65}00000 diff --git a/android/res/drawable-hdpi/qrscanicon.png b/android/res/drawable-hdpi/qrscanicon.png new file mode 100644 index 0000000000000000000000000000000000000000..372f80a8bff7222a94b03bfe596ff2c69359c20e GIT binary patch literal 1376 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84rT@hh9qO>QU(Ub)=X#T08eLUg@U5|w9K4T z1_q6ZwG(YU4m-#kjgMX$q|H|*AjlIWdqhv`QN-$i7OsV18CMTJxx~NF(|OXPM;bfC zWd1~~vD#tr`qcw1jwX49b9{bXdO?r=Pjc#(;P3nW@!h@0?_?jWpFT6I>MN4}?1&`wE)<==TkU-N@ z-_v8mEbTsS5K9%VbVL11yH@==#r--PA*=t;|L5kH2x`rG~*GO?65x}Dv&zdv_(Ars5IM(wpv z*I#Lz>3=C(SGS7gOCGcMbzR?k%zEVw|I}lRRNT2Pd=ck5@LRGrnD51IR;Sf@`DbhA z*fB`H(kf+^kzrcwdMfU7MR|C2{{MNI-@Y^7U0QT;!%n7;3=9mM1s;*b3=F*2L734= zV|E1t0|R@Br>`sf3odzX4Hl)Gm!%9043Z_T5hc#~xw)x%B@E6*sfi`2DGKG8B^e6t zp1uL$jeOz^42&)TJ|V8$0v)0fozilhippJT8r@ntJ$m{*CT4x+mi@MN6P%nUy17sG z^qTDLGsQn(YEba>=$Kh?@v{>W=OiZ0O-Y@XnYkbJFF0ZOu zQC+j5rgmj*-KwUhHEnI{y1F)WcW<09Ve`aETP983I%UeXY16jPn7MP-tX;Eb@0v4b z_uRR=7cJViWXXZ0OAoAEd1%$D!>d;xS+nNoh7HFzZalte(}``{PVd-pX4kHByLX@4 zz32SieHZrayRd)%#Y2ZKA3JvK_=)Q$Pu@6l=GOW1cP?DGbK%0>ix=-+zI^ZU<@;Bz z+`oGD!L@4-uU~(3sX3{rk@!K78@$(aRSv-o1MD{`Kn* z@85rZ|Kan84_`ih{QBwB*Uz87efjeJ+qWOzzyJLC^VhFmzkmP!^XJcBF!=ZH-@pI= z{|Dc(yUD=7z?kIi?vfppx^xBu1LGS{7srr@!*8c&1|KpIXcHIY-_rD;v8zJ+-uKq7 ziiYhETDLFa;Q0Ihes0hy6<*a`_n8M@{?@GBU1^oOX|`^Is`s=KE&ZTR8wBO7l-9GJ zQuW+Z<19z>TiUYhgq_KyYg%&StlZgbT6&wiB4EIvi# z>MQ0ONoP#;G)|w1WZRK=##nE~fisb;JCe^-Om&q1Tv6(maly*WythFwHMxUn>B%W; z3@5BVEwSQmxMm|ygBEwQ(5H$SOtlYLn5z#mXh_?-R4tfp-*-0qB7?{MwlzW;XU{}3 zbQq`ga9aiXpR8gOlf0lKer4JM|E1p-q});}DLvWpWy&MB#sKe&Yt(%Y#@%J!-dM9O zc-`NO7aFcxxSD#q1@-o=Jpa*3Z>D?3mnAJZ9^&jV7dBSa8p(yOJ2>xTtw}!LlD(U~ z7s$R;`X#N?V5q>}&u literal 0 HcmV?d00001 diff --git a/android/res/drawable-ldpi/qrscanicon.png b/android/res/drawable-ldpi/qrscanicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0f38bc25ac9d9e8907825b82a036a81eeaac95ab GIT binary patch literal 1203 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4rT@h2A3sW#~2tGTQi-V13aCb6$*;-(=u~X z85lGs)=sqbIP4&EG(LK1kTzeLfFMth>=8Y!M-i(7TDTU5Wn4Y@|#%hPf>sJr7IGW@Y&hhzm=>p_?`zM)8J`nDB>|?I;sPxfG zU+?F8{AChVJr(WL<;-6+9qEsm9$WV~rsT$XV`VorTXz{nv94U5?S|TtS|3FULjp}t zeNT@Kv&b(DI~{s$k9vKg#d=-+!nw~BCPf@ja^aW|c$Dqb27$$)%12#heG``Z$9PTmmN$i!0K=yrD7{{GzEg-k5-8nxFx zU4NxKz;DUgV7?c-S)Eqr<)5vc zW5*!*N~@GvMuusz>#4ZU73Ja8`Tyr-e*4aRcWKeZ4Lg}WGB7Z37I;J!GcfR82Vq7h zjoB3p3=Hfgp1!W^FSz8nH6`@RTB;cs7$i$vBTAg}b8}PkN*J7rQWHy3QxwWGOEMJP zJ$(bh8~MZ;7#J-Ad_r8g1v*3}I;G`06_vZRbb9pkdkl?wP0aetE&Hvk`)%ze*gH&g za-QhsKH1Z2vbWC^U%#n=fzyJ5r-y`24-20W5jit5YGz{6+~kzGDXH_)Gv;SzF38GW zkejTP<;sJrS07xv_VDJ-$G2}k zxqJ87-Fwd-Jb3Z&;fp6vUO#*G_Qi{LuU~(7^XB8*w;$iV`}F?9=Z_!1eEj(J^XG40 zzI^}s_50VaKfZnY@%{VHA3uKm`t|$w?>~S3`~`!5|Ni~^|Np=DZ}UqG3=E7(-tI2x z(U)ItW?*3S_H=O!u{eG8a%Fszfdu;p#|%%`C9z_6%iM3RY@IeEZF**i$ED8Wpa0!= zJm2u^#2elpzq6&|x7kVyTrw}rGO;}Oy3lg_@2jly{#UbAp#wrfE;Vd)Kh5BesEikqZ5#R+7@ zHn<0Lc5vpmWf~?aJzz4GykK&+jr)Mn>)aiQUF&|$j=%q1T9s{$SeMDmgBRD_K5^bi zK_)z7(VY!K&pA2I_8KpE;QMEtOk;DX%B@s3iGP=(<7S@9Rr@ew+Rr7cq{0jrzngL@ zT$s6L!I^cAf+$VMLlYGRBoq5bsyW-pqeEj41%wqL>qX!r7KS?`vPx)Rf zi{q}mi&xZw*NcR#@n)MK=qWY%seoV#BfE>H3`3vi=8Y!M-i(7TDTU5Wn4Y@|#%hPf>sJr7IGW@Y&hhzm=>p_?`zM)8J`nDB>|?I;sPxfG zU+?F8{AChVJr(WL<;-6+9qEsm9$WV~rsT$XV`VorTXz{nv94U5?S|TtS|3FULjp}t zeNT@Kv&b(DI~{s$k9vKg#d=-+!nw~BCPf@ja^aW|c$Dqb27$$)%12#heG``Z$9PTmmN$i!0K=yrD7{{GzEg-k5-8nxFx zU4NxKz;DUgV7?c-S)Eqr<)5vc zW5*!*N~@GvMuusz>#4ZU73Ja8`Tyr-e*4aRcWKeZ4Lg}WGB7Z37I;J!GcfR82Vq7h zjoB3p3=Hfgp1!W^FSz8nwOG?*cPBD1Fi4iTMwB?`=jNv7l`uFLr6!i7rYMwWmSiZn zd-?{1H}Z)yFfh&w@Ck9{7U&R_=#-Z0R8;O#)9BXH>Cw~gF*NEmG3zt8?69P?nb9$`Vq<3~B+f}rnVX(5 zKQnVdR`!DIoQ1i$3-j_8b%yrjH*Sw+S2s;U*$H7jcCR@K+9YHC{3+`Oix zWo>Kgy7u<<9Ubd?dN%g;ZJID)^Mr|8CQaTtWy-dxQ@2l_zGKFW9W!R`oIQKjoH@Ja z&fBwS@&2Vt4=i1FVA-;RD_0&~z52-7wMW;jKel1R@r@ggZ`^od)20(!x1QR%?ezBT zXLjy7yLisHz6<;JUp#p5(xF3_4@bM|M~rg&mTX2 z{q*VUmoMMHe*OOS>yK~WetiG_^T&^0KY#xE_3QWV-+%u6`3naB{{8#+|NsBLh0j14 zlrhQM-KA3|QSA%^1Cywyi(`n#@wZc>qc1xMv|VnKYB4*i#J<%{;DTA0@|LdW%en_|X`@$4(t8Vc-;ikD`%Q?zbl^MNVc6P_@H`AvJe zrmx}D9-Z@f0gX%DZhHlZG1rxR`G2?}P|0A;wRtyU6BcXB1qgLG6g)U)aK&`_aclO# zDV>+AECgDb7^{LO{He&Wn%-U|oqkT|`Df#G_0_qh6F7X{RIWZ}+8q1q*)OBq>0%pW zZ@pS2+EGW;~#(s`+9RgMDd-YYyvS{b9~J6F6#_{z*{p`llG z7+9i}Wu&w7`+ZlvI=9%_zjy7rdGS%(PR_Ov`f%t|gVPPoJEs&KOrm4je@#g`SNq56 zaMPK)kE$N3{me=IsWBz^ri03<(CSNtkxU^X_+~x z3=A3*YbV-z9Cna78XvthNSm)rK#(U$_K2R=qlnc3EnEx3GOiwca*2PTr}Lyok2H3O z$^3~}W3|KL^{WS398K~H=lJ}(^nxD!pXAgn!Qc1$zXnoesUWN4-ALV!f_@;oN5mlOhf%xo}JfJj!-zgTUfY<)bdMz6s0yV?3@u z$y@C@n}p@Fz(%fsWMV0AbUV9ke}C@oLME1ZjoNFU zuD{Ya)BjSou5J~}mpo?i>$<-8nDxpV{;9_rskn1p_#)1A;J0LLFyD*atWK-*^3T@J zv15>YrB%u-Bg3@V^;F#Fit_O4{QvVZzkO%EyR_)yhMi0w85kHi3p^r=85nr4gD|6$ z#_S3P1_t&LPhVH|7hLk(+9D#~R$XFXV2~_vjVN)>&&^HED`9XhN=+}`+Lg6+tLp1lH8rhiZ(rZpxuL6TLto#fsZ+Pl zn7MQ2tevxF?V2-Z_uRR==g!+RZ~mV7^Y<=Tuy@g-eTx_GU$*Sv^5q9ttT?oC<)PK9 zkE~mFY~A`}8#Wx@wCTjwt*5qbJH2h&>Fqnt?AUQ;=gzY`cb(n4@4~))7xwSJc<|t* z!-p>)IePWz(W}RfT|0jK+KCg_PoBJS>eP)>r*ED)bL;HcTW8PSK6mc+`SW)!UAlMq z^1Ulp9$dZp;M%o^*RDUje*MwSn~!hZdUE^r)4O+{-Mjbv{{80<9=v$?@a3aNFCRU6 z_4x6tCr@5Kefs*@vp3J5zkTuI-OHEnU%&qF_U*@a?>@bM|M~rg&mTT~`S|h6$B$n> zefs+O^S3WwzJLAt3*85o#KJY5_^GVZ;d`7-*lgTOKG35S_kY_z%>8Z;ELTvtEpbae<6dB0k# zeNn?U-&{eFs3XtbtBHG;q+c?;8MWni-RpguKUdGQnR)h&iNJ(OlP29tin`2jcIVp} z&1Gvug(vJgy7E5nh4aV#cQf7lS%2=j%7gg~8sf{7_bfY<8g}E}?$3LE|MV1=T;O6F zao+dP!qgjoFFLc%xo+@P>YL89y45O7Y5@nh@8&whuI=WS;cmR=`du-Z3+^m6PR3_m zD#O*g0Y81}?^9Ip!u4K6C7NvcgYpLhy{WRV*U-b;rZ=Nb$!KXE2?iUUZepek{eaESPZj~wXoAxv0rw4j6+}WRT*5Uz= zpn$8f&E>ndWU}Oynfw+=?&7T$Dt`R*%Ck3oa}>-oc2!T>u(x8yy?>?lU7xB7u9U4_ zzwgQ2aP#t0+KU2Mtt;OCzIyv@&w8^kzDbwO4{rUl@nTKqo82#(ijPYdzqBx{KV1IA zHGWm&es<}j{Gfir`H9_vvlkk@o zlD}g2=6;LW7W==@>cyS#@>4x8CLJymYaPJL-i*B@|RuRYO(QGMlO> WUl#hSRlvZ&z~JfX=d#Wzp$Pz2lOU4- literal 0 HcmV?d00001 diff --git a/android/res/drawable-xxhdpi/qrscanicon.png b/android/res/drawable-xxhdpi/qrscanicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a12cbcc85768761494393af6678ffa7570e10c63 GIT binary patch literal 1809 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H9Lx+13>Rhybuln7wq`mz2Y5O=D-;yvr)B1( zGB9XNtet4*$tC`Up3ajVJ<`}A zCi5p^jnxi|*RLLEaWu&*oa6KB(hGX@f09$T1b^S}kMHh1ekc22{q&hxWk(o8H}{kn zDW()_JADvU#GLw(c^DVqLj9+YPlPwLXdzh6I|P z`ko#eW|3bQb~^Oh9`*V}i}kwtg>#=NOo}+5}i}*3j)!+8lkcp+d(e3QE{r$PS3z=BvHEOSY zy8cSzO#e&Sy1G>?U-Fp6uj~5WW7aEg_@^Fgq~gwX;fpxef!~s~!F(@vvpTKL%RgH? z$Bserl~yUUj11Fa*HdwyE6T&G^Z(Dw{Pvys?$V-*8+I~%WME+6EbxddW?vEr8!myBmfkCpwHKN2hKQ}iuuY|$5C^fMpHASI3vm`^o z-P1Q9ypd0wfq`*DfKP}kw?K!eM5nY|r=oI~nnt&lPLG~`kBM2Ixn;kVb-%6M1SjW- zF0PY2y(aqyObrX45fM2vGHPaY%&geh*$Ihr5|id8r_4=BotKt2FFj*^X6Ayd>;<{G z3-b#W7ZxrqDq2!pyriUbX?gjwii+h`RV%7%R@Bt4tgm0y(6G9>c}+{p+P1cJ?d|J3 zJ2!N7ZRqLQ*xS3Qzkl<@Nn0jQ-ZpL8_UY4i%$Tub=B%BwX6>3id)M5#yB96mw`lSH zB})!0U3y^IvV$vD99p^Z@TygZ*Q`0ZZr!mB8;)<;a%$_=Q(L#4-oE|J_8n(->^QS? z=h+u+vm^UzIgHO<;!<3U%r3y=HuJ9 zAK$+H^zPlK_wPTy|M2g)|NZ;-@Bjb*lkN6QVPIfjO!9Vj>Ho^q{F8x!+0fI)F{I+w+u6UP zF9k>(S66T<+c<&a?K9TXSMq{FGQvU@xJpK@TpBC7`=ak=o-Q3_=9~Lt&L6vR%}V^n z%?O+K&)v_JKd+H9D?4{j)`E`#3Kp< z?r}TcB*4))VQH(?CaFan;#zArb@KTlH%{LbN}fFd`&?(fG4RUp5S2X4 zkvieLfdY$@LWq(C7q5io6W-RQP`%^5f|nRM{+@Sw9>ROwz$r3xQ{iESh5YP)jhN#L zHvfHEX1UY)ptQ=70$Y$y=Y=*$H8K??mkM&d7CRNt^U2GD?;?lnHC+=|!&5UR+zC`s zzdpN!`{&8$Z*GejTKS6YI<-KiVpd7y^IuDv?nzHNzh|y%qtOSUay_okZ=Wvt%(ZRV zcgv*Y5aYXdE;c94_nw;bL1{80KRd&;gEwEfKP=Q(_xDr#Ej<~#g-6dirRNKk=l`+1 z9ChmJiH)$WF^;YuNekvnD`s#JAa7V^oFqs3vxzm~ieO8kaS?@V(!FEK29^m(_Drrh>FH!X`6 zp82rtpXSRIGkTW_B-*yP1s;>y(lQ}Xzmd}^T0G$OMk&r%r*+4(R7|@5Of?o=bW>z| z&aw;HTnpLLxOxLPWKDxl_}z=FoNpZ5k?r@Wv}>}Tp~S>(;hFd3*PMDO{HxXCT>#V0 z>dJQ?E0&AgF7f`OvE=33ic0^_JDOW3Fg6HqTy@g;FX$;As~-D8ZqtYVn|6saLDMxu a!jb&>;uD&iD|cCeggjmST-G@yGywoTM1cMP literal 0 HcmV?d00001 diff --git a/android/res/drawable-xxxhdpi/qrscanicon.png b/android/res/drawable-xxxhdpi/qrscanicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f2785d9ad1859f378cede013ed642a7e1d9747a5 GIT binary patch literal 2123 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Lx+145>_WOc@v$TQi-V13aCb6$*;-(=u~X z85lGs)=sqbIP4&EG(LK1kTzeLfFMth>=8Y!M-i(7TDTU5Wn4Y@|#%hPf>sJr7IGW@Y&hhzm=>p_?`zM)8J`nDB>|?I;sPxfG zU+?F8{AChVJr(WL<;-6+9qEsm9$WV~rsT$XV`VorTXz{nv94U5?S|TtS|3FULjp}t zeNT@Kv&b(DI~{s$k9vKg#d=-+!nw~BCPf@ja^aW|c$Dqb27$$)%12#heG``Z$9PTmmN$i!0K=yrD7{{GzEg-k5-8nxFx zU4NxKz;DUgV7?c-S)Eqr<)5vc zW5*!*N~@GvMuusz>#4ZU73Ja8`Tyr-e*4aRcWKeZ4Lg}WGB7Z37I;J!GcfR82Vq7h zjoB3p3=Hfgp1!W^FSz8n^^A7++|FWPV2~_vjVN)>&&^HED`9XhN=+!wvhKIFn_%xS(aCwD zi|Zse_sO1Klf8YW`1(!t511MlI4vl6dPwN>h{&0dQ8S}sX2r(Nj*Fk2kT@qXX>M}L z+?3RLX=(G)Gv;SzF38GWke#zIH+NxP-lF`1#YII+ii?+&lrAkVUsh4Eyt-yZL&NIE z#??(tYnq$aw6?BmZ(rZhvA(l&Ls!>^?(U5}JsW#_H}&;x>hIq?VZ!DK6Sqv9v}Mxd zt<$IPm@#9=jF~%U&e}P9_O7{ech8--XWslh^XKnfw0QsG#ru~oJ+O4yfo01ME?<6d z#fn2KR~}lm>hS8-M^>*nvUcs!b?c6;Uw?eVhT|JI9^bg}#OBQ>H*Yz)Wy`6pTTgA> zc6!^k)7y8P*|Foy&Yfp>?mD|`*SX!h&+XlNVeh^R`}SSfzyIRFgO?5-ynN`;<->Sm9K7I4dnOo=1-9CT*&V>tiFJ8QR>C(N+ zm+xJ^eE-Uo`&X|%xOVN~wd)VBUw?Gt#-p1zAK$w5fByF6%lEHezkmJu+PKH z(U${6+VUM!JiCRzAKNRqT`BMA-hewh*qfMFJ`xkRa5%bXg@@|_vCMxi9}P`b*B0?V zG0XgWPk#3AxpTfRbeL2fb<Ol(4AC&AY&D|4>V&fq`X?!iUc@BwN26YF*}%tpW`UED8*a91;2xHs$oc{C{ClrROiJ;O$dpEd3^)(kbp?dGlZ?@8{at%e?Nf zuL}u(8gP*5T~()e{-&TO4jtxSYi9*8Ugz$dfBK=+YmSxqHCMY(ah#@?LOa!-6@^5B4ff&7SU1yX@VM7h!yj z_q$70E|D(d^q4Czebt0@XP&0>*;yfr<2U&_e6TeHhscZXRhe^Ho<_*(ZVR2$F#Fm` zbIY=Rv1nDlhFZ1KqUGh&XM8($T1=y$<;p28OH&&UPg|iI_TGP;UMx|Qv39&EIY%j| z;pwlP)*p9HJ-s#SU;H0aA06dvi~A?dFNbP|d#W%0Dx)?ha_PO_X?L{bmU~xrt((1U zmAZ(23CB^gs4bcxo)SzS_g%!xGk zay^{!xR0(A0~0vPf)A`(eNk*T%Q^|M>qQ(q#x+kbG4E#CmH+16(zN%^oz4wsb=O}B zcy;d98j+iZnq^FTz8^{3G0WsI+vCT63hJj01mEGf@l8@Hu2Jynr{L|ou71>f5Zczr zc5G?GtBa2!IKFr<_-}1u#;sC5f#sE~`hmR3EPU4f3fpI2*l&Nhl4*+MwRcO;G&1Gr zzmKc^eX8Ku%uiWIR>t(3?Q~qhFuP#x^VfZ|&aaF*%m4q~bXPm~nO48_V%IqEuT1;w zG*2Wqq2W(a)RxIhv@hyR{LXC12KG@(;{|@>RWDjs`*fFi{CJeZ;h+F&5FlsPl0S@7 W@;t8dpMA#(683cUb6Mw<&;$UVDk9bZ literal 0 HcmV?d00001 diff --git a/android/res/xml/shortcuts.xml b/android/res/xml/shortcuts.xml index 8af59ce..899301f 100644 --- a/android/res/xml/shortcuts.xml +++ b/android/res/xml/shortcuts.xml @@ -1,7 +1,7 @@ Date: Thu, 31 Oct 2024 14:50:01 +0100 Subject: [PATCH 1043/1428] Document that Qt fixed this. --- guis/Flowee/QRScanner.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index 059a6a9..146ffc9 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -275,8 +275,9 @@ FocusScope { camera.start(); flashFrame.visible = camera.isTorchModeSupported(Camera.TorchOn) } else if (Qt.platform.os !== "linux") { - // at least on Linux stopping a camera and turning it on again fails with - // "Camera is in use" + // at least on Linux up until, including Qt6.7 stopping a camera and + // turning it on again fails with "Camera is in use" + // When we demand Qt6.8, we can remove the above if() camera.stop(); } } -- 2.54.0 From 7a7270e9d8a626d1ad052106ce8329d1c172144b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Oct 2024 15:32:36 +0100 Subject: [PATCH 1044/1428] Fix linter warnings from QML --- guis/Flowee/QRScanner.qml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index 146ffc9..8981567 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -16,7 +16,6 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 import QtQuick.Shapes import QtMultimedia import Flowee.org.pay; @@ -50,7 +49,7 @@ FocusScope { // The 'cutout' overlay. Shape { id: cutout - anchors.fill: parent.fill + anchors.fill: parent smooth: true opacity: 0.5 @@ -66,13 +65,13 @@ FocusScope { strokeColor: "transparent" startX: 0; startY: 0 - PathLine { x: width; y: 0 } - PathLine { x: width; y: height } - PathLine { x: 0; y: height } - PathLine { x: 0; y: height / 2 } + PathLine { x: root.width; y: 0 } + PathLine { x: root.width; y: root.height } + PathLine { x: 0; y: root.height } + PathLine { x: 0; y: root.height / 2 } // move to the center part. - PathLine { x: cutout.x1; y: height / 2 } + PathLine { x: cutout.x1; y: root.height / 2 } PathLine { x: cutout.x1; y: cutout.y1 + cutout.radius } PathArc { @@ -103,8 +102,8 @@ FocusScope { radiusX: cutout.radius radiusY: cutout.radius } - PathLine { x: cutout.x1; y: height / 2} - PathLine { x: 0; y: height / 2 } + PathLine { x: cutout.x1; y: root.height / 2} + PathLine { x: 0; y: root.height / 2 } PathLine { x: 0; y: 0 } } } -- 2.54.0 From 527c1c3dc5f74c669849fe16bf28ce2940ac2f0c Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 31 Oct 2024 20:19:05 +0100 Subject: [PATCH 1045/1428] Various possible fixes for camera usage. --- guis/Flowee/QRScanner.qml | 9 ++-- src/CameraController.cpp | 91 +++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index 8981567..15c3895 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -282,12 +282,11 @@ FocusScope { } } - Camera { - id: camera - active: false - } CaptureSession { - camera: camera + camera: Camera { + id: camera + active: false + } videoOutput: videoOutput } VideoOutput { diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 1cdf921..f546aac 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -54,7 +54,6 @@ class CameraControllerPrivate { public: explicit CameraControllerPrivate(CameraController *qq); - ~CameraControllerPrivate(); // Configure the camera void initCamera(); // check if we need to load the camera. @@ -247,43 +246,18 @@ void CameraControllerPrivate::checkState() cameraStarted = true; QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) { currentFrame = frame; - - if (!m_scanningThread) { - m_scanningThread = new QRScanningThread(this); - QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); - m_scanningThread->start(); - } }); + + assert(m_scanningThread == nullptr); + m_scanningThread = new QRScanningThread(this); + QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); + m_scanningThread->start(); + logDebug() << "Camera active is now true"; emit q->cameraActiveChanged(); // this emit makes QML activate the camera } } -void CameraController::initCamera() -{ - d->initCamera(); -} - -QString CameraController::helpText() const -{ - return d->helpText; -} - -QRScanner::ScanType CameraController::scanType() const -{ - if (d->scanRequest == nullptr) - return QRScanner::InvalidType; - return d->scanRequest->scanType(); -} - -void CameraController::setHelpText(const QString &text) -{ - if (d->helpText == text) - return; - d->helpText = text; - emit helpTextChanged(); -} - // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) @@ -406,7 +380,7 @@ void QRScanningThread::run() break; case QRScanner::PaymentDetailsTestnet: assert(false); // TODO - break; + return; default: logFatal() << "Unknown scanType provided"; return; @@ -748,33 +722,64 @@ bool CameraController::visible() const void CameraController::qrScanFinished() { QString resultText; - if (d->m_scanningThread) + if (d->m_scanningThread) { resultText = d->m_scanningThread->text; - - delete d->m_scanningThread; - d->m_scanningThread = nullptr; - - d->cameraStarted = false; - emit cameraActiveChanged(); - d->visible = false; - emit visibleChanged(); + d->m_scanningThread->deleteLater(); + d->m_scanningThread = nullptr; + } + // stop copying video frames + assert(d->videoSink); QObject::disconnect(d->videoSink, nullptr, this, nullptr); + d->visible = false; + emit visibleChanged(); + setHelpText(QString()); if (d->scanRequest) { d->scanRequest->finishedScan(resultText, QRScanner::Camera); d->scanRequest = nullptr; } + QCamera *cam = qobject_cast(d->camera); if (cam) cam->setTorchMode(QCamera::TorchOff); if (d->torchEnabled) { + // don't use the simple setter as that one is doing much more. d->torchEnabled = false; emit torchEnabledChanged(); } - setHelpText(QString()); + // Have a bit of delay with actually turning off the camera. + QTimer::singleShot(100, [=]() { + d->cameraStarted = false; + emit cameraActiveChanged(); // makes the QML 'stop()' the camera. + }); } void CameraController::checkState() { d->checkState(); } + +void CameraController::initCamera() +{ + d->initCamera(); +} + +QString CameraController::helpText() const +{ + return d->helpText; +} + +QRScanner::ScanType CameraController::scanType() const +{ + if (d->scanRequest == nullptr) + return QRScanner::InvalidType; + return d->scanRequest->scanType(); +} + +void CameraController::setHelpText(const QString &text) +{ + if (d->helpText == text) + return; + d->helpText = text; + emit helpTextChanged(); +} -- 2.54.0 From 2012d6df6d09864d4dd074f76235b355878b1a2f Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 28 Nov 2024 23:13:00 +0100 Subject: [PATCH 1046/1428] Avoid a define for the wallet log level. This is a general cleanup, making the log levels not be defines but just a constexpr or hardcoded. Just like all of them. --- CMakeLists.txt | 1 - src/WalletCoinsModel.cpp | 2 +- src/Wallet_p.h | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ca4518..b88b716 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,7 +161,6 @@ if(NOT ANDROID) endif() add_compile_definitions(LOG_DEFAULT_SECTION=10000) -add_compile_definitions(LOG_WALLET=10001) if (NetworkLogClient) add_compile_definitions(NETWORK_LOGGER) elseif (ANDROID) diff --git a/src/WalletCoinsModel.cpp b/src/WalletCoinsModel.cpp index a8badb9..47662cc 100644 --- a/src/WalletCoinsModel.cpp +++ b/src/WalletCoinsModel.cpp @@ -75,7 +75,7 @@ QVariant WalletCoinsModel::data(const QModelIndex &index, int role) const auto iter = m_rowsToOutputRefs.find(index.row()); if (iter == m_rowsToOutputRefs.end()) { - logDebug(LOG_WALLET) << "This looks wrong"; + logDebug(10002) << "This looks wrong"; return QVariant(); } Wallet::OutputRef outRef(iter->second); diff --git a/src/Wallet_p.h b/src/Wallet_p.h index ebbcb81..bf5c311 100644 --- a/src/Wallet_p.h +++ b/src/Wallet_p.h @@ -23,7 +23,6 @@ #include #include #include -#include class Wallet; @@ -38,6 +37,8 @@ constexpr int BYTES_PER_OUTPUT = 149; constexpr int MAX_INPUTS = 670; constexpr int MATURATION_AGE = 100; //< the amount of blocks a coinbase takes before we can spend it +constexpr int LOG_WALLET = 10001; + namespace WalletPriv { // we may have transactions that spend outputs created within the same block. -- 2.54.0 From 68116d978f0b6fcde7f92dde5842c561c9fbde77 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 28 Nov 2024 23:20:11 +0100 Subject: [PATCH 1047/1428] This chooses the cmake boost finding behavior CMake details for finding boost have been shipped for years inside of boost, this makes cmake use that upsteam info to configure boost and avoids problems when a newer boost than cmake is found. --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b88b716..4c8d072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,11 @@ else () find_package(Qt6 COMPONENTS DBus LinguistTools) find_package(ZXing REQUIRED) endif() +if (CMAKE_VERSION VERSION_GREATER "3.29.9") + # use the upstream boost info instead of the cmake-shipped one for finding + # this policy was introduced in cmake 3.30 + cmake_policy(SET CMP0167 NEW) +endif() find_package(Boost 1.78.0 REQUIRED filesystem chrono thread) include_directories(${Boost_INCLUDE_DIRS}) -- 2.54.0 From aa7096a2d2551a4ac273452102edf37b1c7b09a0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 29 Nov 2024 14:59:07 +0100 Subject: [PATCH 1048/1428] Avoid doing work when no request is open. --- src/CameraController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index f546aac..c953faf 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -584,7 +584,7 @@ void CameraController::startRequest(QRScanner *request) void CameraController::abortRequest(QRScanner *request) { - if (d->scanRequest == request) { + if (request && d->scanRequest == request) { // The scanning thread will abort and nicely shutdown on change of this variable d->lock.lock(); d->cameraStarted = false; -- 2.54.0 From 8bbea6c0fef3b7ddd285a37338fb353ac4a2087b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 29 Nov 2024 15:40:15 +0100 Subject: [PATCH 1049/1428] Follow upstream wrapping of shared_ptr --- src/NetDataProvider.cpp | 2 +- src/Wallet.cpp | 6 +++--- src/Wallet.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index c02c5e0..6bafefa 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -308,7 +308,7 @@ void NetDataProvider::updatePeers() } if (peer->status() == Peer::Connected && iter->segment == 0) { - auto segment = peer->privacySegment(); + auto segment = peer->privacySegment().lock(); if (segment) { iter->segment = segment->segmentId(); changed = true; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 30de4b4..0f46f62 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -44,7 +44,7 @@ Wallet *Wallet::createWallet(const boost::filesystem::path &basedir, uint16_t se { Wallet *wallet = new Wallet(); wallet->m_basedir = basedir / QString("wallet-%1/").arg(segmentId).toStdString(); - wallet->m_segment.reset(new PrivacySegment(segmentId, wallet)); + wallet->m_segment = std::make_shared(segmentId, wallet); if (name.isEmpty()) wallet->setName(QString("unnamed-%1").arg(segmentId)); else @@ -1519,9 +1519,9 @@ void Wallet::broadcastUnconfirmed() } } -PrivacySegment * Wallet::segment() const +std::shared_ptr Wallet::segment() const { - return m_segment.get(); + return m_segment; } void Wallet::createNewPrivateKey(uint32_t currentBlockheight) diff --git a/src/Wallet.h b/src/Wallet.h index 57a53b6..a94784a 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -121,7 +121,7 @@ public: */ void checkHeaderSyncComplete(const Blockchain &blockchain); - PrivacySegment *segment() const; + std::shared_ptr segment() const; /// Create a new private key, can not be called on a HD wallet. void createNewPrivateKey(uint32_t currentBlockheight); @@ -457,7 +457,7 @@ private: int scoreForSolution(const OutputSet &set, int64_t change, size_t unspentOutputCount) const; - std::unique_ptr m_segment; + std::shared_ptr m_segment; mutable QRecursiveMutex m_lock; /// used to determine if we need to persist the wallet bool m_walletChanged = false; -- 2.54.0 From d16695c35baea40f7a9da915eb512757d1129f1a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 29 Nov 2024 15:50:42 +0100 Subject: [PATCH 1050/1428] Qualify properties when not clear. --- guis/mobile/AccountSyncState.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index dcc912c..d0ea476 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -41,7 +41,7 @@ Item { } Connections { - target: account + target: root.account function onLastBlockSynchedChanged() { checkIfDone(); } } @@ -70,7 +70,7 @@ Item { y: root.uptodate ? 0 : progressbar.height + 13 wrapMode: Text.Wrap text: { - if (isLoading) + if (mainWindow.isLoading) return ""; var account = portfolio.current; if (account === null) -- 2.54.0 From f72663df66eaf7a328d73cac4af553ae43d3b958 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 29 Nov 2024 19:37:10 +0100 Subject: [PATCH 1051/1428] Add backup sync progress to bar --- guis/ControlColors.js | 4 +-- guis/Flowee/Progressbar.qml | 59 ++++++++++++++++++++++++-------- guis/mobile/AccountSyncState.qml | 12 +++++-- src/AccountInfo.cpp | 11 ++++++ src/AccountInfo.h | 5 +++ src/Wallet.cpp | 5 +++ src/Wallet.h | 4 ++- 7 files changed, 81 insertions(+), 19 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index 8c77233..33840ce 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -42,7 +42,7 @@ function applyDarkSkin(item) { item.palette.highlight = "#76bfc7" // mouseover on popup, slider thumb, outline of active textfield, selected-text background item.palette.highlightedText = "#090909"// selected text - // Desktop is the only one that used tooltips + // Tooltips colors are currently unused item.palette.toolTipBase = "#629c7b" // tooltip background item.palette.toolTipText = "#000000" // tooltip text and outline item.palette.shadow = "#90e4b5" // tooltip shadow @@ -70,7 +70,7 @@ function applyLightSkin(item) { item.palette.highlightedText = "#f9f9f9"; item.palette.toolTipBase = "#ffffff"; item.palette.toolTipText = "#000000"; - item.palette.shadow = "#28282a"; + item.palette.shadow = "#7abd84"; item.palette.midlight = "#5c9e67" item.palette.link = "#45a7d7"; diff --git a/guis/Flowee/Progressbar.qml b/guis/Flowee/Progressbar.qml index b4267c1..6901ade 100644 --- a/guis/Flowee/Progressbar.qml +++ b/guis/Flowee/Progressbar.qml @@ -18,7 +18,7 @@ import QtQuick Rectangle { - id: progressbar + id: root height: 40 color: "#00000000" border.width: 2 @@ -27,22 +27,38 @@ Rectangle { // should be calculated to be between 0 and 1 required property double progress; + // Like progress, but overlaying the main progressbar in a separate color + property double progress2: 0; // should always be smaller than progress + + property var overlayGradient: Gradient { + GradientStop { position: 0; color: palette.shadow } + GradientStop { position: 1; color: palette.midlight } + } Rectangle { + // just the first 10 pixels width: Math.min(10, parent.width * parent.progress) y: 2 height: parent.height - 4 color: palette.highlight radius: 10 + Rectangle { + id: overlayProgressBar + // for progress2 + visible: root.progress2 > 0 + width: Math.min(10, root.width * root.progress2) + height: parent.height + radius: 10 + gradient: root.overlayGradient + } } - Rectangle { id: bar x: 5 y: 2 width: { - var full = progressbar.width; - var w = full * progressbar.progress + var full = root.width; + var w = full * root.progress w = Math.min(w, full - 5) // the right max w -= 5; // our X offset Math.max(0, w); @@ -50,25 +66,40 @@ Rectangle { height: parent.height - 4 color: palette.highlight clip: true + Rectangle { + // for progress2 + visible: root.progress2 > 0 + width: Math.max(0, Math.min(root.width * root.progress2, root.width - 5) - 5); + height: parent.height + gradient: root.overlayGradient + } Label { id: percentLabel - text: (100 * progressbar.progress).toFixed(0) + "%" + text: (100 * root.progress).toFixed(0) + "%" y: 5 - x: progressbar.width / 2 - 10 - width / 2 + x: root.width / 2 - 10 - width / 2 color: palette.highlightedText } } Rectangle { width: { // just the last 10 pixels. - var full = progressbar.width; - return Math.max(0, full * progressbar.progress - (full - 10)); + var full = root.width; + return Math.max(0, full * root.progress - (full - 10)); } - x: progressbar.width - 10 + x: root.width - 10 y: 2 height: parent.height - 4 color: palette.highlight radius: 10 + Rectangle { + // for progress2 + visible: root.progress2 > 0 + width: Math.max(0, root.width * root.progress - (root.width - 10)); + height: parent.height + gradient: root.overlayGradient + radius: 10 + } } Rectangle { id: groove @@ -77,15 +108,15 @@ Rectangle { color: palette.light clip: true x: { - var full = progressbar.width; - var distance = progressbar.progress * full; + var full = root.width; + var distance = root.progress * full; distance = Math.min(distance, full - 5); // and right distance = Math.max(5, distance); // avoid the left rounding area return distance; } width: { - var full = progressbar.width; - var w = full - progressbar.progress * full; + var full = root.width; + var w = full - root.progress * full; w -= 5; w = Math.min(w, full - 10); // the rounded corners return w; @@ -93,7 +124,7 @@ Rectangle { Label { text: percentLabel.text y: 5 - x: progressbar.width / 2 - 5 - width / 2 - parent.x + x: root.width / 2 - 5 - width / 2 - parent.x } } } diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index d0ea476..e7a1099 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -58,8 +58,16 @@ Item { return 0; let currentPos = root.account.lastBlockSynched; let totalDistance = end - startPos; - if (totalDistance <= 6) - return 100; // uptodate + let ourProgress = currentPos - startPos; + return ourProgress / totalDistance; + } + progress2: { + let startPos = root.startPos; + let end = Pay.expectedChainHeight + if (startPos == 0) + return 0; + let currentPos = root.account.lastBlockSynched2; + let totalDistance = end - startPos; let ourProgress = currentPos - startPos; return ourProgress / totalDistance; } diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index fe9e44a..845a431 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -49,9 +49,13 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) emit lastBlockSynchedChanged(); emit timeBehindChanged(); }, Qt::QueuedConnection); + connect(wallet, &Wallet::backupBlockHeightChanged, m_wallet, [=]() { + emit lastBlockSynchedChanged2(); + }, Qt::QueuedConnection); connect(wallet, SIGNAL(encryptionChanged()), this, SLOT(walletEncryptionChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(userOwnedChanged()), this, SIGNAL(userOwnedChanged()), Qt::QueuedConnection); connect(wallet, SIGNAL(transactionConfirmed(int)), this, SIGNAL(balanceChanged()), Qt::QueuedConnection); + connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); } @@ -105,6 +109,13 @@ int AccountInfo::lastBlockSynched() const return m_wallet->segment()->lastBlockSynched(); } +int AccountInfo::lastBlockSynched2() const +{ + if (!m_wallet->segment()) + return 0; + return m_wallet->segment()->backupSyncHeight(); +} + QDateTime AccountInfo::lastBlockSynchedTime() const { if (!m_wallet->segment() || m_wallet->segment()->lastBlockSynched() < 1) diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 6fa1664..654c17f 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -37,6 +37,7 @@ class AccountInfo : public QObject Q_PROPERTY(int historicalOutputCount READ historicalOutputCount NOTIFY utxosChanged) Q_PROPERTY(int id READ id CONSTANT) Q_PROPERTY(int lastBlockSynched READ lastBlockSynched NOTIFY lastBlockSynchedChanged) + Q_PROPERTY(int lastBlockSynched2 READ lastBlockSynched2 NOTIFY lastBlockSynchedChanged2) /** * This is the oldest block that this account ever might have seen transactions at. @@ -101,7 +102,10 @@ public: void setName(const QString &name); QString name() const; + /// returns the last and highest block we inspected for transactions. int lastBlockSynched() const; + /// returns the backup height which is re-checked for transactions. + int lastBlockSynched2() const; QDateTime lastBlockSynchedTime() const; QString timeBehind() const; @@ -203,6 +207,7 @@ signals: void utxosChanged(); void nameChanged(); void lastBlockSynchedChanged(); + void lastBlockSynchedChanged2(); void timeBehindChanged(); void isPrimaryAccountChanged(); void userOwnedChanged(); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 0f46f62..52e9bbf 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1444,6 +1444,11 @@ void Wallet::setLastSynchedBlockHeight(int height) } } +void Wallet::updateBackupBlockHeight() +{ + emit backupBlockHeightChanged(); +} + // called every app-start we reach the tip void Wallet::headerSyncComplete() { diff --git a/src/Wallet.h b/src/Wallet.h index a94784a..fdb87da 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -115,6 +115,7 @@ public: void rebuildFilter() override; /// Let the wallet know that it is up-to-date to \a height void setLastSynchedBlockHeight(int height) override; + void updateBackupBlockHeight() override; void headerSyncComplete() override; /** * Check if the headers are high enough for our usage. @@ -407,7 +408,8 @@ signals: void balanceChanged(); void utxosChanged(); void appendedTransactions(int firstNew, int count); - void lastBlockSynchedChanged(); + void lastBlockSynchedChanged(); // see PrivacySegment::lastBlockSynched + void backupBlockHeightChanged(); // see PrivacySegment::backupSyncHeight void userOwnedChanged(); void transactionChanged(int txIndex); void transactionConfirmed(int txIndex); -- 2.54.0 From bc2ee8abe664cdcbd0b332498389821f48642b13 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 29 Nov 2024 20:07:29 +0100 Subject: [PATCH 1052/1428] Auto hide sync status when we're up to date. This upgrades us to no longer use the expected block height based on current time, but instead uses the certainty we get from asking various peers for their view of the world. Now when we are certain that we're at the same tip as the rest of the world, we can safely hide the 'up to date' text and make the UX again a little bit simpler. --- guis/mobile/AccountSyncState.qml | 12 ++++++++++-- src/AccountInfo.cpp | 24 ++++++++++++++++++++++++ src/AccountInfo.h | 7 +++++++ src/CMakeLists.txt | 1 - src/FloweePay.cpp | 23 +++++++++++++++++++++-- src/FloweePay.h | 31 +++++++++++++++++++++++++------ 6 files changed, 87 insertions(+), 11 deletions(-) diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index e7a1099..3231d1e 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,15 @@ import "../Flowee" as Flowee Item { id: root - height: indicator.height + 3 + (uptodate ? 0 : progressbar.height + 10) + visible: !account.uptodate + height: { + if (account.uptodate) + return 0; + var h = indicator.height + 3; + if (!root.uptodate) + h += progressbar.height + 10; + return h; + } property QtObject account: null property bool uptodate: false property int startPos: account.initialBlockHeight diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 845a431..6838974 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -48,6 +48,8 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) } emit lastBlockSynchedChanged(); emit timeBehindChanged(); + + updateUpToDate(); }, Qt::QueuedConnection); connect(wallet, &Wallet::backupBlockHeightChanged, m_wallet, [=]() { emit lastBlockSynchedChanged2(); @@ -57,6 +59,9 @@ AccountInfo::AccountInfo(Wallet *wallet, QObject *parent) connect(wallet, SIGNAL(transactionConfirmed(int)), this, SIGNAL(balanceChanged()), Qt::QueuedConnection); connect(FloweePay::instance(), SIGNAL(headerChainHeightChanged()), this, SIGNAL(timeBehindChanged())); + connect(FloweePay::instance(), &FloweePay::blockHeightCertaintyChanged, this, [=]() { + updateUpToDate(); + }); } int AccountInfo::id() const @@ -241,11 +246,30 @@ void AccountInfo::walletEncryptionChanged() emit nameChanged(); } +void AccountInfo::updateUpToDate() +{ + bool isUpToDate = false; + if (FloweePay::instance()->blockHeightCertainty() == FloweePay::Certain) { + auto lbs = lastBlockSynched(); + isUpToDate = lbs == -2 // special value for just created wallet + || FloweePay::instance()->chainHeight() == lbs; + } + if (m_isUpToDate == isUpToDate) + return; + m_isUpToDate = isUpToDate; + emit uptodateChanged(); +} + bool AccountInfo::hasAnonimityTransactions() const { return m_wallet->walletStoresCashFusions(); } +bool AccountInfo::uptodate() const +{ + return m_isUpToDate; +} + int AccountInfo::accountStartBlockHeight() const { return m_accountStartBlockHeight; diff --git a/src/AccountInfo.h b/src/AccountInfo.h index 654c17f..c1ac50d 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -61,6 +61,7 @@ class AccountInfo : public QObject Q_PROPERTY(bool isSingleAddressAccount READ isSingleAddressAccount NOTIFY encryptionChanged) Q_PROPERTY(bool isHDWallet READ isHDWallet NOTIFY encryptionChanged) Q_PROPERTY(bool isArchived READ isArchived WRITE setIsArchived NOTIFY isArchivedChanged) + Q_PROPERTY(bool uptodate READ uptodate NOTIFY uptodateChanged) Q_PROPERTY(QString mnemonic READ hdWalletMnemonic NOTIFY encryptionChanged) Q_PROPERTY(QString mnemonicPwd READ hdWalletMnemonicPwd NOTIFY encryptionChanged) Q_PROPERTY(bool isElectrumMnemonic READ isElectrumMnemonic NOTIFY encryptionChanged) @@ -202,6 +203,8 @@ public: bool hasAnonimityTransactions() const; void setHasAnonimityTransactions(bool newHasAnonimityTransactions); + bool uptodate() const; + signals: void balanceChanged(); void utxosChanged(); @@ -220,6 +223,7 @@ signals: void neverEmitted(); // to silence the lambs^Warnings void accountStartBlockHeightChanged(); void hasAnonimityTransactionsChanged(); + void uptodateChanged(); // for the benefit of the portfolio data provider void isPrivateChanged(); @@ -230,6 +234,8 @@ private slots: void walletEncryptionChanged(); private: + void updateUpToDate(); + Wallet *m_wallet; AccountConfig m_config; QTimer *m_closeWalletTimer = nullptr; @@ -239,6 +245,7 @@ private: int m_accountStartBlockHeight; int m_initialBlockHeight; bool m_hasFreshTransactions = false; + bool m_isUpToDate = false; friend class WalletSecret; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc21e55..c34ce1b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,7 +36,6 @@ set (PAY_SOURCES PaymentDetailInputs.cpp PaymentDetailComment.cpp QRScanner.cpp - PaymentProtocol.cpp PortfolioDataProvider.cpp PriceDataProvider.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 7587f8f..5d97120 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -298,7 +298,7 @@ FloweePay::FloweePay() in.close(); } - // forward signal + // forward signals connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); connect (this, &FloweePay::startSaveData_priv, this, [=]() { // As Qt does not allow starting a timer from any thread, we first use a signal @@ -306,6 +306,8 @@ FloweePay::FloweePay() // the singleshot below. QTimer::singleShot(1000, this, SLOT(saveData())); }, Qt::QueuedConnection); + connect (this, SIGNAL(internal_heightCertaintyChanged()), + this, SIGNAL(blockHeightCertaintyChanged()), Qt::QueuedConnection); connect (QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() { shutdown(); @@ -322,8 +324,10 @@ void FloweePay::shutdown() m_indexerServices->save(); auto *dl = m_downloadManager.get(); - if (dl) // p2pNet follows lazy initialization. + if (dl) { // p2pNet follows lazy initialization. dl->removeHeaderListener(this); + dl->removeP2PNetListener(this); + } for (auto wallet : std::as_const(m_wallets)) { if (dl) { // p2pNet follows lazy initialization. dl->removeDataListener(wallet); @@ -809,6 +813,15 @@ int FloweePay::headerChainHeight() const return m_downloadManager->blockHeight(); } +FloweePay::NetworkCertainty FloweePay::blockHeightCertainty() const +{ + if (!m_downloadManager.get()) + return NotCertain; + // we static cast here since the enum here is exported to QML, they are + // otherwise identical to the upstream enum. + return static_cast(m_downloadManager->connectionManager().blockHeightCertainty()); +} + int FloweePay::expectedChainHeight() const { if (!m_downloadManager.get()) @@ -860,6 +873,11 @@ void FloweePay::headerSyncComplete() emit headerChainHeightChanged(); } +void FloweePay::newBlockHeightCertainty(P2PNet::NetworkCertainty) +{ + emit internal_heightCertaintyChanged(); +} + bool FloweePay::darkSkin() const { return m_darkSkin; @@ -1515,6 +1533,7 @@ DownloadManager *FloweePay::p2pNet() if (m_downloadManager == nullptr) { m_downloadManager.reset(new DownloadManager(ioService(), m_basedir.toStdString(), m_chain)); m_downloadManager->addHeaderListener(this); + m_downloadManager->addP2PNetListener(this); m_downloadManager->notifications().addListener(&m_notifications); QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); diff --git a/src/FloweePay.h b/src/FloweePay.h index 3656eff..a0e4335 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -23,15 +23,16 @@ #include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include #include #include #include -#include class Wallet; @@ -44,7 +45,7 @@ class IndexerServices; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); -class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterface +class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterface, public P2PNetInterface { Q_OBJECT Q_PROPERTY(QString version READ version CONSTANT) @@ -53,6 +54,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa // p2p net Q_PROPERTY(int headerChainHeight READ headerChainHeight NOTIFY headerChainHeightChanged) Q_PROPERTY(int expectedChainHeight READ expectedChainHeight NOTIFY expectedChainHeightChanged) + Q_PROPERTY(FloweePay::NetworkCertainty blockHeightCertainty READ blockHeightCertainty NOTIFY blockHeightCertaintyChanged FINAL) Q_PROPERTY(int chainHeight READ chainHeight NOTIFY headerChainHeightChanged) Q_PROPERTY(bool isMainChain READ isMainChain CONSTANT) // GUI user settings @@ -123,6 +125,13 @@ public: }; Q_ENUM(BroadcastStatus) + enum NetworkCertainty { + NotCertain = P2PNet::NotCertain, + ReasonablyCertain = P2PNet::ReasonablyCertain, + Certain = P2PNet::Certain, + }; + Q_ENUM(NetworkCertainty) + FloweePay(); /** @@ -279,6 +288,11 @@ public: */ int headerChainHeight() const; + /** + * Certainty grows as more validated peers agree. + */ + NetworkCertainty blockHeightCertainty() const; + /** * Return the chain-height that based on the date/time we expect * to be at. @@ -300,6 +314,9 @@ public: void setHeaderSyncHeight(int height) override; void headerSyncComplete() override; + // P2PNetInterface interface + void newBlockHeightCertainty(P2PNet::NetworkCertainty certainty) override; + /** * Returns true if this is the mainchain. * \see selectChain() @@ -393,6 +410,8 @@ signals: void privateModeChanged(); void appProtectionChanged(); void skinFollowsPlatformChanged(); + void blockHeightCertaintyChanged(); + void internal_heightCertaintyChanged(); // not thread-safe private slots: void loadingCompleted(); -- 2.54.0 From 3c9f1111e87c65e19e9175a22cffa4e5f193ea8b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 30 Nov 2024 16:25:25 +0100 Subject: [PATCH 1053/1428] Fetch current price only when needed Only fetch on startup if the last fetch is more than 5m ago --- src/PriceDataProvider.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index b0e0099..5957529 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -51,7 +51,9 @@ void PriceDataProvider::start() if (m_priceHistory.get()) m_priceHistory->initialPopulate(); m_timer.start(ReloadTimeout); - fetch(); + const auto now = time(nullptr); + if (now - m_currentPrice.timestamp > 300) + fetch(); } bool PriceDataProvider::oldData() const -- 2.54.0 From 45832b1b9e380f2226e23fa1f0c6deaec1557532 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 1 Dec 2024 13:57:18 +0100 Subject: [PATCH 1054/1428] Rewrite progressbar UI component Have a different approach which makes everything much simpler --- guis/Flowee/Progressbar.qml | 128 +++++++++++------------------------- 1 file changed, 38 insertions(+), 90 deletions(-) diff --git a/guis/Flowee/Progressbar.qml b/guis/Flowee/Progressbar.qml index 6901ade..178874e 100644 --- a/guis/Flowee/Progressbar.qml +++ b/guis/Flowee/Progressbar.qml @@ -17,114 +17,62 @@ */ import QtQuick -Rectangle { +Item { id: root height: 40 - color: "#00000000" - border.width: 2 - border.color: palette.midlight - radius: 10 + width: 200 // should be calculated to be between 0 and 1 required property double progress; // Like progress, but overlaying the main progressbar in a separate color property double progress2: 0; // should always be smaller than progress - property var overlayGradient: Gradient { - GradientStop { position: 0; color: palette.shadow } - GradientStop { position: 1; color: palette.midlight } + Label { + id: percentLabel + text: (100 * root.progress).toFixed(0) + "%" + y: 5 + x: root.width / 2 - width / 2 } - Rectangle { - // just the first 10 pixels - width: Math.min(10, parent.width * parent.progress) - y: 2 - height: parent.height - 4 - color: palette.highlight - radius: 10 - Rectangle { - id: overlayProgressBar - // for progress2 - visible: root.progress2 > 0 - width: Math.min(10, root.width * root.progress2) - height: parent.height - radius: 10 - gradient: root.overlayGradient - } - } - Rectangle { - id: bar - x: 5 - y: 2 - width: { - var full = root.width; - var w = full * root.progress - w = Math.min(w, full - 5) // the right max - w -= 5; // our X offset - Math.max(0, w); - } - height: parent.height - 4 - color: palette.highlight + Item { + width: root.width * root.progress + height: parent.height clip: true Rectangle { - // for progress2 - visible: root.progress2 > 0 - width: Math.max(0, Math.min(root.width * root.progress2, root.width - 5) - 5); - height: parent.height - gradient: root.overlayGradient - } - Label { - id: percentLabel - text: (100 * root.progress).toFixed(0) + "%" - y: 5 - x: root.width / 2 - 10 - width / 2 - color: palette.highlightedText - } - } - Rectangle { - width: { - // just the last 10 pixels. - var full = root.width; - return Math.max(0, full * root.progress - (full - 10)); - } - x: root.width - 10 - y: 2 - height: parent.height - 4 - color: palette.highlight - radius: 10 - Rectangle { - // for progress2 - visible: root.progress2 > 0 - width: Math.max(0, root.width * root.progress - (root.width - 10)); - height: parent.height - gradient: root.overlayGradient + color: palette.highlight + height: root.height + width: root.width radius: 10 } - } - Rectangle { - id: groove - y: 2 - height: parent.height - 4 - color: palette.light - clip: true - x: { - var full = root.width; - var distance = root.progress * full; - distance = Math.min(distance, full - 5); // and right - distance = Math.max(5, distance); // avoid the left rounding area - return distance; - } - width: { - var full = root.width; - var w = full - root.progress * full; - w -= 5; - w = Math.min(w, full - 10); // the rounded corners - return w; + Item { + clip: true + width: root.width * root.progress2 + height: parent.height + Rectangle { + height: root.height + width: root.width + radius: 10 + gradient: Gradient { + GradientStop { position: 0; color: palette.shadow } + GradientStop { position: 1; color: palette.midlight } + } + } } Label { text: percentLabel.text y: 5 - x: root.width / 2 - 5 - width / 2 - parent.x + x: root.width / 2 - width / 2 + color: palette.highlightedText } } + + Rectangle { + id: outline + height: parent.height + width: parent.width + color: "#00000000" + border.width: 2 + border.color: palette.midlight + radius: 10 + } } -- 2.54.0 From bb4ebdfadda80c099e4345307ff21adadebc7590 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 1 Dec 2024 17:43:57 +0100 Subject: [PATCH 1055/1428] Import updated translations from crowdin --- translations/floweepay-common_de.ts | 2 +- translations/floweepay-desktop_de.ts | 22 +++++++++---------- translations/floweepay-mobile_de.ts | 16 +++++++------- translations/module-send-sweep_de.ts | 32 ++++++++++++++-------------- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 75f2d47..7ac8db7 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -286,7 +286,7 @@ Wallet is locked - Wallet is locked + Geldbörse ist gesperrt diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index a1f27b3..41e603e 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -292,7 +292,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Create a new wallet with smart creation of addresses from a single seed-phrase - Create a new wallet with smart creation of addresses from a single seed-phrase + Erstellt eine neue Geldbörse mit intelligenter Erstellung von Adressen aus einer einzigen Seed-Phrase @@ -381,7 +381,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. New Wallet Name - New Wallet Name + Neuer Geldbörsen Name @@ -397,13 +397,13 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. imported wallet password - imported wallet password + importiertes Geldbörsen-Passwort Discover Details online check for wallet details - Discover Details + Entdecke Details @@ -505,7 +505,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. This allows adding a public comment, that will be included in the transaction and seen by everyone. - This allows adding a public comment, that will be included in the transaction and seen by everyone. + Dies ermöglicht das Hinzufügen eines öffentlichen Kommentars, der in die Transaktion aufgenommen wird und von allen gesehen wird. @@ -533,7 +533,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. High risk transaction - High risk transaction + Hochriskante Transaktion @@ -781,22 +781,22 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Public-comment - Public-comment + Öffentlicher Kommentar Add a comment you want to include in the transaction, visible for everyone. - Add a comment you want to include in the transaction, visible for everyone. + Fügen Sie einen Kommentar hinzu, den Sie in die Transaktion einfügen möchten, sichtbar für alle. Custom message, to be included in the transaction. - Custom message, to be included in the transaction. + Benutzerdefinierte Nachricht, die in die Transaktion aufgenommen werden soll. Text - Text + Text @@ -925,7 +925,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Rejected - Rejected + Abgelehnt diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 9a4040f..ba77f67 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -186,12 +186,12 @@ Re-scan Chain - Re-scan Chain + Chain neu scannen Remove Wallet - Remove Wallet + Geldbörse entfernen @@ -409,7 +409,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. New Wallet Name - New Wallet Name + Neuer Geldbörsen Name @@ -425,13 +425,13 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. imported wallet password - imported wallet password + importiertes Geldbörsen-Passwort Discover Details online check for wallet details - Discover Details + Entdecke Details @@ -834,7 +834,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss High risk transaction - High risk transaction + Hochriskante Transaktion @@ -946,7 +946,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Claim a Cash Stamp - Claim a Cash Stamp + Cash Stamp beanspruchen @@ -1147,7 +1147,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Rejected - Rejected + Abgelehnt diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index 4db602c..bb7bc1c 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -6,51 +6,51 @@ Sweep coins - Sweep coins + Coins einlesen Scan QR (WIF) to find funds Please note that WIF and QR are names - Scan QR (WIF) to find funds + Scanne QR (WIF) um Geld zu finden Sweeping from address: - Sweeping from address: + Einlesen von Adresse: Found %1 coins on address. this is a simple number - - Found %1 coins on address. - Found %1 coins on address. + + %1 Coin auf Adresse gefunden. + %1 Coins auf Adresse gefunden. Ignoring %1 tokens. Number of CashTokens - - Ignoring %1 tokens. - Ignoring %1 tokens. + + Ignoriere %1 Token. + Ignoriere %1 Tokens. Failed to understand QR - Failed to understand QR + Konnte QR nicht verstehen Indexer results invalid. Please try again. - Indexer results invalid. Please try again. + Indexer-Ergebnisse ungültig. Bitte versuchen Sie es erneut. Transfer to: - Transfer to: + Transferieren nach: @@ -76,7 +76,7 @@ The payment has been sent to: Followed by the address - The payment has been sent to: + Die Zahlung wurde gesendet an: @@ -89,17 +89,17 @@ Sweep & Send - Sweep & Send + Einlesen & Senden Allows sweeping a paper-wallet, moving the contents to your own wallet - Allows sweeping a paper-wallet, moving the contents to your own wallet + Ermöglicht das Verschieben einer Papier-Geldbörse und das Verschieben des Inhalts auf Ihre eigene Geldbörse Sweep Paper Wallet - Sweep Paper Wallet + Papier-Geldbörse einlesen -- 2.54.0 From 33a728799d047dcd987e685d3ca930685b5ebe7c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 4 Dec 2024 23:43:05 +0100 Subject: [PATCH 1056/1428] Switch explorer The blockchair explorer seems to be down a lot more, so lets point to the new iteration from the same team. 3xpl.com --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 5d97120..9441810 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -788,7 +788,7 @@ void FloweePay::copyToClipboard(const QString &text) void FloweePay::openInExplorer(const QString &txid) { - QDesktopServices::openUrl(QUrl("https://blockchair.com/search?q=" + txid)); + QDesktopServices::openUrl(QUrl("https://3xpl.com/bitcoin-cash/transaction/" + txid)); } FloweePay::UnitOfBitcoin FloweePay::unit() const -- 2.54.0 From b463347b391f4a93c45683d3ae3273baab40298d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 1 Nov 2024 14:12:34 +0100 Subject: [PATCH 1057/1428] Upgrade versions in reproducable Android build This updates all the relevant libraries we use to their latest stable versions in the reproducable build environment we use for Android. --- android/build.gradle | 12 +- android/docker/Dockerfile | 6 +- android/docker/build-docker.sh | 2 +- .../docker/scripts/android-composing.patch | 46 ----- android/docker/scripts/android-deployqt.patch | 170 ------------------ android/docker/scripts/aurs.sh | 16 +- android/docker/scripts/buildBoost.sh | 2 +- android/docker/scripts/buildOpenSsl.sh | 2 +- android/docker/scripts/buildQt.sh | 38 ++-- android/docker/scripts/buildZXing.sh | 2 +- 10 files changed, 52 insertions(+), 244 deletions(-) delete mode 100644 android/docker/scripts/android-composing.patch delete mode 100644 android/docker/scripts/android-deployqt.patch diff --git a/android/build.gradle b/android/build.gradle index f87d9e9..0c88b42 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' + classpath 'com.android.tools.build:gradle:8.6.0' } } @@ -18,21 +18,25 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + implementation 'androidx.core:core:1.13.1' } android { - /* ****************************************************** + /******************************************************* * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion * - qtAndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. + * - qtGradlePluginType - whether to build an app or a library * - * are defined in gradle.properties file. That file is - * created / updated by androiddeployqt. + * are defined in gradle.properties file. This file is + * updated by QtCreator and androiddeployqt tools. + * Changing them manually might break the compilation! *******************************************************/ + namespace androidPackageName compileSdkVersion androidCompileSdkVersion buildToolsVersion androidBuildToolsVersion ndkVersion androidNdkVersion diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 7c67979..f28523a 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.5.3 +ARG QtVersion=v6.8.1 FROM archlinux:latest ARG QtVersion @@ -8,7 +8,7 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -#copy mirrorlist /etc/pacman.d/ +copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Suy --noconfirm --noprogressbar \ && pacman -Sy --noconfirm --noprogressbar --needed base-devel \ @@ -41,7 +41,7 @@ RUN useradd builduser -d /home/builds -m -u 1000 -U \ wget \ libc++ \ libxcrypt-compat \ - jdk11-openjdk \ + jdk17-openjdk \ && pacman -Sc --noconfirm \ && rm -rf /var/cache/pacman/pkg/* \ && /usr/local/bin/createRootPwd diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 76338a7..dc7023f 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.5.3 +QtVersion=v6.8.1 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/android-composing.patch b/android/docker/scripts/android-composing.patch deleted file mode 100644 index 4a6b7f1..0000000 --- a/android/docker/scripts/android-composing.patch +++ /dev/null @@ -1,46 +0,0 @@ -From f5be35240b561c84f8b2b5d85bfe922e1c7391eb Mon Sep 17 00:00:00 2001 -From: Andreas Buhr -Date: Thu, 14 Oct 2021 05:20:23 +0200 -Subject: [PATCH 1/2] Android: Fix handling of cursor position when stop - composing - ---- - .../android/qandroidinputcontext.cpp | 22 +++++++++++++------ - 1 file changed, 15 insertions(+), 7 deletions(-) - -diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp -index d2eb05c24d..6798ad585c 100644 ---- a/src/plugins/platforms/android/qandroidinputcontext.cpp -+++ b/src/plugins/platforms/android/qandroidinputcontext.cpp -@@ -1144,13 +1144,21 @@ bool QAndroidInputContext::focusObjectStopComposing() - - m_composingCursor = -1; - -- // commit composing text and cursor position -- QList attributes; -- attributes.append( -- QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); -- QInputMethodEvent event(QString(), attributes); -- event.setCommitString(m_composingText); -- sendInputMethodEvent(&event); -+ { -+ // commit the composing test -+ QList attributes; -+ QInputMethodEvent event(QString(), attributes); -+ event.setCommitString(m_composingText); -+ sendInputMethodEvent(&event); -+ } -+ { -+ // Moving Qt's cursor to where the preedit cursor used to be -+ QList attributes; -+ attributes.append( -+ QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); -+ QInputMethodEvent event(QString(), attributes); -+ sendInputMethodEvent(&event); -+ } - - return true; - } --- -2.43.0 - diff --git a/android/docker/scripts/android-deployqt.patch b/android/docker/scripts/android-deployqt.patch deleted file mode 100644 index 48db8de..0000000 --- a/android/docker/scripts/android-deployqt.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 1b92ca6a08e69cd9a9870487ff548edc0193e789 Mon Sep 17 00:00:00 2001 -From: Alexey Edelev -Date: Wed, 3 Jan 2024 13:16:41 +0100 -Subject: [PATCH 2/2] Add the support of the qt_import_plugins functionality to - androiddeployqt - -qt_import_plugins allows to control application deployment on -non-Android platforms. This adds support for the pre-defined plugin list -that is computed using the qt_import_plugins input. - -Task-number: QTBUG-118829 -Change-Id: Iaa9c3f600533a4b5a3079ab228fabf212d9ce5a5 -Reviewed-by: Assam Boudjelthia ---- - cmake/QtAndroidHelpers.cmake | 3 +- - src/corelib/Qt6AndroidMacros.cmake | 6 ++- - src/tools/androiddeployqt/main.cpp | 70 ++++++++++++++++++++---------- - 3 files changed, 55 insertions(+), 24 deletions(-) - -diff --git a/cmake/QtAndroidHelpers.cmake b/cmake/QtAndroidHelpers.cmake -index 857a029991..ba72e21e84 100644 ---- a/cmake/QtAndroidHelpers.cmake -+++ b/cmake/QtAndroidHelpers.cmake -@@ -252,7 +252,8 @@ function(qt_internal_android_dependencies target) - # Module plugins - if(module_plugin_types) - foreach(plugin IN LISTS module_plugin_types) -- string(APPEND file_contents "\n") -+ string(APPEND file_contents -+ "\n") - endforeach() - endif() - -diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake -index 9a44308a0f..0a270b96bb 100644 ---- a/src/corelib/Qt6AndroidMacros.cmake -+++ b/src/corelib/Qt6AndroidMacros.cmake -@@ -236,6 +236,10 @@ function(qt6_android_generate_deployment_settings target) - _qt_internal_add_android_deployment_property(file_contents "android-no-deploy-qt-libs" - ${target} "QT_ANDROID_NO_DEPLOY_QT_LIBS") - -+ __qt_internal_collect_plugin_targets_from_dependencies("${target}" plugin_targets) -+ __qt_internal_collect_plugin_library_files("${target}" "${plugin_targets}" plugin_targets) -+ string(APPEND file_contents " \"android-deploy-plugins\":\"${plugin_targets}\",\n") -+ - # App binary - string(APPEND file_contents - " \"application-binary\": \"${target_output_name}\",\n") -@@ -303,7 +307,7 @@ function(qt6_android_generate_deployment_settings target) - # content end - string(APPEND file_contents "}\n") - -- file(GENERATE OUTPUT ${deploy_file} CONTENT ${file_contents}) -+ file(GENERATE OUTPUT ${deploy_file} CONTENT "${file_contents}") - - set_target_properties(${target} - PROPERTIES -diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp -index ec33fd354b..f3581de0e6 100644 ---- a/src/tools/androiddeployqt/main.cpp -+++ b/src/tools/androiddeployqt/main.cpp -@@ -144,6 +144,7 @@ struct Options - QString qtQmlDirectory; - QString qtHostDirectory; - std::vector extraPrefixDirs; -+ QStringList androidDeployPlugins; - // Unlike 'extraPrefixDirs', the 'extraLibraryDirs' key doesn't expect the 'lib' subfolder - // when looking for dependencies. - std::vector extraLibraryDirs; -@@ -1011,6 +1012,11 @@ bool readInputFile(Options *options) - } - } - -+ { -+ const auto androidDeployPlugins = jsonObject.value("android-deploy-plugins"_L1).toString(); -+ options->androidDeployPlugins = androidDeployPlugins.split(";"_L1, Qt::SkipEmptyParts); -+ } -+ - { - const auto extraLibraryDirs = jsonObject.value("extraLibraryDirs"_L1).toArray(); - options->extraLibraryDirs.reserve(extraLibraryDirs.size()); -@@ -1883,6 +1889,32 @@ QList findFilesRecursively(const Options &options, const QString & - return deps; - } - -+void readDependenciesFromFiles(Options *options, const QList &files, -+ QSet &usedDependencies, -+ QSet &remainingDependencies) -+{ -+ for (const QtDependency &fileName : files) { -+ if (usedDependencies.contains(fileName.absolutePath)) -+ continue; -+ -+ if (fileName.absolutePath.endsWith(".so"_L1)) { -+ if (!readDependenciesFromElf(options, fileName.absolutePath, &usedDependencies, -+ &remainingDependencies)) { -+ fprintf(stdout, "Skipping file dependency: %s\n", -+ qPrintable(fileName.relativePath)); -+ continue; -+ } -+ } -+ usedDependencies.insert(fileName.absolutePath); -+ -+ if (options->verbose) { -+ fprintf(stdout, "Appending file dependency: %s\n", qPrintable(fileName.relativePath)); -+ } -+ -+ options->qtDependencies[options->currentArchitecture].append(fileName); -+ } -+} -+ - bool readAndroidDependencyXml(Options *options, - const QString &moduleName, - QSet *usedDependencies, -@@ -1913,29 +1945,15 @@ bool readAndroidDependencyXml(Options *options, - - QString file = reader.attributes().value("file"_L1).toString(); - -- const QList fileNames = findFilesRecursively(*options, file); -- -- for (const QtDependency &fileName : fileNames) { -- if (usedDependencies->contains(fileName.absolutePath)) -- continue; -- -- if (fileName.absolutePath.endsWith(".so"_L1)) { -- QSet remainingDependencies; -- if (!readDependenciesFromElf(options, fileName.absolutePath, -- usedDependencies, -- &remainingDependencies)) { -- fprintf(stdout, "Skipping dependencies from xml: %s\n", -- qPrintable(fileName.relativePath)); -- continue; -- } -- } -- usedDependencies->insert(fileName.absolutePath); -- -- if (options->verbose) -- fprintf(stdout, "Appending dependency from xml: %s\n", qPrintable(fileName.relativePath)); -- -- options->qtDependencies[options->currentArchitecture].append(fileName); -+ if (reader.attributes().hasAttribute("type"_L1) -+ && reader.attributes().value("type"_L1) == "plugin_dir"_L1 -+ && !options->androidDeployPlugins.isEmpty()) { -+ continue; - } -+ -+ const QList fileNames = findFilesRecursively(*options, file); -+ readDependenciesFromFiles(options, fileNames, *usedDependencies, -+ *remainingDependencies); - } else if (reader.name() == "jar"_L1) { - int bundling = reader.attributes().value("bundling"_L1).toInt(); - QString fileName = QDir::cleanPath(reader.attributes().value("file"_L1).toString()); -@@ -2412,6 +2430,14 @@ bool readDependencies(Options *options) - if (!readDependenciesFromElf(options, "%1/libs/%2/lib%3_%2.so"_L1.arg(options->outputDirectory, options->currentArchitecture, options->applicationBinary), &usedDependencies, &remainingDependencies)) - return false; - -+ QList pluginDeps; -+ for (const auto &pluginPath : options->androidDeployPlugins) { -+ pluginDeps.append(findFilesRecursively(*options, QFileInfo(pluginPath), -+ options->qtInstallDirectory + "/"_L1)); -+ } -+ -+ readDependenciesFromFiles(options, pluginDeps, usedDependencies, remainingDependencies); -+ - while (!remainingDependencies.isEmpty()) { - QSet::iterator start = remainingDependencies.begin(); - QString fileName = absoluteFilePath(options, *start); --- -2.43.0 - diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index 402daa7..0c04b77 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -25,12 +25,16 @@ function makeAur( ) -makeAur android-ndk -# For now we hardcode the 31.0.3 release of the sdk, seeing as Qt hardcodes gradle 7.4.2, which doesn't like a newer sdk. -makeAur android-sdk-platform-tools 89f6840df092ea2f1fc3d446c41be78cf9b66339 -makeAur android-sdk-build-tools 87aea36e5aef112d7af0c2ae5db154e96ab633c3 -makeAur android-sdk-cmdline-tools-latest -makeAur android-platform +# r27 +makeAur android-ndk 658ef36823525853a1338d51020320a9fb1b9cbd +# 35.0.2 +makeAur android-sdk-platform-tools ac481561ad3ab25cd17a4a62571159176ab6f584 +# r34.0.0-2 +makeAur android-sdk-build-tools ce7b51fed9ef7e0db9ee681883204adde1ef3808 +# 16.0 +makeAur android-sdk-cmdline-tools-latest 91763061af0ee3d077246c18bb4b6fe55fc7c566 +# 35_r01 +makeAur android-platform 5506c16537f770278bcb694451800ceb07e92de8 pacman -U --noconfirm /usr/local/cache/*zst diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index e2a0f54..5f5babe 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -8,7 +8,7 @@ VER2=`echo $VERSION|sed -e 's#\.#_#g'` cd /usr/local/cache if ! test -f boost_$VER2.tar.bz2; then - curl -O https://netix.dl.sourceforge.net/project/boost/boost/$VERSION/boost_$VER2.tar.bz2 + wget https://boostorg.jfrog.io/artifactory/main/release/$VERSION/source/boost_$VER2.tar.bz2 fi cd ~builduser diff --git a/android/docker/scripts/buildOpenSsl.sh b/android/docker/scripts/buildOpenSsl.sh index 3c3a136..65ef506 100755 --- a/android/docker/scripts/buildOpenSsl.sh +++ b/android/docker/scripts/buildOpenSsl.sh @@ -7,7 +7,7 @@ source /etc/profile cd /usr/local/cache if ! test -f openssl-$VERSION.tar.gz; then - curl -O https://www.openssl.org/source/openssl-$VERSION.tar.gz + wget https://github.com/openssl/openssl/releases/download/openssl-$VERSION/openssl-$VERSION.tar.gz fi cd ~builduser diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index f21aa74..b55a1f8 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -2,11 +2,12 @@ TAG=$1 if test -z "$TAG"; then - print "Missing required argument 'TAG'" + echo "Missing required argument 'TAG'" exit fi echo "Based on Qt version $TAG" >> /etc/versions source /etc/profile +export NINJA_STATUS='[%u/%r/%f] ' function checkout ( repo=$1 @@ -25,20 +26,31 @@ function checkout ( ) # The QtBase builds are different. -TAG=6.5 checkout qtbase -cd ~builduser -curl 'https://code.qt.io/cgit/qt/qtbase.git/patch/?id=8af35d27' > libxkbcommon-1.6.patch -patch -d qtbase -p1 < libxkbcommon-1.6.patch # Fix build with libxkbcommon 1.6 -patch -d qtbase -p1 < /usr/local/bin/android-composing.patch -patch -d qtbase -p1 < /usr/local/bin/android-deployqt.patch +QTBASE_FLAGS="-no-widgets \ + -no-dbus \ + -no-feature-testlib \ + -no-feature-sql \ + -no-feature-xml \ + -no-feature-networkproxy \ + -no-feature-socks5 \ + -no-feature-brotli \ + -no-feature-dnslookup \ + -no-feature-topleveldomain \ + -no-feature-textmarkdownreader \ + -no-feature-textmarkdownwriter \ + -no-feature-textodfwriter" +# on qtbase flags: +# I checked and noticed that colornames are required (qml fails to load otherwise) +# the cssparser is also required for properly loading svgs. +checkout qtbase mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ -prefix /usr/local \ -no-openssl \ -nomake examples \ - -no-dbus + $QTBASE_FLAGS cmake --build . --parallel cmake --install . rm -rf ~builduser/build/* @@ -55,6 +67,7 @@ cd ~builduser/build/qtbase -android-abis arm64-v8a \ -android-style-assets \ -openssl-linked \ + $QTBASE_FLAGS \ -- \ -DOPENSSL_USE_STATIC_LIBS=ON \ -DOPENSSL_ROOT_DIR=/opt/android-ssl @@ -62,14 +75,17 @@ cmake --build . --parallel cmake --install . rm -rf ~builduser/build/* - # All the others. for i in qtshadertools qtdeclarative qtsvg qtmultimedia do checkout $i mkdir -p ~builduser/build/$i cd ~builduser/build/$i - /usr/local/bin/qt-configure-module ~builduser/$i + CONF="" +# if test "$i" = "qtdeclarative"; then +# CONF="-no-feature-quickcontrols2-fluentwinui3 -no-feature-quickcontrols2-imagine -no-feature-quickcontrols2-ios -no-feature-quickcontrols2-macos -no-feature-quickcontrols2-material" +# fi + /usr/local/bin/qt-configure-module ~builduser/$i $CONF cmake --build . --parallel cmake --install . cd ~builduser @@ -78,7 +94,7 @@ do # Android mkdir -p ~builduser/build/$i cd ~builduser/build/$i - /opt/android-qt6/bin/qt-configure-module ~builduser/$i + /opt/android-qt6/bin/qt-configure-module ~builduser/$i $CONF cmake --build . --parallel cmake --install . cd ~builduser diff --git a/android/docker/scripts/buildZXing.sh b/android/docker/scripts/buildZXing.sh index 6e86810..7506be7 100755 --- a/android/docker/scripts/buildZXing.sh +++ b/android/docker/scripts/buildZXing.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=2.1.0 +VERSION=2.2.0 echo "Based on zxing version $VERSION" >> /etc/versions source /etc/profile -- 2.54.0 From e7b874098632b250136ad75653abe38f67defa2b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 4 Dec 2024 13:05:51 +0100 Subject: [PATCH 1058/1428] Avoid copy --- src/CameraController.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index c953faf..5dc127a 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -93,7 +93,7 @@ protected: void run(); private: std::vector readBarcodes(const QImage &img) const; - std::vector readBarcodes(const QVideoFrame &frame) const; + std::vector readBarcodes(QVideoFrame &frame) const; CameraControllerPrivate *m_parent; // notice that since ZXIng 2.2.0 this is renamed to 'ReaderOptions'. @@ -420,7 +420,7 @@ std::vector QRScanningThread::readBarcodes(const QImage &img) con return ZXing::ReadBarcodes(buf, m_decodeHints); } -std::vector QRScanningThread::readBarcodes(const QVideoFrame &frame) const +std::vector QRScanningThread::readBarcodes(QVideoFrame &frame) const { ZXing::ImageFormat fmt = ZXing::ImageFormat::None; int pixStride = 0; @@ -482,16 +482,15 @@ std::vector QRScanningThread::readBarcodes(const QVideoFrame &fra } if (fmt != ZXing::ImageFormat::None) { - auto img = frame; // shallow copy just get access to non-const map() function - if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){ + if (!frame.isValid() || !frame.map(QVideoFrame::ReadOnly)){ logDebug() << "invalid QVideoFrame: could not map memory"; return {}; } - QScopeGuard unmap([&] { img.unmap(); }); + QScopeGuard unmap([&] { frame.unmap(); }); constexpr int FirstPlane = 0; return ZXing::ReadBarcodes( - {img.bits(FirstPlane) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FirstPlane), pixStride}, m_decodeHints); + {frame.bits(FirstPlane) + pixOffset, frame.width(), frame.height(), fmt, frame.bytesPerLine(FirstPlane), pixStride}, m_decodeHints); } else { return readBarcodes(frame.toImage()); } -- 2.54.0 From a9c387e42a2c0b73554a7860716932c48d4b3bbd Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 4 Dec 2024 13:07:08 +0100 Subject: [PATCH 1059/1428] Pick behavior of Qt6.6 or later cmake feature. --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c8d072..a5c1a86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,9 @@ option(NetworkLogClient "Include the network based logging in the executables" O set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) +if (QT_KNOWN_POLICY_QTP0002) + qt_policy(SET QTP0002 NEW) +endif() # calling find_package Qt two times seems to be needed to get the Qt version :shrug: find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core) @@ -287,7 +290,7 @@ if (ANDROID AND build_mobile_pay) target_link_libraries(pay_mobile PRIVATE pay_lib Qt6::Svg Qt6::Multimedia ${PAY_MOBILE_LIBS}) set_target_properties(pay_mobile PROPERTIES - QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/android + QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_SOURCE_DIR}/android" COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} MOBILE" ) endif () -- 2.54.0 From 86dcb5c39e460fcea8f3eb7a934d7bc40b9f2ebd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Dec 2024 18:59:55 +0100 Subject: [PATCH 1060/1428] Stop mixing two format types This now switches to only using html, removing some markdown headers. --- guis/mobile/About.qml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index bc2dffe..313a2d1 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -61,13 +61,12 @@ Page { headerText: qsTr("Credits") Flowee.Label { - text: "## Author and maintainer + text: "

Author and maintainer

Tom Zander -## Translations +

Translations

-Deutsc -
h
+
Deutsch
Georg Engelmann
Español
Gerard H.R.(devperate)
@@ -81,15 +80,15 @@ Deutsc
bitcoincashbrazil
-## Code Contributors +

Code Contributors

Calin Culianu -## Art Contributors +

Art Contributors

You? " - textFormat: Text.MarkdownText + textFormat: Text.RichText wrapMode: Text.WordWrap width: parent.width } -- 2.54.0 From 814fc828973e0462373065bbc6923850689496cc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Dec 2024 22:06:31 +0100 Subject: [PATCH 1061/1428] Adjust Qt build This reverts a broken Qt patch wrt translation selection. See QTBUG-131894 This removes various Qt features from the build that we don't need in order to keep the library size down. The qtdeclarative module went from 4285 ninja tasks to 2387, or for the android buld from 4117 to 2251. --- android/docker/scripts/buildQt.sh | 37 ++++- .../scripts/qtbase-revert-qtranslator.patch | 144 ++++++++++++++++++ 2 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 android/docker/scripts/qtbase-revert-qtranslator.patch diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index b55a1f8..4ed8cfe 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -44,6 +44,8 @@ QTBASE_FLAGS="-no-widgets \ # the cssparser is also required for properly loading svgs. checkout qtbase +cd ~builduser +patch -d qtbase -p1 < /usr/local/bin/qtbase-revert-qtranslator.patch mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ @@ -74,6 +76,7 @@ cd ~builduser/build/qtbase cmake --build . --parallel cmake --install . rm -rf ~builduser/build/* +rm -rf ~builduser/qtbase # All the others. for i in qtshadertools qtdeclarative qtsvg qtmultimedia @@ -82,9 +85,33 @@ do mkdir -p ~builduser/build/$i cd ~builduser/build/$i CONF="" -# if test "$i" = "qtdeclarative"; then -# CONF="-no-feature-quickcontrols2-fluentwinui3 -no-feature-quickcontrols2-imagine -no-feature-quickcontrols2-ios -no-feature-quickcontrols2-macos -no-feature-quickcontrols2-material" -# fi + if test "$i" = "qtdeclarative"; then + CONF=" \ + -no-feature-quick-tableview \ + -no-feature-quick-treeview \ + -no-feature-qml-xmllistmodel \ + -no-feature-quick-designer \ + -no-feature-qml-network \ + -no-feature-qml-preview \ + -no-feature-qml-worker-script \ + -no-feature-quick-animatedimage \ + -no-feature-quicktemplates2-calendar \ + -no-feature-quickcontrols2-ios \ + -no-feature-quickcontrols2-macos \ + -no-feature-quickcontrols2-windows \ + -no-feature-quickcontrols2-fluentwinui3 \ + -no-feature-quickcontrols2-universal \ + -no-feature-quickcontrols2-imagine \ + -no-feature-quickcontrols2-fusion \ + -no-feature-qml-debug \ + -no-feature-qml-profiler \ + -no-feature-qml-ssl \ + -no-feature-qml-xml-http-request \ + -no-feature-quick-pathview \ + -no-feature-quick-pixmap-cache-threaded-download \ + -no-feature-qml-table-model \ + " + fi /usr/local/bin/qt-configure-module ~builduser/$i $CONF cmake --build . --parallel cmake --install . @@ -95,9 +122,9 @@ do mkdir -p ~builduser/build/$i cd ~builduser/build/$i /opt/android-qt6/bin/qt-configure-module ~builduser/$i $CONF - cmake --build . --parallel - cmake --install . + cmake --build . --parallel && cmake --install . cd ~builduser rm -rf build/* + rm -rf $i done diff --git a/android/docker/scripts/qtbase-revert-qtranslator.patch b/android/docker/scripts/qtbase-revert-qtranslator.patch new file mode 100644 index 0000000..6ccff94 --- /dev/null +++ b/android/docker/scripts/qtbase-revert-qtranslator.patch @@ -0,0 +1,144 @@ +From 0e0e838bb5942a5cd64038385f43e1aa27acebe2 Mon Sep 17 00:00:00 2001 +From: TomZ +Date: Sat, 14 Dec 2024 15:14:36 +0100 +Subject: [PATCH] Revert "QTranslator: work around uiLanguages not including + lang_Terr variants" + +This reverts commit 8c40e8cf12bb931149367677c56816864265249c. +--- + src/corelib/kernel/qtranslator.cpp | 74 +++++-------------- + .../kernel/qtranslator/tst_qtranslator.cpp | 10 +++ + 2 files changed, 28 insertions(+), 56 deletions(-) + +diff --git a/src/corelib/kernel/qtranslator.cpp b/src/corelib/kernel/qtranslator.cpp +index 44c8f9b18d1..cdea4ac988f 100644 +--- a/src/corelib/kernel/qtranslator.cpp ++++ b/src/corelib/kernel/qtranslator.cpp +@@ -23,8 +23,6 @@ + #include "qendian.h" + #include "qresource.h" + +-#include +- + #if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY) + # define QT_USE_MMAP + # include "private/qcore_unix_p.h" +@@ -630,7 +628,7 @@ static QString find_translation(const QLocale & locale, + + QString realname; + realname += path + filename + prefix; // using += in the hope for some reserve capacity +- const qsizetype realNameBaseSize = realname.size(); ++ const int realNameBaseSize = realname.size(); + + // see http://www.unicode.org/reports/tr35/#LanguageMatching for inspiration + +@@ -643,51 +641,7 @@ static QString find_translation(const QLocale & locale, + // Windows (in other words: this codepath is *not* UNIX-only). + QStringList languages = locale.uiLanguages(QLocale::TagSeparator::Underscore); + qCDebug(lcTranslator) << "Requested UI languages" << languages; +- +- QDuplicateTracker duplicates(languages.size() * 2); +- for (const auto &l : std::as_const(languages)) +- (void)duplicates.hasSeen(l); +- +- for (qsizetype i = languages.size() - 1; i >= 0; --i) { +- QString language = languages.at(i); +- +- // Add candidates for each entry where we progressively truncate sections +- // from the end, until a matching language tag is found. For compatibility +- // reasons (see QTBUG-124898) we add a special case: if we find a +- // language_Script_Territory entry (i.e. an entry with two sections), try +- // language_Territory as well as language_Script. Use QDuplicateTracker +- // so that we don't add any entries as fallbacks that are already in the +- // list anyway. +- // This is a kludge, and such entries are added at the end of the candidate +- // list; from 6.9 on, this is fixed in QLocale::uiLanguages(). +- QStringList fallbacks; +- const auto addIfNew = [&duplicates, &fallbacks](const QString &fallback) { +- if (!duplicates.hasSeen(fallback)) +- fallbacks.append(fallback); +- }; +- +- while (true) { +- const qsizetype last = language.lastIndexOf(u'_'); +- if (last < 0) // no more sections +- break; +- +- const qsizetype first = language.indexOf(u'_'); +- // two sections, add fallback without script +- if (first != last && language.count(u'_') == 2) { +- QString fallback = language.left(first) + language.mid(last); +- addIfNew(fallback); +- } +- QString fallback = language.left(last); +- addIfNew(fallback); +- +- language.truncate(last); +- } +- for (qsizetype j = fallbacks.size() - 1; j >= 0; --j) +- languages.insert(i + 1, fallbacks.at(j)); +- } +- +- qCDebug(lcTranslator) << "Augmented UI languages" << languages; +- for (qsizetype i = languages.size() - 1; i >= 0; --i) { ++ for (int i = languages.size() - 1; i >= 0; --i) { + const QString &lang = languages.at(i); + QString lowerLang = lang.toLower(); + if (lang != lowerLang) +@@ -695,16 +649,24 @@ static QString find_translation(const QLocale & locale, + } + + for (QString localeName : std::as_const(languages)) { +- // try each locale with and without suffix +- realname += localeName + suffixOrDotQM; +- if (is_readable_file(realname)) +- return realname; ++ // try the complete locale name first and progressively truncate from ++ // the end until a matching language tag is found (with or without suffix) ++ for (;;) { ++ realname += localeName + suffixOrDotQM; ++ if (is_readable_file(realname)) ++ return realname; + +- realname.truncate(realNameBaseSize + localeName.size()); +- if (is_readable_file(realname)) +- return realname; ++ realname.truncate(realNameBaseSize + localeName.size()); ++ if (is_readable_file(realname)) ++ return realname; + +- realname.truncate(realNameBaseSize); ++ realname.truncate(realNameBaseSize); ++ ++ int rightmost = localeName.lastIndexOf(u'_'); ++ if (rightmost <= 0) ++ break; // no truncations anymore, break ++ localeName.truncate(rightmost); ++ } + } + + const int realNameBaseSizeFallbacks = path.size() + filename.size(); +diff --git a/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp b/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp +index fae3b4118e1..3a60040b71c 100644 +--- a/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp ++++ b/tests/auto/corelib/kernel/qtranslator/tst_qtranslator.cpp +@@ -254,6 +254,16 @@ void tst_QTranslator::loadLocale() + // more general alternatives, or to languages with lower priority. + for (const auto &filePath : files) { + QVERIFY(tor.load(wantedLocale, "foo", "-", path, ".qm")); ++ // we search 'en_Latn_US/AU', 'en_Latn', and 'en', but never 'en_US/AU' ++ if (filePath.endsWith("en_US") || filePath.endsWith("en_US.qm")) { ++ QEXPECT_FAIL("US English", ++ "QTBUG-124898 - we search 'en_Latn_US', 'en_Latn', and 'en', but never 'en_US", ++ Continue); ++ } else if (filePath.endsWith("en_AU") || filePath.endsWith("en_AU.qm")) { ++ QEXPECT_FAIL("Australia", ++ "QTBUG-124898 - we search 'en_Latn_AU', 'en_Latn', and 'en', but never 'en_AU", ++ Continue); ++ } + QCOMPARE(tor.filePath(), filePath); + QVERIFY2(file.remove(filePath), qPrintable(file.errorString())); + } +-- +2.47.1 + -- 2.54.0 From 61967f61f8a439d8b0f9691dd4e04e7a2941533b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 15 Dec 2024 22:19:40 +0100 Subject: [PATCH 1062/1428] Remove no longer needed hack --- src/CameraController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 5dc127a..c6a63ae 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -142,7 +142,6 @@ void CameraControllerPrivate::initCamera() QCamera *cam = qobject_cast(camera); if (!cam) return; - cam->stop(); // workaround for why some phones don't scan the first time. QCameraFormat preferred; bool preferredIsCheap = false; for (const auto &format : cam->cameraDevice().videoFormats()) { -- 2.54.0 From 9598e547d07fa75f0fbe25c0d26ebe803fe07b00 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 16 Dec 2024 10:09:43 +0100 Subject: [PATCH 1063/1428] Use new Qt --- android/build-pay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index f9d6dbc..37ca4d5 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="codeberg.org/flowee/buildenv-android:v6.5.3" + _docker_name_="codeberg.org/flowee/buildenv-android:v6.8.1" fi mkdir -p imports -- 2.54.0 From 9364b2e97a070b76602cbd72414f88ccf6f0974f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 16 Dec 2024 10:30:20 +0100 Subject: [PATCH 1064/1428] Prepare for december release --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index bfa4fe4..9a13bff 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="32" android:versionName="2024.12.0"> diff --git a/src/main.cpp b/src/main.cpp index 9e37171..716df76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,7 +89,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.10.2"); + qapp.setApplicationVersion("2024.12.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 7f06c0b1626e8a087fdcef18e9605050da242dc0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 16 Dec 2024 10:42:28 +0100 Subject: [PATCH 1065/1428] Make this a comment. --- android/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index f28523a..58ee0b9 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,7 +8,7 @@ ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin # for europe you might want to copy your local mirrorlist -copy mirrorlist /etc/pacman.d/ +#copy mirrorlist /etc/pacman.d/ RUN useradd builduser -d /home/builds -m -u 1000 -U \ && pacman -Suy --noconfirm --noprogressbar \ && pacman -Sy --noconfirm --noprogressbar --needed base-devel \ -- 2.54.0 From 673e816961090aca6726297d4a5efa65d645bdc0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Dec 2024 18:34:36 +0100 Subject: [PATCH 1066/1428] Fix package --- android/res/xml/shortcuts.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/res/xml/shortcuts.xml b/android/res/xml/shortcuts.xml index 899301f..1bbad1a 100644 --- a/android/res/xml/shortcuts.xml +++ b/android/res/xml/shortcuts.xml @@ -5,7 +5,7 @@ android:shortcutShortLabel="@string/activityOpenScanner"> -- 2.54.0 From dd596c5740c9dd3a67c68e8d222f187581b260cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Dec 2024 18:42:15 +0100 Subject: [PATCH 1067/1428] Refactor notifications and add Android support This takes the NotificationManager and splits it into multiple compile units based on the backend that is available. The 'dbus' was the only one available so far (which serves kde/gnome desktops) and this is moved to its own file. This adds android support as well, but so far only for block notifications (when a new block is mined). --- android/AndroidManifest.xml | 3 +- .../java/org/flowee/pay/PayNotifications.java | 123 ++++++++++ android/res/drawable-hdpi/icon.png | Bin 5413 -> 4373 bytes android/res/drawable-ldpi/icon.png | Bin 4907 -> 4304 bytes android/res/drawable-mdpi/icon.png | Bin 5108 -> 4695 bytes android/res/drawable-xhdpi/icon.png | Bin 5636 -> 4640 bytes android/res/drawable-xxhdpi/icon.png | Bin 6570 -> 4525 bytes android/res/drawable-xxxhdpi/icon.png | Bin 7795 -> 4603 bytes src/CMakeLists.txt | 9 +- src/FloweePay.cpp | 13 +- src/NotificationManager.cpp | 215 +--------------- src/NotificationManager.h | 43 +--- src/NotificationManager_android.cpp | 65 +++++ src/NotificationManager_dbus.cpp | 230 ++++++++++++++++++ src/NotificationManager_dummy.cpp | 36 +++ src/NotificationManager_p_dbus.h | 70 ++++++ src/main_utils_android.cpp | 5 + 17 files changed, 565 insertions(+), 247 deletions(-) create mode 100644 android/java/org/flowee/pay/PayNotifications.java create mode 100644 src/NotificationManager_android.cpp create mode 100644 src/NotificationManager_dbus.cpp create mode 100644 src/NotificationManager_dummy.cpp create mode 100644 src/NotificationManager_p_dbus.h diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 9a13bff..0cc3f95 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -6,6 +6,7 @@ + + android:icon="@drawable/logo"> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.flowee.pay; + +import java.util.Vector; +import java.util.ListIterator; +import android.app.Notification; +import android.app.NotificationManager; +import android.service.notification.StatusBarNotification; +import android.content.Context; +import android.app.NotificationChannel; + +import org.flowee.pay.test.R; + +public class PayNotifications +{ + private static final int BlockNotificationId = 9839874; + private static PayNotifications g_singleton = null; + private static PayNotifications instance(Context context) { + if (g_singleton == null) + g_singleton = new PayNotifications(context); + return g_singleton; + } + + private Notification.Builder m_blockMessageBuilder = null; + private NotificationManager m_notificationManager = null; + private String m_blockChannelId = null; + private Vector m_blockFoundMessages = new Vector(); + + private int m_walletMessageId = 1; + private String m_walletChannelId = null; + + public static void setup(Context context) { + // create the signleton as it creates our channels + PayNotifications.instance(context); + } + + public static void notifyBlock(String message) { + PayNotifications.instance(null).notifyBlock_priv(message); + } + + + public PayNotifications(Context context) { + // see example here on how to make these translatable: + // https://developer.android.com/develop/ui/views/notifications/build-notification#java + NotificationChannel newBlocksChannel = new NotificationChannel("blocks", "New Blocks", + NotificationManager.IMPORTANCE_HIGH); + m_notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + m_notificationManager.createNotificationChannel(newBlocksChannel); + m_blockChannelId = newBlocksChannel.getId(); + m_blockMessageBuilder = new Notification.Builder(context, m_blockChannelId); + } + + private void notifyBlock_priv(String message) { + // We split this into the title and the message since that is how the anatomy of android + // notifications work. + int dot = message.indexOf('.'); + String title = ""; + if (dot > 0) { + title = message.substring(0, dot + 1); + message = message.substring(dot + 1); + message = message.trim(); // likely a space behind the dot. + } + + // We append the new text to the active notification if exists. + StatusBarNotification[] notifications = m_notificationManager.getActiveNotifications(); + boolean stillActive = false; + for (StatusBarNotification n : notifications) { + if (n.getId() == BlockNotificationId) { + stillActive = true; + break; + } + } + if (stillActive) { + m_blockFoundMessages.add(0, message); + int lines = 0; + message = null; + ListIterator iter = m_blockFoundMessages.listIterator(); + while (iter.hasNext()) { + String m = iter.next(); + if (lines++ < 6) { + if (message == null) + message = m; + else + message += "\n" + m; + } + else { // lets keep only some + iter.remove(); + } + } + } else { + m_blockFoundMessages.clear(); + m_blockFoundMessages.add(message); + } + + // https://developer.android.com/reference/android/app/Notification.Builder?hl=en + m_blockMessageBuilder.setSmallIcon(R.drawable.icon) + .setContentTitle(title) + .setContentText(message) + .setColor(0xA0F87) // flowee blue + .setAutoCancel(true); + + // we always use the one BlockNotificationId for this as we never want to have more than one + // notification in the air of this type. + m_notificationManager.notify(BlockNotificationId, m_blockMessageBuilder.build()); + } +} diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png index 95f0759e16587b27f98f1cccfd1efc9a8b755027..d26f29679f3114d9c01fc181b6d38d0d8f6347ff 100644 GIT binary patch delta 1222 zcmZ3gHC1VXL_HHT0|SFbbMzGk28Ii%5uRzDo>~kX3=9lh3=E7c42%p63?2*&42(!@ zCProkkghfc1_mKUX0RHNIw?k0FuR9=fk7I|o&qu*s)mVyfx(cGiGi1afng2<1A`GG z6GOcS0|Ub<1_p-Ac8F#B7$AUy0R+KT!<@jtz~Jub8=zpMXK0{jY{9@_WME`sWoTq& zXrTZi&8-X#7#JBC7a*JjGV4R5BpU+*V@sy9bAYF_vqC{pep+TuDg#5soZ5-?wjPHa zB--vT^K#X0DibI?5nz4O)m3IIm+K0Ftky7DAHBcLYP_CZ-HRVY6?{nF`0Bx48C)saO&YdyC=k-6Czx7%7k{ldV=B2h}3g-*>=ig#`Ui*kc`=r)K z%a1!e#Z~H_?0WQ3`uU>MOy_%M%=}_g*b<>rw&BvjH7Qv~f{z4-Ue=61QMIP%-lH|4 zS!+uD{~aobi;sL}bv96CV~47Tz`_M>Mp_#Lm&d9fb((cf*zTX;b^VFnis6zyGTbYi zn&14YKluMadH&i%Pg77(cDHa}zwGi#CYJe)+I!!wH*1*bf9Z9u zZWRkp4zu|6T;H3_dgTod)t${!!?`YraIrr4+p|xTo#!so-t4&hQO_-E8H}#1zQS8j zz;UE&%HDUM-_1L>?{D~0#46I~PIv{c(* zX&V??85n>yRpb^}`R1o&rd7HmmZaJ$A<`zY>d8~s+Er4`Qj=0lQVn&@%?-?SO^gi; zbS(@GEp#oC6O$87jf{;fjEpDSv%3nL=o%X78d@M5uz5OrHb=eQ=MQTb7#KJMd_r77 zNt}V<|NsBZch6q}< vzZ03g_G6i{++Q!d-g11j+;{J2 z#kBuB?cL|k+AbkbFVOk;j6v=r-bXWiZ$H@MFO#SmscNS#Xa2J3NPkSYP2J;|lE?=$ z1&t@qQ|}Y#KFSwnHDjtzZ^bN&RasU~xg+~S&qdGK>eajT-N|@~bFZ~e$1JyOQW4w8 z!l@*3CBTikb{afAdSVNb`wS^TcGv(2NPO_CMtRysN*a<>OoB`HYAjPB$=t+3H#sHIJkiwDGTGSDY;qTCl%|=5p@os5 zrICrTp}B#fxe}Lxf`YrJZ-7-;YEf}!ex9w8k)EM}p0UYfUbaACV=Ds_D?^JALlY|# zQ!67AZ3Cmpg>2GT#pkoNt0Y+_86+8-8tWP+nJZI&5PKxIqIJ+kL+e(U}gyL32|ir0b>S+GzNy53=C%&82&RDGcXt% zGZ?2a7|&!dKEq)AA0(M(%#fDGkT#Pc?F>WOe~{Xl#tbvl7-r67n0ba_=6{gZGsX;O z(iqOnWH@t%;mm)A{|pTOjT!!@G5nv&@c#_M|NkI^8H|nV8PbdyW*Re`F=qG=vdGxj z*f`DDc&4%O8Dr!BAbZn{jnmSM(`Fi{oiR@P4|2v#W8;}=#xrLc&pcy1^FPQ{XN--{ zq#2)?X?*64@tOa|{~3(`8yo*mGyXr*`2QK>|NqlKelku2xq4`a>Re~_2bjMG41W?I^rw6y;qAI>yRo0*n2b7tDiGifvbgFJl37$kXSX4;uEX=nbY z{bxw~Z=Ci&E$#ozwEt(){{No|3O(bQpzxVF6BGjfL7@ZkQ`$_BtIy0d{tpT)ki}{B zGeKsbnVI$<6nG%{w3#4qX6DTQps+k+46^sk%$aA-%slgd=6{Bn|BYwDf zGa$47gQDq-G000G{+Tmp{-62JaOS`9ng3~L{?9z~|IC^H|3Q%mP68ljfMXRDr{H)3 zxe6TgpeP0hCdiTC@Bzg**vlX{gFOLC0B4Lrfpw;SCdg#4+5i88V(R~Ykgxv#|F5Vz zK}D|P&BRIHXH2%!={$Voo`?6s6vysAvnMwM_kREW>)Q2aJ$;8mEV`~VPy9Q3^6y!b z?{!X0b?%lo=ri=4>JzfuKX`?LN}riVr>kL?l~$*KP)EV6b*o<;U;gZzDuficP3-G!lpRn~)nfq}EYBeIx*f$s&HaKu<&uRf9|JhHxt>11iUYe}H z9LrJUv?uCR#h>r(+K&vjP1Eh!TXy2}#d!zb_WSgg+n$c-;Z5Nda&o-E)5!2?i-QeI z?Nw)Ou7!Fmi4U$jiZvd0m~bRZqufN1BU$tc?}Lj18$|@P{_OW&kSe)r+S)(o6ifLQ zBgK>yxtQTEmMCj8dK%Y1^F{)6yF?(tIzNG~N;GVCXk>dKY8C zy`mg~ zbmdKI;Vst0KQY*2mk;8 diff --git a/android/res/drawable-ldpi/icon.png b/android/res/drawable-ldpi/icon.png index 4efde19ac74db745b66378f8b22aec19b8b41f97..74ac67ba6de61abe699b45f67c37fe2d71901f49 100644 GIT binary patch delta 1203 zcmZ3jc0qB1L_HHT0|Nt}$fR@z28Ii%5uRzDo>~kX3=9lh3=E7c42%p63?k*UW9>xVHE=dLuNa~vV9B?z`+259w4h37#Lu*yQgn}f{~t~fu6Ai1A~!)k%^U| zk(Hr^0*ExXGB9UgWMEu?a1zL@gU$)}85kH_GM$|RJe{2t3X1a6GILTH7%Jw}POP`} zIP4(Nc7K_dt9DbFK;el1>zl5wGF!P^R|sUahROQq{cTp`_3Y|i{2;2}L;A*74<4;q z-Mm_X<6nb?wT0#`kIwt5tzw39ckUjpzW=-EyR3qVme1y0X^g(x&z2Y|rWLPFdLf#~ zv#9^tj59jPev^Ve$i{!?`0!Wn+~z;8|H=HV&$^f7;Gi-uwJlRPU%)^A7TfdMM;zKG zwLV&Y+~Fy%Quk!nqmRU zdYPFiRz}ImX%=QF7P_X!W(K+@=9a0ti3SFix~aydmZ@eb$rc92#z;o^7iFer<|XF1 z@zk#_zIQc?w zK_gE?@5+;Hr!GuNO;m9^oR_fJozsfdY}o-P8{TVvw(;TYkGMaWi`Q;4-&?>kFBIff MPgg&ebxsLQ0Ld|Re*gdg delta 1717 zcmcbhxLR$3L_G&H0|SH0lCEP63=GGrLLy3n63Z0|it^Jkb5a#bDhpB-QY$jk7#J#U zHJuJ#M+ zuYzq3ojAl{(<;N3Tc;g*`RSk5Qt$8=HdiuvuNwybI-C0Ot!>wS=4ZT;CnYCXGW=p( zSgCMM@>Or;k)~HM35K2ePwvkvyTER>J@Q&g^S7xJ|H{_O9F4!+r6lTT-C>ck!cWZ6 zRVG`sD?srI*IKI?F~3-Sr6!4pyC2;8;KA{vs|SxphO>t|3jARxEG-NwnJDysx=7TK zih?`+<=^GX-!m)>Oq?a!#XKu-_RAEfGta_~z6)4jBBi=_deQ!V5<1dq_8mVfhE@%F-=}3P}xJ}*Tn3Bi` zGX;$&&r|Oc=swC9W;J7~PjAI6i&a@xPq`!eL(fIe+3MB1_1(#MiF2>DPsc2`Y*G>1 z$ik^4awWix)hubtt0fU0m+KB4w}10XwMg@cR`bMU#?XujvGzaY|GcZersd;X<@u21 z!q-O@@{1k5>s?r5eeV0l$0q&_4~z?zZvT6g&EU6Z)!wzP{SE(eA3WN%RKd9`8ta-Q8Jp@RrX;55 zrWhxsT3DK;B_$;qPyWLquVQA9WNwjcW}s_gY@V!ZVq|HmYiVp~rkj{*X>5>|mS|#W zoHW^pcz$`h*z|1l&B{^+!7i*NJnT4T+ zk&%Isfq|ikiHVUCmx6+VyQgn}Rak0Kab|v=t&)+Rp@E*U$z)!(Kw)Dm0~0Glix5K- zD-$y-Q%h|FqsfJA(pbgkv$dY5}c8|hk@B`4}8r5af#CR-Yq zB$}p8_F{JxHr6#T(S;gpZe?JgZD6!{5qmaA{o^Hv{1_M*HwXBHxN-}0@QZYsTF&D*>QnY|9*dd`t(Cn%K+jBKT{$f@V%56!*RGv8_V456KkqL|$@NHU*7xMDTOlCYRb9L1 z`t?^*@_oF59eT>0a?%}I`cr@W{H?A%MO3t7fBA%eb0+_oIl0KI$Iy35)xkB_@4Wc^ z`_H5)$Ftme{?DEAZTe(!u?~yK>EHhT6qe|&@az3MXR@M9$Ewwr#ie`H6*^x{oU}5# z|J$D*uU>s#z4nT`_hJ>f&OFba`Y79OexVL-!46*Gj+Vol=RDgx>**d5*=}{6$)Emw z+rIy1-=i(UvR#_GQw|=wZxcV=Eq}IK-Yh<`P6^rG%Ie**32T+bF6>}nU|>x0c6VXu zV3qY?U|`@Z@Q5sCVBk9f!i-b3`J@>b7}!fZeO=j~vk39%GhAk_bZ1~-l&JS~aSX9I zotz+Hqma|&x1gG=6mL^whpJbJ_|w5dsR!=%aGMvVpsxg2=Z7c4lyvVv(r zmMohIE6+QxqN1k)j(MD%occVhJgW-ot+%+ix>_#avUv06jm$%_)lRH`Yid_7{idC3A5+r1LY9GpEmB+9SadkE9bCD1rpy|yg$o3dSyu^Snk*)em`ULv95i)9V4E_p0{53 zv1VVAWbI3d_0M+NpLY_OzOZE3sY82K9JnQJeOb%AeofNjZSUnf<&Tt2Nt^h?bC2-S ziHVzEFF&dB=cCE?%;PtY?tHm=Tj9&XuJ*GNIFp66tS=UO{4@A8E0q0cM5Bu^#l=ua+i*Ycm-{&WBRZ29!B%eGpcZdO_mUA*dA z2Fv^Y4OR_b(_N!QZI(nvSi+}tG5)GWo!$kKH3M;3XN(<)sOOHyr>5UB=P_2emR?JAb3rbY$^28p^x<`x#Z zCMKqqx|T*M=DNwrNrt8drl}^DX_k}i*XS66q{&B;K$DMz=|^F8~q z@y#Y(p3A5EI+*IaB4wv-Wa=_LIMt*0_8DfY&*~B?8yPLPGOE?BY2NZFpzwo3Yz()9 zu}%4&)HE}J69p6frhJXEIGonK^}_kmkoC9ys=`cG_^v8;Kd8<=iN}a?`}`ndRg?bz-`@KSt5?b z63V%A+a#hhd$#>%VHRKQRP7xa>G1f;xe02LI~%@T&YfF+rY&|$)hmXLVVS2ymrr%y zVpW>g#j<5~-8Tm99;5IgZ|8pDXDr7v3m$0NiOzqZ6K>dGv25|PWX@KrWhbsz1v5w| z-TA>o`mxYx-qVSSJHanBm<7%Fb% z1~>{GR^Zt4yUUI-GUyuNbEjeo*Dl*V7tuPP02~ zBM+ZA#9`AS!&X|;-Sx8O-=R$xMXi)fqYiELciCS)v$p#6llKjk&TN*inevesjj;GtRM)Ci*Ey{ZtZ5tx53r{OoXJBA#nas#2TwgoU*5j~)%+dJET}q;k z)*TipEBwS9U1hRGy8;xiaILkP5%Y`HS89@oxckAa4;~y(x_awa~ zweRt3ImUhWj#fT>2Un~wCy zgxl0Tjwy+JFjLTY@;voEf$pPxVOBGy`t(-JvRIX6^^`lZKlEJmoULBHTi>0GmpJ!Y z`*h55%O(}EjVzo>B3A<3Sk02Qyjl|Bak=i$ar-yFREsp9Xf;nvW(>`kP#Gr=@*$jSrR_$Hu+TZXm_rarG zYc;D6JmoX^l5%3%8!yLMP74|K%)e;O*hfh z(oi?eB+cA3CDqK_(!_G|Y!-PH3p4ZNL^DHkT{DZsWL=Xab8}rwlSEV9BopHl3kw6I zG}Bb`$@f^)RZNU5lMRg1EOe7B(=2pNQVlJ2EsRo4brVx8%neKpjg!rjEGBERMroQ^ z7+M$^85&rcS(q3anksQAC@8pl`UY5qr4|)u=I7Ze8R;1s=oy<#Uc(wFY;0v~6NphNzZlZ;yiLR+xsztIv zQmUn?q50&OY_7t_x&|h?s0MF#WY6ZPPZeXaV_;y?4)6(aN3dhp9&0Je(Tn&-u@%0`RnXc zr%R}H^9Z)*#8ed&teZUL_``?q{`~o4?>wJZu;bL}M?Zf2uB_h4FVflBeelnpzgxE6 zFteKV_wT=qtc`UId%l1FrKUBhta8_`-M9FJJ1<;#nwV0*Zt>Fdkuj_Di?((59@)M7 z#=n1m{{H!;rZvUYbHT#J=MOF}{d+m@=Y@PFr8Z~x`Ae5yI5xl?}6o}A^@!zCd-y8?N+5^}e1sNl2vQ#?6{MMdBO|1uF6V@tgOfpdItl7M2(Tjr%p6-`a=~=qr+?!vYUj6u}t~(`v{ffSa zo5fYSH*LNa8<_6Hz`($mO>_%)r2R1cVu21s zKW7o*F_5i|2Z=CddAc};cpQIQKh@Ctu!Bg0|Hh1^8lHTe%@b#NvbSGrFbvWZb((IK z`eI4+LX*WWr#QA-aL(m&wMyT-!=rxQd)1}oCsn6D$k_MvMD_dXJ-cH#6jfO*Yu1KN z5O*qDeZYOs4EDWeKJ8nqSyIT-(z4?Cfw(O_D{?)PH||>(tPsqwrEFD=UGNLF#;W?W zA^&&S#V?q5^18j-XAR!tu02}|)~`e{hajtcCC48zeThVgUGbA&NM$fPR@5`s#2t8Q zyie+TtP%$!m*cjC`IDTQ=IvRdeb7B|mf{J&`6-;mH@<}2_~Q4xQv2`LzeQ`5jubyObzgmHTfq}u()z4*}Q$iB} D!D?Ki diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png index 6190243e43e9b5a4145aec48a1e036cbfb3614c2..dc28b8f7820ca9e8e102f3ebf9693692f410d7f7 100644 GIT binary patch delta 1292 zcmZqCS)ejOqMnJFfq`NExB06X7#J?3MtG)qdTKFnFfcH1F)%Q)FfcMOFeETAFfbyq znHZTFK)Tu(7#M^YnZas6>ZBN1!R#Ie1_o&;dkO;sg9cO$NRJ^S69X>;1H&8!1_mQW zCWbT-1_p*z3=9mJ?GVfMF+czZ0|=TkF))Bsz-V_*-v9+8JwpRMV+#fbBLgE7D?=kI zLkk5EX>Mg;z`)4BxB%fKkXhDukItU>QMO*9?fx<^SM8=Ufx;63);C>UWwvs;t`Nv- z4U_fJ``fI>>)F-4_(4>`hxCoF9z0sJx_Pw%$G-*(YYWX?9-a49Tg43J?%X|GegAjS zcUc7!EuYQ1(inZWpDi&`OeS?7f9{s~^!pXjX^F4-f)y~3&a&98(1AC%YUuRZjnWx9wT!(IKp&xUL* zWsPZf3-|TQF0W)_nct|r_w9PKhME4CUgzpovGC+Di(k+6y~(Us-tbV}*(^1j>yii; z>w~{N`!v~k?lSGoj=LZA+@hAj=*sFVyafduN4lo$efRm@ymR~hMqhq)pTAM7XwB|P z489Bue9@U95hW46o4c7DnN%zdl9NnKjZ<}#6H|?JO-w9}bd!uM(sWZ&OpH@Z4NT2a zQjI5nV3Aj`urNxsv@lNAO)*SP(KRtOG}E=POf}ZEOfj=eF)%etGEOs}Y|N^zlA360 zVwq@^rfX`DWTb17mXfS%X=!MnYi3|(X=spSoNSO{F}aR4O4Hof#MH>Z%*epV%)-Rj zScywPK>?JWtin=@iZk=`Y?Yu1YceBSpd&P4g}@S)wt=CQfdN=kMQ(wWZ+=Q3 zNvf?9A{ip9o;;bYUBxoV&>+duJXP1o($rYj#5l!7H_;%~Lf6vJ)FQ>yz|t}`$z-w} zyQ_qWuAz~xp+$(Hft87+m60jbI{nEz*pgJt3@uVql2cQ4lPpavbxo2IjdU#ylFW3C zQq0WFl1xlZQ_T!PrU=7LF|slQ;|8|Nn0q zF4zLf-6cVO!3+-1Zlp~X6c(@N7G_}J7G`9aHAjMhfq}im)7O>#1&16-N1SR-;D!45*L3m)E6s;e%tU)voL|N z*y7nk_6XSw_Kbwm1sUt4rRU}=9Jl{I?Z!`5euj$-aupBqDppMXs?hab7@(S`HLU62RbKhQ!lKOvFgdJ-aoJ8wSd{CJrygLsvKAq zn(1NVyXD}VoDUA1oL`svw<~05E;@I~`N7`v?FV^yBkd%A+D5f})e7OR|8LaQzQtd~ zLoPAoutzGF4BOhH;j8qY>#vF4b?Es*zw8}-otN@+xwm~kQQ_`tmg<`jI-5De-s{!` zi5XW5C%VtO_~kJLgB9*=Eh-=^D*K&;e?j5a| z_J60n`}|qkB?RgPIv<}g$bH27Xr}M&2YdWw5>+Er?bPMWUp5`-j|sP_dmK{|`Cz7? z@#J~xeFEJ_`NFJbO!eulm}Rjl%jzk2WPj+n=s8=xdbhqi8830}wf5~w*0sOkU+#lPyVhz}A9%`V@FnHM zvNv9ivz!((?3sVj{At6%=mq6xU$5QTf09c3}TFpGhKw<0qnq9nq1^K>RhCY9t=149cFa|>OIG{YoalN3`6-NZz5Gu@
6#gtS?HRW8Jg-Q8dw_Yf{aSCFtH)rJ$hT?&%v~6_#35oSC0zt7N2SXrO0oGMSexP}tbYz{JYXBE-w3!TPXBg7{gVfG6W|*1AFmoou%rgu#|AVxi zF=jZE#&BjP!y<)0p9mF~fh5 zMaIU)#%adJGmVYU7#sfw*_&o;oR(&sHq$umjB(n3kTYf)8_!HLo;lNa<{9Ie|3R)g zV{CjT&G^hr<1=TB&-^$3&tUxD*!X{%@&B2||IZlz|DOi(lW`iz)icvT?)ne%0?4NN zv^0=mXVQ%SgS?bxoCX3j)6&kQrTqu_aHetE%(S$bGt*|CNt^i}E3LTK2(q@8OeP*Wde^6k7 zEKaMR2{QZ4%(VZYzyryr%>;onGiUw>h2 z1;-P}Rp6ipMKL%qL5>864=BdLUIw`t>ieD(kT ze?!x0x9_~%wfj1sa7S9knwPIWMntcamF(~{?P>_>>5uNM_V01j>k=31P*R%^9<}_? z;oGOrJmeSbh_&m!-ZJse?8*P{Ru~$K(90C$-EcI4b@dN^E}Ne4HV5-^O(;@%ujKdH=PV_x9z?`Hrp!nA8?B zGwfKmrS9jych*<8So{u>yU*|-M@M=)o6esHH+FM9SmE1ro3Vgv#S{isrE}2?J-ioK z84?zTGj3$oV`!)s*vItX*L4O4Ve@m$&9CFx8Jw;>V3_?NiNV8yL4L}V{8v{RBAFks z=S!#PG)Qmk$(1}Nd1Pu4BSYoV1rs-wvjsIAwW-mW)nwBknsW9x&w*6wtNn>x-Q6+) zPvUt!R2z16J{C%4@O*W9x~YP2Ux1GZ!}r9}{ejHiN}Dr7mCwY~vpeuOF5BeXa&+b$ zxdyZ6jtR0Ui*y1QCp@qTjBKda(3fAJwty>1Lq=)wuG_B#He6?}5oK~ZDDXFgX-#J4 zt}NM9wEOZd3{zf*B2!~PeiWp?b!%Hx%i_CbyJso?^l>YxkB`hT z6yGG@=(}~rjcTQ-UmZA3?9#seiqDF1M|1fH`{W1pizhKXa@H(f^)MmKR&E;8D%aFi z4;RQESd-siUBO=P!P&&>2ScRExhLDFF7sS@KBysg8ds^z1l7>#_3zLBs;htSjklq> t+~AMIgO9eiUL=$l?DLmPF4*~x>DvSUz4Q1}+J!--zNf37%Q~loCIH!IK3f0) diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png index e86054c7f5798681ee1362474bc1facb0f4fd4dc..db34019b731a5e14ef92cd6fe3117279c3f31510 100644 GIT binary patch delta 1426 zcmZ2wyjFRFL_HHT0|Ub>4K8g428Ii%5uRzDo>~kX3=9lh3=E7c42%p63=UWwvs;t`Nv-4U_fJ``fI>>)F-4_(4>`hxCoF9z0sJ zx_Pw%$G-*(YYWX?9-a49Tg43J?%X|GegAjScUc7!EuYQ1(inZWpDi&`OeS?7f9{s~^!pXjX^F4-f) zy~3&a&9C}{{~wg+uRZjnWx9wT!(IKp&xUL*WsPZf3-|TQF0W)_nct|r_w9PKhME4C zUgzpovGC+Di(k+6y~(Us-tbV}*(^1j>yii;>w~{N`!v~k?lSGoj=LZA+@hAj=*sFV zyafduN4lo$efRm@ymR~hMqhq)pTAM7XwB|P489BuycL-tn>(3Ynd;5VjEz%_QcZO& z(~>N8O-#%ZbrX|~jC2jm%~FyK3{%t0l8ltN6ciLHatnNYtvvIJOA_;vQ$1a5l`3)z z^fEJ3tc)y73{uSvjC4~?lZzbq_8R{k_CmHD)nwlD!TclYS7^WsqZeWelG&eRe zH8LX*&=HzILtqJ1+rZGu0PYei-~5!! zv`Ux6l2ltIL~=$}J$WiyyGn{hT8c?hl7X(NWnz-9iLrUAZjxD|iLQx-Wooi&pIuM~GX%B7GZ6 z2?GP8mZytjNW|f{*K$uCHsD}LP-H(bv8c5F2_wt)ugV+ikJY?cx~D{-K4)RIN9^LD z22t%g`$c<_qnGA>{FjpLV)RR=V1L7`mb-7n?YWs&hU6%&%X_Ah z2R=^olUgS^-EsPOqJDdKlaopFhSt9L!x9%cmNq@mJ7TWzFW!aU*{Hjq;ZUx|xdn@( ze|e}~xOwp5pF`(#ECRG{-*lWA7;yK&ndP#slh@2FiC?hf-(ut1_73Aq&ad=2(gp6c zX0SaJ{n^jJ)urnq+~K(Jf`#=Jjz2*c^ZBALcHW*N{$%=k$F*kh84FvL#iCw3#B8lu&eIKlgQCIH)z4*}Q$iB}i=N$O delta 3393 zcmZ3hyvlfjL_G%+0|P^bO6hwB28Lr*ArU1(iRB6fMfqu&IjIUIl?AB^sTG-N3=9>w zaswQN4l8i%`P^m4$jEDVe9KFp`U}s?LnnFOde^gK!!a>tEb|{c@tgdbVl)ZgnyIiF>)O9=gB3=aJ%D zVa^lD^EG-xmQ6Uc;B0>3%6)V8Z^)}@J@0J0=Ao6^qx*Nna^6q;*w>XaYl>iKF5iN` z8kx+7N0#&*GpTS}+vw@j)cWW4WxihwKK~-GJK1uap7>X`Ugl{0MJ!#MBM%0)&~!cCtW>wG%}n$+)>~ULt$xQP{~B0|IET@GGd*)v>f7);`dO`Ww*K4=-UuIWuj$NA9(CnPB#^dH|yZ7&2zIl7! zc=4Q#d=BCDmDXEqwX=!ODle<`>G|emw zEsTteOpMGdjVuj~l(-ZW6x=<11FXVQi;6Sz^K6xj^b8I3j7=u(J(1h*Tllyz`)Sd zAk{3b7}!fZ zeO=j~vk37RX??$PrUgu~_7KYR>4 zz`>HSHf-(I(=yZz1tRgD|h4H#mm!UyEPQ#GFG3Kdm|?$0j-~Ily^_V<6dwc1wz~+MqRRKXEOIN-sI>XYo-0s!-mOZ8Q4)uxM z3pEeb^!N(K%XU@&_;4?O(aJtv<9l}YVd)|Dd+SP4T&~}{w^nCW?Qhwmd!DzRV`%eB zejd*ivP$~J9iM)UK8|nQMpju85)xWEYa%~xs^GeAcrfkk!_z5G|Nd=uTYR#nkZHqw zu3vH2uGQ`T;JVQCmuGfkrM;h<>+`bm@AWf2bQw4HPJNho_u}Hx%jutF8QyGMnNnJM z`afTw9*0@juHE_hx27asPguWpYTW+1(=MAWv~T>C3z|Rq^VX%i+_{@;|2(fTNt*dW zT%FDNXokr=o0|y}r7aF^ib{LdxQ@px?A2@i-g=MC5)l;@YP-Jq`gChIEDVbJw)VVm z8N;@u`lT}>{a<7yy!fVO{Cdmo<-P}>*=WCbwJTF9zEE((#^0~`?arjyuuJMod@ua; z*?z1x;1>=it6g$Gw1F*m~cm8);TSSSFPpmbPR&l3E73byK_-Y zWX|0E4jsFKyYFx77w!qOsXZyV`NzhM?pI#T=DD?S?b^8t6>l1C4d<@E_~n9Z{`tAt zu^*a5=R0oT{8k)%=7G9ez1G%Sio41$CqA6H<>0)!up?`x1zxm#vFq6S`yV#sUDsPW zeSWF@(;F-~I*i`IyE4|D3=Q2XAp9aWj5lb##jJWP`XKl*!o!fE`2E=_5|N1raV|3d4`g{5> z8d-JHnZ$%YOa3#sec=OhcC=g$Uz?ElfAiO0yL=9)6?>SRJ-jBM{{MsZOhGp;ZW1fBz8q_~#YCqzbC$ijQm*q}XPf+n4;?yx zS|4=E*=RQ%&GR&z_~>u>zw%*CHow6&!_hS%BK2f1d)Yc@pw z5WmAdsXH_GMCXl3uK6V_2Ha{h!)>g*9)}mo%1Z5$Ue0jFXy!MO{VP^XnMp}S_PN7kvp8Jp@QYug^JjxaR5YL-x#&Ncbfev`?zU*l_PSJp4x9DMoq z?ek||E?+BiUO#2i!fCk%OSV={b(pSLKjYQ&CmK>^-f9&$8G8Oa-0^d<%BxM@%&Q9j z2{CydKT>lpXEpcj1nuV&S9~{K{`J5b6}LApmn1CrHy6sYY3q*5Y1&zMa`EjWSE9Zf z&(Zt;HAbB!cy<21EgnZ|rcUzR(6CcQYEiwx!2qqYj=4N<-ra3ndiu!YnNv1#iT#w` zr`XLl!%gY)=VCd7C10OKKfe3rxYmt6bCw@b8heXw8eg8v!jrl8UmNSTj9aD>v*w+d z{gNj^%J=-NtwG&)mT%_z_Ar}Cd9L2aX(49Cvyz(^O4byAwokWL-f&}apxMDD@sIAt znGLZXUiGCfJln&pRfMMgu+2OvzH1}bvvaDq?L3dNcSqMQa8xt6mOU)SnGTCXn36O)=RtR++_RwNl|p6)2WGXw)uz|Tu+>~ z?1ID6%P-f*1ggIYVp}(@BI5Jb+h!4ezJ65ZZ{s=}lYafEQN78nn<*d7fBsH>)MsSu znW>+<_p;%)yl3b9d}Cvyqi(G{vG%5RxTl(L;^VuwwpH#qwDZ1s-E0lV?-!4;`@Puo zI{3sB#YL@~bH7y_%{6*yHc^S?;O~63Pii|p-`TsSFGZib zdVg@QxnW^bZRa`8XGf$x>lt|tFrXqGu5X1-b^Yt+8||&i{da$Lq}}|`_nuS!|6Ata zJXN}s)75nylh_w^$zN?=nwLwBjI3wBm0z0gbNr9Gt*GezonjKDyH0ISxyf>dBY}Y} zf#DcWPT&HW12UX%c1{&yU^YNS^=NzyZgI_q>CUQ!puG)U3t;ma^i3$uUT<900XnFg3;gV^%(({(fEqpArKD1B0ilpUXO@geCyc C>kz8| diff --git a/android/res/drawable-xxxhdpi/icon.png b/android/res/drawable-xxxhdpi/icon.png index 2de449d0c29fa3449fded8046f61ca68540cf6dd..e595f04eddb6c5b7c15481c1cb5a5040072fadb2 100644 GIT binary patch delta 1503 zcmext^ILg>L_HHT0|SHprj{uT3=9`iBRtbQJ+&A(7#JA17#J8?7#JBC7!EKnFfbyq znHZTFK)Tu(7#M^YnZas6>ZBN1!R#Ie1_o&;8)U2oR1FgY1A`$W69X>;1H&8!1_mQW zCWd+u1_p*z3=9mJ?GVfMF+czZ0|!bI#S&i4Tt9$W-sDcma8(%$mv}Sel zY6Xse4HniGn!7wY@2j?o8Oq(cd${`k@1pOr3MN`Un|Gx#`ffj4Vx*W>ygKQHXd=&| z{%bSN=p_413i==$|DEH*U%hji|GfSu^S3_hUXp`@%DmLJOyPV1|NL8Q&ubrXXrI*j zX!&u6r?^VplUyskgdTQOX+M}~Wa zQ}df&^#}hyD9>Me=t;|T5kH2z`hA}b*;>jP)9x1T>z7?#$;2|hQG4&(^=1t-{V%=F z)vaRT$zc}1p6h#)S+Bg|p}MnKYB<*=5iZsTe|z?6vh&KYnZ z80nfATO{dPTAHWmnwTf0B_*ern46fJDsd?&C{*MY`1)FT<`tJD<|U_ky4WgJY5~(CF>@pB`52e85<@hr=%LCS{NlF8R1`)nVy-KnB$V4 zT$(%Cmz7^7HPPJ2GReq9*V5F;P}jsV(NZ_b!qQSV(cCE6%+SEl$T-<#asz9Wrn#|+ zsgZ%1p^>Grk*SFp*icZqve5VwFy z9)m|b0|R4)r;B4q#=W;UV*8FLa4;kY@gJDDXP5gU2DLZN+pX)9G*6VwE-8PITK8!A zQLQpI1!ZOB$MUYJX=%6Y6pV}i|J#&#cH;IIi|@sY9?EH|ec=3md4gPX>;>1~kC?;M zPOvRWOJKOhZs2e^Xd6SOW^}_ZF_t zH4W|y@;_^Z^=B-8cayWBp6LLK#g|ol2F(j^FPgqh@`_{w^M~b?(|9`tN7XdFSYLN9JRn@`OK>#k5P_sIK}K=^4Fpk+MVBYNOnx%O$V= zW?*pr8_jU;=L7}ECKgU16^{uInjLzXt}wo;KbM-4lJe*852np;JO$Ir46cD9)zj6_ JWt~$(69Br^{WSmp delta 4627 zcmeyZ{MlxLL_G%+0|Ud|eZK=37#NOKg+!DDC6+4`6y>L7=A|cD|t#wkxxBOTtW4i01Z`aEW zQOV5)voe}zmV7vHIr@bD*8&qIuO!j$xBS#Sb@kLw?$d7JH*6MOezY!GY1i|p4SP)=%61kCi!pV-ka74m zQGwU#piW!2O^H)Zz)|&sPXFF!y8mE0(G+E9oLjbT;$PW%nWOQSyOcy7tvf7IR``iI zy2@mWb_FP2;aY1oBjy*Yuhb+Farc8;A3QjoboJoT$Z+;>M}a>Kg{6f-B@>1IPZx8Ubmm#u(Rbl31`P&_W=xowdd4$|Yv1G7a*X@# z9j%!5f2Y0s{8`&21nLDkAD=PEeZ>1{rtj?sd;Db*RU=jH)aA@yHXZ4Y3Ad?x98(hc zV5Xq)M3_*f9SdBIa|GYx4t_WFLCa*_UV}AmQ5;R z8(BD&M6Lw5v6>}qd9@_M<8s}h6#cACg~<6C#UHqCZ?vQm?s)1rwM4DFU7e(S=YXOvA-|zd(-6O-fMGS{$|fp ztvnHRX{|cLR;4Ag-d&q@y`}Ts?QODryuMyOHj}58etdJ&Y0{bb`Ze>Uwkeu6IbTzg zlX|?c;(_!G%cI(lAH4G3s2!pDzW&6jqsH5|#+(+JEBcMUOn*kBzSIT_!5E5LFXPdFz@sgx*dCf=Hh?tmPTeFw;_WDMx^;>>+57VfCpFtDII}y|*K9Vc7G$dqR!|y;8dBxwF^SwzIqVtvud#V$W3f;NZ{e z}~` z!;8kJ$K|=F>2fP7|Nq8zVjutBtgH95!@K_Pna$&1!#MGlYHUvHqYUYuZ@2jZ?zc-W zJ#TJmx}EnA7eoElO(te$u5PQ^`6l1I5pAbnbgQc);Qr3b2@hf!R(y3`eRb7UOJ=!5 z9q9`j1V62KA=2!?+Hm9B8e^k#{yo{| zV4fKhW0h&l=#kUl;putu_3eH^eFf?2UwglQ4++0M>%|Z4*;#u`@Bhe3vTQlmKX1zk zn}$5K{p5tf-yxUYVx z?CCEi%Qox!mQMXrJLBdK%{g=IRaK`pc_{7euKn%ztxQOM!SnbJ+B$pA{pZNPzfV|d z)|YdW5|r8&A6qi}wZWc2&Hw&MvJi5Pa_3HfH_Ikpy3v{EEPDD&R z-)L%@SzNsSOigWjt^DS*lSQN#+&`O9kvXfYq`pO3N~%(di`V;6eRRjGi{2ZOm8Z{G zv!=U-Z{@;;ohCNQ?x%NuHF)k4ZOFW9d)CpF`odeR7cN`Y=bt;7`_Yz{TKgJg=Cnv& zoVfbyq;lsUU+S{5-=BD5_?OKfZAr3V!@H%^h4-drv&cI?Dft>E_xNU0z}~q_nRxf@ zdGzQ~rH{>pde(P6>Rd-RcpXtPW z`$&17YM&U(i@2cR&zAFdy|(9Hy36AX&x&^DwLwRV>JELsnclvuzGOLL$K$p2*0&BT zH*8V8^UB`vBhUPeGYlGHej9swKHnkh;B-jx;B$$REn*!pe-|$b+F@IBCF4{DqY|^z zKlTqV5;V8-1;obwy_Bl>EcWhQK8A^BX318(zLx9Pe1Df^F>@tDk>G=cx00PxzJ85) zm;Llvtfc+L=$zZl5lRYOx8~JPEp0sPUhv%S@G@V)XDlhH&id8){M#$1O`Mn&__a<) zV#3rGiw9+|?KNIl7c(ug@{~~Py4|~`SNd@78wQUVJ~NiAS-bb}LlZu3ZzFAmQ`$B* zGo#rv-UM@Pe7P~kVX@n3Ztnbo9}4?=z0HjmoH)GPa?krupR&4cu{k)UPS|atTCduF znEhj{Q<{AGu{8}!I~lC%7j~5VW$;*G^J&GGCnt0G($jI(IGYg~2Ne`au)73V0E&TH1=N9EhdnRT?HmrG@(I3gUbg4|birR-c z6E#>Cyq%f8lpm3VCq&zmlB{h}zduihGO+VLhGb?bjKB<4;k^k3d)3#~9PhI)- z%dM^R*={4z1|7Z$g}HmQg#`qT=ia$jvwL^?yr;k07*8BxnAnznt0XilOKy88&)L%p z0~E`i3)m}USI^qCu>I?DTXnU;32Hzuk)_eAE5@b@k1KtL`sV+CS-jd|FL% zScQLCI*WM+*5;zqAY+FMD1 z%nFay!h0g>vfI7nALVCgK6yS>YwCZdpFa*B+pizxFjuo`-uHD!t(&7I&(A$r-&K43 z(Ba9KFLUd~?$Y}q*Li*2)P0{;?Ke<%EM(eLxL`ToQe$(it-GU_UAEbk@PA6Zrut6f zMXWCC>?ZwraqYd@>&2aC*Y3{R6&tm5Q^vuvOi}d(oXsq+UUiOfBB zStl}`>P`EaWw~|#43G0KFYlc5Tt13T;nXy~`uKGhHeM{&zo@;Zn(4y)X|Jzqr=8^! z_>{Kn2E)X4XYLgG{Ph!!wpB3?t&{hg%5*_qpEWOK$;Cr?sftz(OTHHu>F(|h|9oUk zz?YZ24!Z9TZ>j(9`EdVQ_nd|}*|Q=iYj?g>R6Ja5F3ymZ_~>(|usyG|p70;JsV^AT z*2G1LuXt3?(~)w{V%ch2hQDs-`R(VFzD%E$uOlM8fOq}LA1@Sd+623OWmxdLYTtjm z>@O0non;?|WEc1vr?lDbVQ{Tkv0x43i~WD+J$X|6mwnc!=-IdB7szryoubPic|3-l zZ%$Rsiq8G!aXAgw{3e^RePr5_w`9$Ll?XLB{|C`axw&d!oE3fAK{9OKV=F6Oe4Q=0-7jOGjz_R7IeeT{(>yz1Kj|p3u z=Y>>w_VLvDTuYO_-^C}e^Vg3vlV4ig^-#HPcQYpPe4MPHz|OSRcJ=)q7R5`ipCa^l zvq8GMRkiVSH;wn+@|PM)SJb;p`ug6wbm`QF4^LK}|7o>zR^rrU5{wh&&ivu@**GcI ze}&7-=aw^H=3EbCm^vk7+27yK4;=WReq7_s4!htbmo2NlFALwRu=Z2_+J#kRsG@Rl?=l@byX3R8nDZ-GChY4g^}iKUa>Pz~VoBRH|EbUNKiB{K z%)a{8-GAb$`{aEdT9=rcF6dwUZLP1(jbAc;-e=G0ojJE}%B)%9;?w_qy{>MS!!!Ti zrmjVMPOOgXPAkq0Jm0ByeUqlM#8sxBp)>VghMCX5;T1Ab#Ax5X&ndxeT*Xg~Ue0;3 zF05p_pN`X%JLjE+jMU!ecy|{BmvKi-O-%liRiE%{@$)6^_rm8yJf6G7Z{{AqpI3!` zURu7iY;R9}*Ii477Y8?PGCmiwWKHI(+jmQrThD$|!lIy~!=k{zgck|fDxChcNwk|y zWuiM@dsdzUzUsG@0=v{Q< zQ*(T&km`c#X+M@PeJU;zlRR~EI=6Tw8>7JQ8<+i>C9XeQ^Z3oVlI`NjOTQE?YVV(5 zZ#b(y*3C|_yd;r<16zj)xnI?yu3(+c;8eljB&xvB!XUupz`>x%!Jr6bgSjAf6I6 6) // don't report expected when variance could explain the diff @@ -869,8 +873,11 @@ void FloweePay::setHeaderSyncHeight(int newHeight) void FloweePay::headerSyncComplete() { + if (m_gotHeadersSyncedOnce) + return; m_gotHeadersSyncedOnce = true; emit headerChainHeightChanged(); + m_notifications.headerSyncComplete(); } void FloweePay::newBlockHeightCertainty(P2PNet::NetworkCertainty) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 1b899ca..1184f4a 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -16,56 +16,9 @@ * along with this program. If not, see . */ #include "NotificationManager.h" -#include "FloweePay.h" -#include "PriceDataProvider.h" -#include - -#if QT_DBUS_LIB -#include -#include -#endif #include -constexpr const char *MUTE = "notificationNewblockMute"; - -NotificationManager::NotificationManager(QObject *parent) - : QObject(parent), - m_startupTime(QDateTime::currentDateTimeUtc()) -{ - setCollation(true); - -#if QT_DBUS_LIB - // We use the notification spec (v 1.2) - // https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html - m_newBlockHints["desktop-entry"] = "org.flowee.pay"; - m_newBlockHints["urgency"] = 0; // low - // "sound-file" // filename TODO - m_walletUpdateHints["desktop-entry"] = "org.flowee.pay"; - m_walletUpdateHints["urgency"] = 1; // normal - - // setup slots for DBUS-signals. Essentially remote callbacks from the deskop notifications app - QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", - "NotificationClosed", this, SLOT(notificationClosed(uint,uint))); - QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", - "ActionInvoked", this, SLOT(actionInvoked(uint,QString))); -#endif - - connect (this, SIGNAL(newBlockSeenSignal(int)), this, SLOT(newBlockSeen(int)), Qt::QueuedConnection); - connect (this, SIGNAL(segmentUpdatedSignal()), this, SLOT(walletUpdated()), Qt::QueuedConnection); - - QSettings appConfig; - m_newBlockMuted = appConfig.value(MUTE, false).toBool(); -} - -void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) -{ - emit newBlockSeenSignal(notification.blockHeight); // move to the Qt thread -} - -void NotificationManager::segmentUpdated(const P2PNet::Notification&) -{ - emit segmentUpdatedSignal(); // move to the Qt thread -} +#include bool NotificationManager::newBlockMuted() const { @@ -78,171 +31,11 @@ void NotificationManager::setNewBlockMuted(bool mute) return; m_newBlockMuted = mute; QSettings appConfig; - appConfig.setValue(MUTE, m_newBlockMuted); + appConfig.setValue(KEY_MUTE, m_newBlockMuted); emit newBlockMutedChanged(); } -void NotificationManager::newBlockSeen(int blockHeight) +void NotificationManager::headerSyncComplete() { - if (m_newBlockMuted) - return; - if (m_startupTime.secsTo(QDateTime::currentDateTimeUtc()) < 60) { - // When Flowee Pay first starts it synchronizes with the network - // and we get a 'new block seen' notification almost every time. - // This is annoying and useless, so lets ignore the new blocks - // happening the first minute or so. Which is almost always - // enough time to sync the headers. - return; - } - logCritical() << "new block" << blockHeight; -#if QT_DBUS_LIB - auto iface = remote(); - if (!iface->isValid()) return; - QVariantList args; - args << QVariant("Flowee Pay"); // app-name - args << QVariant(m_blockNotificationId); // replaces-id - args << QString(); // app_icon (not needed since we say which desktop file we are) - args << QString(); // body-text - if (FloweePay::instance()->chain() == P2PNet::MainChain) - args << tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); // summary text - else - args << tr("tBCH (testnet4) block mined: %1").arg(blockHeight); // summary text - QStringList actions; // actions - actions << "mute" << tr("Mute"); - args << actions; - args << m_newBlockHints; - args << -1; // timeout (ms) -1 means to let the server decide - if (!iface->callWithCallback("Notify", args, this, - SLOT(newBlockNotificationShown(uint)))) { - logWarning() << "dbus down, can't show notifications"; - } -#endif + m_headerSyncComplete = true; } - -void NotificationManager::newBlockNotificationShown(uint id) -{ -#if QT_DBUS_LIB - m_blockNotificationId = id; - logDebug() << "new block notification id:" << m_blockNotificationId; -#endif -} - -void NotificationManager::notificationClosed(uint32_t id, uint32_t reason) -{ -#if QT_DBUS_LIB - logDebug() << " something got closed" << id << reason; - if (m_blockNotificationId == id) { - m_blockNotificationId = 0; - } - else if (m_newFundsNotificationId == id) { - m_newFundsNotificationId = 0; - flushCollate(); - } -#endif -} - -void NotificationManager::actionInvoked(uint, const QString &actionKey) -{ - if (actionKey == "mute") - setNewBlockMuted(true); -} - -void NotificationManager::walletUpdated() -{ - const auto data = collatedData(); - if (data.empty()) - return; - - if (m_openingNewFundsNotification) { - // we are currently (async) opening a notification, wait until its actually open. - QTimer::singleShot(50, this, SLOT(walletUpdated())); - return; - } - - int64_t deposited = 0; - int64_t spent = 0; - int txCount = 0; - for (const auto &item : data) { - deposited += item.deposited; - spent += item.spent; - txCount += item.txCount; - } - -#if QT_DBUS_LIB - auto iface = remote(); - if (!iface->isValid()) return; - QVariantList args; - args << QVariant("Flowee Pay"); // app-name - args << QVariant(m_newFundsNotificationId); // replaces-id - args << QString(); // app_icon (not needed since we say which desktop file we are) - args << tr("New Transactions", "dialog-title", txCount); - - const auto gained = deposited - spent; - auto pricesOracle = FloweePay::instance()->prices(); - QString gainedStr; - if (pricesOracle->price()) - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price()); - if (gainedStr.isEmpty()) { - // no price data available (yet). Display crypto units - gainedStr = QString("%1 %2") - .arg(FloweePay::instance()->amountToStringPretty((double) gained), - FloweePay::instance()->unitName()); - } - if (gained > 0) // since we indicate adding, we always want the plus there - gainedStr = QString("+%1").arg(gainedStr); - - // body-text - if (data.size() > 1) { - args << tr("%1 new transactions across %2 wallets found (%3)") - .arg(txCount).arg(data.size()) - .arg(gainedStr); - } - else if (gained < 0 && txCount == 1) { - args << tr("A payment of %1 has been sent") - .arg(gainedStr.mid(1)); - } - else { - args << tr("%1 new transactions found (%2)", "", txCount) - .arg(txCount) - .arg(gainedStr); - } - - args << QStringList(); - args << m_walletUpdateHints; - args << -1; // timeout (ms) -1 means to let the server decide - if (!iface->callWithCallback("Notify", args, this, - SLOT(walletUpdateNotificationShown(uint)))) { - logWarning() << "dbus down, can't show notifications"; - } - if (m_newFundsNotificationId == 0) { - // The assignment of m_newFundsNotificationId is async, as such - // we want to remember we are opening one and not have multiple calls in - // flight at the same time - m_openingNewFundsNotification = true; - } -#endif -} - -void NotificationManager::walletUpdateNotificationShown(uint id) -{ -#if QT_DBUS_LIB - m_newFundsNotificationId = id; - m_openingNewFundsNotification = false; -#endif -} - -#if QT_DBUS_LIB -QDBusInterface *NotificationManager::remote() -{ - if (m_remote == nullptr) { - m_remote = new QDBusInterface("org.freedesktop.Notifications", // service - "/org/freedesktop/Notifications", // path - "org.freedesktop.Notifications", // interface name (duplicate of service) - QDBusConnection::sessionBus(), this); - if (!m_remote->isValid()) - logWarning() << qPrintable(QDBusConnection::sessionBus().lastError().message()); - } - - return m_remote; -} -#endif diff --git a/src/NotificationManager.h b/src/NotificationManager.h index a2ea5f3..1f17552 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -18,13 +18,10 @@ #ifndef NOTIFICATIONMANAGER_H #define NOTIFICATIONMANAGER_H -#include -#include #include -#include +#include -class QDBusMessage; -class QDBusInterface; +class NotificationManagerPrivate; class NotificationManager : public QObject, public NotificationListener { @@ -33,42 +30,26 @@ public: explicit NotificationManager(QObject *parent = nullptr); void notifyNewBlock(const P2PNet::Notification ¬ification) override; - void segmentUpdated(const P2PNet::Notification &) override; + void segmentUpdated(const P2PNet::Notification ¬ification) override; bool newBlockMuted() const; void setNewBlockMuted(bool mute); + // When Flowee Pay first starts it synchronizes with the network + // and we get a notifyNewBlock call almost every time. + // This is annoying and useless, so lets ignore it until we got a sync-complete. + void headerSyncComplete(); + signals: - // the above will happen in the network thread, lets move them to our thread with some slots. - void newBlockSeenSignal(int blockHeight); - void segmentUpdatedSignal(); void newBlockMutedChanged(); -private slots: - // the above will happen in the network thread, lets move them to our thread with some slots. - void newBlockSeen(int blockHeight); - void newBlockNotificationShown(uint id); - void notificationClosed(uint id, uint subId); - void actionInvoked(uint id, const QString &actionKey); - - void walletUpdated(); - void walletUpdateNotificationShown(uint id); - private: - QDBusInterface *remote(); - + bool m_headerSyncComplete = false; bool m_newBlockMuted = false; - bool m_openingNewFundsNotification = false; - QDateTime m_startupTime; -#if QT_DBUS_LIB - uint32_t m_blockNotificationId = 0; - uint32_t m_newFundsNotificationId = 0; - - QVariantMap m_newBlockHints; - QVariantMap m_walletUpdateHints; - QDBusInterface *m_remote = nullptr; -#endif + static constexpr const char *KEY_MUTE = "notificationNewblockMute"; + friend class NotificationManagerPrivate; + NotificationManagerPrivate *d; }; #endif diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp new file mode 100644 index 0000000..634ecb6 --- /dev/null +++ b/src/NotificationManager_android.cpp @@ -0,0 +1,65 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NotificationManager.h" +#include "FloweePay.h" +#include "PriceDataProvider.h" +#include + +#include +#include +#include + +constexpr const char *ClassName = "org/flowee/pay/PayNotifications"; + +NotificationManager::NotificationManager(QObject *parent) + : QObject(parent), + d(nullptr) +{ + setCollation(true); + QSettings appConfig; + m_newBlockMuted = appConfig.value(KEY_MUTE, false).toBool(); +} + +void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) +{ + if (m_newBlockMuted) + return; + if (!m_headerSyncComplete) + return; + + QString message; + if (FloweePay::instance()->chain() == P2PNet::MainChain) + message = tr("Bitcoin Cash block mined. Height: %1").arg(notification.blockHeight); + else + message = tr("tBCH (testnet4) block mined: %1").arg(notification.blockHeight); + + auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { + QJniEnvironment env; + auto jmessage = QJniObject::fromString(message); + jclass notifications = env.findClass(ClassName); + QJniObject::callStaticMethod(notifications, "notifyBlock", + "(Ljava/lang/String;)V", jmessage); + }); + // The java side will handle everything from here, so we just ignore the task. + Q_UNUSED(task); +} + +void NotificationManager::segmentUpdated(const P2PNet::Notification&) +{ + // TODO +} diff --git a/src/NotificationManager_dbus.cpp b/src/NotificationManager_dbus.cpp new file mode 100644 index 0000000..dad90fa --- /dev/null +++ b/src/NotificationManager_dbus.cpp @@ -0,0 +1,230 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NotificationManager.h" +#include "NotificationManager_p_dbus.h" +#include "FloweePay.h" +#include "PriceDataProvider.h" + +#include + +#include +#include +#include +#include + +NotificationManager::NotificationManager(QObject *parent) + : QObject(parent), + d(new NotificationManagerPrivate(this)) +{ + setCollation(true); + QSettings appConfig; + m_newBlockMuted = appConfig.value(KEY_MUTE, false).toBool(); +} + +void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) +{ + d->newBlockSeen_fromNetwork(notification.blockHeight); +} + +void NotificationManager::segmentUpdated(const P2PNet::Notification&) +{ + d->segmentUpdated_fromNetwork(); +} + + +// ///////////////////////////////// + +NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) + : QObject(qq), + q(qq) +{ + // We use the notification spec (v 1.2) + // https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html + m_newBlockHints["desktop-entry"] = "org.flowee.pay"; + m_newBlockHints["urgency"] = 0; // low + // "sound-file" // filename TODO + m_walletUpdateHints["desktop-entry"] = "org.flowee.pay"; + m_walletUpdateHints["urgency"] = 1; // normal + + // setup slots for DBUS-signals. Essentially remote callbacks from the deskop notifications app + QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", + "NotificationClosed", this, SLOT(notificationClosed(uint,uint))); + QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", + "ActionInvoked", this, SLOT(actionInvoked(uint,QString))); + + connect (this, &NotificationManagerPrivate::newBlockSeenSignal, + this, &NotificationManagerPrivate::newBlockSeen, Qt::QueuedConnection); + connect (this, &NotificationManagerPrivate::segmentUpdatedSignal, + this, &NotificationManagerPrivate::segmentUpdated, Qt::QueuedConnection); +} + +void NotificationManagerPrivate::newBlockSeen_fromNetwork(int height) +{ + emit newBlockSeenSignal(height); +} + +void NotificationManagerPrivate::segmentUpdated_fromNetwork() +{ + emit segmentUpdatedSignal(); +} + +void NotificationManagerPrivate::newBlockSeen(int blockHeight) +{ + assert(QThread::currentThread() == thread()); + if (q->m_newBlockMuted) + return; + if (!q->m_headerSyncComplete) + return; + auto iface = remote(); + if (!iface->isValid()) return; + QVariantList args; + args << QVariant("Flowee Pay"); // app-name + args << QVariant(m_blockNotificationId); // replaces-id + args << QString(); // app_icon (not needed since we say which desktop file we are) + args << QString(); // body-text + if (FloweePay::instance()->chain() == P2PNet::MainChain) + args << tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); // summary text + else + args << tr("tBCH (testnet4) block mined: %1").arg(blockHeight); // summary text + QStringList actions; // actions + actions << "mute" << tr("Mute"); + args << actions; + args << m_newBlockHints; + args << -1; // timeout (ms) -1 means to let the server decide + if (!iface->callWithCallback("Notify", args, this, + SLOT(newBlockNotificationShown(uint)))) { + logWarning() << "dbus down, can't show notifications"; + } +} + +void NotificationManagerPrivate::newBlockNotificationShown(uint id) +{ + m_blockNotificationId = id; +} + +void NotificationManagerPrivate::segmentUpdated() +{ + const auto data = q->collatedData(); + if (data.empty()) + return; + + if (m_openingNewFundsNotification) { + // we are currently (async) opening a notification, wait until its actually open. + QTimer::singleShot(50, this, SLOT(segmentUpdated())); + return; + } + + int64_t deposited = 0; + int64_t spent = 0; + int txCount = 0; + for (const auto &item : data) { + deposited += item.deposited; + spent += item.spent; + txCount += item.txCount; + } + + auto iface = remote(); + if (!iface->isValid()) return; + QVariantList args; + args << QVariant("Flowee Pay"); // app-name + args << QVariant(m_newFundsNotificationId); // replaces-id + args << QString(); // app_icon (not needed since we say which desktop file we are) + args << tr("New Transactions", "dialog-title", txCount); + + const auto gained = deposited - spent; + auto pricesOracle = FloweePay::instance()->prices(); + QString gainedStr; + if (pricesOracle->price()) + gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price()); + if (gainedStr.isEmpty()) { + // no price data available (yet). Display crypto units + gainedStr = QString("%1 %2") + .arg(FloweePay::instance()->amountToStringPretty((double) gained), + FloweePay::instance()->unitName()); + } + if (gained > 0) // since we indicate adding, we always want the plus there + gainedStr = QString("+%1").arg(gainedStr); + + // body-text + if (data.size() > 1) { + args << tr("%1 new transactions across %2 wallets found (%3)") + .arg(txCount).arg(data.size()) + .arg(gainedStr); + } + else if (gained < 0 && txCount == 1) { + args << tr("A payment of %1 has been sent") + .arg(gainedStr.mid(1)); + } + else { + args << tr("%1 new transactions found (%2)", "", txCount) + .arg(txCount) + .arg(gainedStr); + } + + args << QStringList(); + args << m_walletUpdateHints; + args << -1; // timeout (ms) -1 means to let the server decide + if (!iface->callWithCallback("Notify", args, this, + SLOT(walletUpdateNotificationShown(uint)))) { + logWarning() << "dbus down, can't show notifications"; + } + if (m_newFundsNotificationId == 0) { + // The assignment of m_newFundsNotificationId is async, as such + // we want to remember we are opening one and not have multiple calls in + // flight at the same time + m_openingNewFundsNotification = true; + } +} + +QDBusInterface *NotificationManagerPrivate::remote() +{ + assert(QThread::currentThread() == thread()); + if (m_remote == nullptr) { + m_remote = new QDBusInterface("org.freedesktop.Notifications", // service + "/org/freedesktop/Notifications", // path + "org.freedesktop.Notifications", // interface name (duplicate of service) + QDBusConnection::sessionBus(), this); + if (!m_remote->isValid()) + logWarning() << qPrintable(QDBusConnection::sessionBus().lastError().message()); + } + + return m_remote; +} + +void NotificationManagerPrivate::walletUpdateNotificationShown(uint id) +{ + m_newFundsNotificationId = id; + m_openingNewFundsNotification = false; +} + +void NotificationManagerPrivate::actionInvoked(uint, const QString &actionKey) +{ + if (actionKey == "mute") + q->setNewBlockMuted(true); +} + +void NotificationManagerPrivate::notificationClosed(uint32_t id, uint32_t reason) +{ + if (m_blockNotificationId == id) { + m_blockNotificationId = 0; + } + else if (m_newFundsNotificationId == id) { + m_newFundsNotificationId = 0; + q->flushCollate(); + } +} diff --git a/src/NotificationManager_dummy.cpp b/src/NotificationManager_dummy.cpp new file mode 100644 index 0000000..dc62bad --- /dev/null +++ b/src/NotificationManager_dummy.cpp @@ -0,0 +1,36 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NotificationManager.h" + +#include + +NotificationManager::NotificationManager(QObject *parent) + : QObject(parent), + d(nullptr) +{ + QSettings appConfig; + m_newBlockMuted = appConfig.value(KEY_MUTE, false).toBool(); +} + +void NotificationManager::notifyNewBlock(const P2PNet::Notification&) +{ +} + +void NotificationManager::segmentUpdated(const P2PNet::Notification&) +{ +} diff --git a/src/NotificationManager_p_dbus.h b/src/NotificationManager_p_dbus.h new file mode 100644 index 0000000..0de3117 --- /dev/null +++ b/src/NotificationManager_p_dbus.h @@ -0,0 +1,70 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NOTIFICATIONMANAGER_P_DBUS_H +#define NOTIFICATIONMANAGER_P_DBUS_H + +#include +#include +#include + +class QDBusMessage; +class QDBusInterface; + +class NotificationManager; + +class NotificationManagerPrivate : public QObject +{ + Q_OBJECT +public: + explicit NotificationManagerPrivate(NotificationManager *qq); + + void newBlockSeen_fromNetwork(int height); + void segmentUpdated_fromNetwork(); + +signals: + // emitted by notifyNewBlock_fromNetwork, in order to move the call to the Qt thread. + void newBlockSeenSignal(int blockHeight); + void segmentUpdatedSignal(); + +private slots: + // newBlockSeen on the local thread. + void newBlockSeen(int blockHeight); + // the wallet has updated. + void segmentUpdated(); + + // callbacks from dbus + void walletUpdateNotificationShown(uint id); + void actionInvoked(uint id, const QString &actionKey); + void notificationClosed(uint id, uint subId); + void newBlockNotificationShown(uint id); + +private: + QDBusInterface *remote(); + + QDBusInterface *m_remote = nullptr; + bool m_openingNewFundsNotification = false; + uint32_t m_blockNotificationId = 0; + uint32_t m_newFundsNotificationId = 0; + + QVariantMap m_newBlockHints; + QVariantMap m_walletUpdateHints; + + NotificationManager * const q; +}; + +#endif diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 837330d..4470886 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -64,6 +64,11 @@ void setupCallbacks(UserIntent *pi) }; QJniEnvironment env; env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 2); + + // setup the notification channels and data + jclass notifications = env.findClass("org/flowee/pay/PayNotifications"); + QJniObject::callStaticMethod(notifications, "setup", + "(Landroid/content/Context;)V", QNativeInterface::QAndroidApplication::context()); } CommandLineParserData* createCLD(QGuiApplication &app) -- 2.54.0 From 6e6c909be5a5eb4b37d8998c9e2d58a98f41240a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Dec 2024 11:48:07 +0100 Subject: [PATCH 1068/1428] Add Christmas-egg The easter-egg for Christmas. --- guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 5 +++++ guis/mobile/TransactionListItem.qml | 14 +++++++++++++- guis/mobile/images/hat.svg | 9 +++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 guis/mobile/images/hat.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index e0cc26f..a55a076 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -41,6 +41,7 @@ mobile/images/module-mainmenu.svg mobile/images/module-mainmenu-light.svg mobile/images/filter-light.svg + mobile/images/hat.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index bcfbe53..a669444 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -186,6 +186,11 @@ ListView { } MouseArea { anchors.fill: parent } // eat all taps } + property bool itIsChristmas: { + var today = new Date(); + return today.getMonth() == 11 && + (today.getDate() == 24 || today.getDate() == 25 || today.getDate() == 26) + } delegate: Item { id: transactionDelegate property var placementInGroup: model.placementInGroup diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 9ef12e9..2be4b90 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -35,10 +35,12 @@ Item { // icon Image { + id: mainIcon + property bool receiving: model.fundsIn === 0; source: { if (model.isFused) return "qrc:/cf.svg"; - if (model.fundsIn === 0) + if (receiving) var base = "receiving"; else if (isMoved) base = "move"; @@ -52,6 +54,16 @@ Item { smooth: true anchors.verticalCenter: ruler.verticalCenter opacity: isRejected ? 0.6 : 1 + + } + Image { + visible: itIsChristmas + source: "qrc:/hat.svg" + width: 60 + height: 60 + rotation: mainIcon.receiving ? 45 : 0 + x: mainIcon.receiving ? 14 : -2 + y: -2 } Flowee.Label { diff --git a/guis/mobile/images/hat.svg b/guis/mobile/images/hat.svg new file mode 100644 index 0000000..3fd4bc0 --- /dev/null +++ b/guis/mobile/images/hat.svg @@ -0,0 +1,9 @@ + + + + + + + + + -- 2.54.0 From 581db51e718da17074dd83e0c08204a9821713aa Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Dec 2024 14:00:35 +0100 Subject: [PATCH 1069/1428] Import translations from crowdIn --- CMakeLists.txt | 1 + translations/floweepay-common_en.ts | 81 +- translations/floweepay-common_ha.ts | 173 ++-- translations/floweepay-desktop_en.ts | 456 +++++----- translations/floweepay-desktop_es.ts | 12 +- translations/floweepay-desktop_ha.ts | 946 +++++++++++--------- translations/floweepay-mobile_en.ts | 239 ++--- translations/floweepay-mobile_es.ts | 4 +- translations/floweepay-mobile_ha.ts | 531 ++++++----- translations/mobile-i18n.qrc | 1 + translations/module-build-transaction_ha.ts | 23 +- translations/module-peers-view_en.ts | 21 +- translations/module-peers-view_ha.ts | 97 +- translations/module-send-sweep_en.ts | 130 +-- translations/module-send-sweep_ha.ts | 105 +++ 15 files changed, 1619 insertions(+), 1201 deletions(-) create mode 100644 translations/module-send-sweep_ha.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index a5c1a86..498b600 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ if(NOT ANDROID) translations/module-send-sweep_es.ts translations/module-send-sweep_de.ts translations/module-send-sweep_pl.ts + translations/module-send-sweep_ha.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index ab31ac7..ec75f61 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Wallet: Up to date - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Behind: %1 day @@ -31,17 +31,17 @@ - + Up to date Up to date - + Updating Updating - + Still %1 hours behind Still an hour behind @@ -99,27 +99,27 @@ Transaction rejected by network - + Payment has been sent to: Payment has been sent to: - + Copied address to clipboard Copied address to clipboard - + Opening Website Opening Website - + Add a personal note Add a personal note - + Close Close @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -279,32 +279,37 @@ Payment - + Invalid PIN Invalid PIN - + + Wallet is locked + Wallet is locked + + + Not enough funds selected for fees Not enough funds selected for fees - + Not enough funds in wallet to make payment! Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -312,13 +317,13 @@ PaymentProtocolBip70 - + Payment request unreadable Payment request unreadable - - + + Payment request expired Payment request expired @@ -331,17 +336,17 @@ Paste - + Failed Failed - + Instant Pay limit is %1 Instant Pay limit is %1 - + Selected wallet: '%1' Selected wallet: '%1' @@ -354,6 +359,14 @@ Copied to clipboard + + TextField + + + Paste + Paste + + TextPasteDecorator @@ -421,7 +434,7 @@ - + Change #%1 Change #%1 diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts index b5e10a2..02d76e4 100644 --- a/translations/floweepay-common_ha.ts +++ b/translations/floweepay-common_ha.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Baya kan na'ura - + Wallet: Up to date Wallet: Na zamani - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Bayan: %1 kwanaki @@ -31,17 +31,17 @@ - + Up to date Na zamani - + Updating Sabontawa - + Still %1 hours behind Har yanzu %1 a baya @@ -99,27 +99,27 @@ hanyar sadarwa ta ƙi ciniki - + Payment has been sent to: An aika biyan kuɗi zuwa: - + Copied address to clipboard Adireshin da aka kwafi zuwa allo - + Opening Website Buɗe Yanar Gizon - + Add a personal note Ƙara bayanin kula na sirri - + Close Kulle @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity An haɗa tsabar kuɗi don ƙara ɓoye suna + + FiatTxInfo + + + Value now + Daraja yanzu + + + + Value then + Daraja zuwa ga + + FloweePay - + Initial Wallet Asusu na farko - - + + Today Yau - - + + Yesterday Jiya - + Now timestamp Yanzu - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp ½ awa da ya wuce - + %1 hours ago timestamp @@ -266,32 +279,37 @@ Payment - + Invalid PIN Makulli Mara inganci - + + Wallet is locked + Wallet is locked + + + Not enough funds selected for fees Babu isassun kuɗin da aka zaɓa don kuɗi - + Not enough funds in wallet to make payment! Babu isassun kuɗi a cikin asusu don biyan kuɗi! - + Transaction too large. Amount selected needs too many coins. Ma'amala yayi girma sosai. Adadin da aka zaɓa yana buƙatar tsabar kuɗi da yawa. - + Request received over insecure channel. Anyone could have altered it! An karɓi buƙatar akan tashar da ba ta da tsaro. Kowa zai iya canza shi! - + Download of payment request failed. Sauke buƙatar biyan kuɗi ya gagara. @@ -299,17 +317,40 @@ PaymentProtocolBip70 - + Payment request unreadable Ba za a iya karanta buƙatar biyan kuɗi ba - - + + Payment request expired Neman biyan kuɗi ya ƙare + + QRScanner + + + Paste + Wallafa + + + + Failed + Ba a yi nasara ba + + + + Instant Pay limit is %1 + Iyakar Biyan Nan take shine %1 + + + + Selected wallet: '%1' + Asusun da aka zaɓa: '%1' + + QRWidget @@ -318,17 +359,33 @@ An kwafo zuwa allo + + TextField + + + Paste + Wallafa + + + + TextPasteDecorator + + + Paste + Wallafa + + Wallet - - + + Change #%1 Chanji #%1 - - + + Main #%1 Mafarin #%1 @@ -336,12 +393,12 @@ WalletCoinsModel - + Unconfirmed Ba'a tabbatar ba - + %1 hours age, like: hours old @@ -350,7 +407,7 @@ - + %1 days age, like: days old @@ -359,7 +416,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +425,7 @@ - + %1 months age, like: months old @@ -377,7 +434,7 @@ - + Change #%1 Chanji #%1 @@ -385,27 +442,27 @@ WalletHistoryModel - + Today Yau - + Yesterday Jiya - + Earlier this week A farkon wannan makon - + This week Wannan Makon - + Earlier this month A farkon wannan watan @@ -413,12 +470,12 @@ WalletSecretsView - + Explanation Bayani - + Coins a / b a) active coin-count. b) historical coin-count. @@ -427,23 +484,33 @@ a) ƙidaya tsabar kudi b) tsabar kudi na tarihi. - - + + Copy Address Kwafi Adireshi - + + QR of Address + QR of Address + + + Copy Private Key Kwafi Keɓaɓɓen Maɓalli - + + QR of Private Key + QR of Private Key + + + Coins: %1 / %2 Tsabar kudi: %1/%2 - + Signed with Schnorr signatures in the past Sa hannu tare da sa hannun Schnorr a baya diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 2793fc4..5916108 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -84,72 +84,72 @@ Invalid PIN - + Include balance in total Include balance in total - + Hide in private mode Hide in private mode - + Address List Address List - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Backup details Backup details - + Seed-phrase Seed-phrase - + Seed format Seed format - + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. @@ -170,42 +170,42 @@ IP Addresses - + Total found Total found - + Tried Tried - + Punished count Punished count - + Banned count Banned count - + IP-v4 count IP-v4 count - + IP-v6 count IP-v6 count - + Pardon the Banned Pardon the Banned - + Close Close @@ -221,38 +221,38 @@ - + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - + Peer for wallet: %1 Peer for wallet: %1 - + Disconnect Peer Disconnect Peer - + Ban Peer Ban Peer - + Close Close @@ -269,11 +269,6 @@ Name Name - - - Go - Go - Force Single Address @@ -286,13 +281,18 @@ This ensures only one private key will need to be backed up When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up + + + Go + Go + NewAccountCreateHDAccount - Creates a new wallet with smart creation of addresses from a single seed-phrase - Creates a new wallet with smart creation of addresses from a single seed-phrase + Create a new wallet with smart creation of addresses from a single seed-phrase + Create a new wallet with smart creation of addresses from a single seed-phrase @@ -317,12 +317,6 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - - - - Name - Name - Select import method @@ -367,59 +361,60 @@ Change will come back to the imported key. Change will come back to the imported key. - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + + New Wallet Name + New Wallet Name + + + + Start Start - + Password Password - - Optional - Optional + + imported wallet password + imported wallet password - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Lookup Details + Discover Details - - Nothing found for seed - Nothing found for seed - - - + Derivation Derivation + + + Nothing found for seed + Nothing found for seed + NewAccountPane @@ -428,11 +423,39 @@ Change will come back to the imported key. New HD wallet New HD wallet + + + Seed-phrase based + Context: wallet type + Seed-phrase based + + + + Easy to backup + Context: wallet type + Easy to backup + + + + Most compatible + The most compatible wallet type + Most compatible + Import Existing Wallet Import Existing Wallet + + + Imports seed-phrase + Imports seed-phrase + + + + Imports private key + Imports private key + New Basic Wallet @@ -456,52 +479,34 @@ Change will come back to the imported key. Context: wallet type Great for brief usage - - - Seed-phrase based - Context: wallet type - Seed-phrase based - - - - Easy to backup - Context: wallet type - Easy to backup - - - - Most compatible - The most compatible wallet type - Most compatible - - - - Imports seed-phrase - Imports seed-phrase - - - - Imports private key - Imports private key - PaymentTweakingPanel - + Add Detail Add Detail - + Coin Selector Coin Selector - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. + + + Comment + Comment + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + This allows adding a public comment, that will be included in the transaction and seen by everyone. + ReceiveTransactionPane @@ -521,52 +526,52 @@ Change will come back to the imported key. Import Running... - + Checking Checking - - Transaction high risk - Transaction high risk + + High risk transaction + High risk transaction - + Payment Seen Payment Seen - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - + Instant payment failed. Wait for confirmation. (double spent proof received) Instant payment failed. Wait for confirmation. (double spent proof received) - + Description Description - + Amount Amount - + Clear Clear - + Done Done @@ -574,147 +579,147 @@ Change will come back to the imported key. SendTransactionPane - + Confirm delete Confirm delete - + Do you really want to delete this detail? Do you really want to delete this detail? - + Add Destination Add Destination - + Prepare Prepare - + Enter your PIN Enter your PIN - - + + Warning Warning - + Payment request warnings: Payment request warnings: - + Transaction Details Transaction Details - + Not prepared yet Not prepared yet - + Copy transaction-ID Copy transaction-ID - + Fee Fee - + Transaction size Transaction size - + %1 bytes %1 bytes - + Fee per byte Fee per byte - + %1 sat/byte fee %1 sat/byte - + Send Send - + Destination Destination - + Max available The maximum balance available Max available - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Enter Bitcoin Cash Address - - + + Copy Address Copy Address - + Amount Amount - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + Cancel Cancel - + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -723,56 +728,81 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin + + + Public-comment + Public-comment + + + + Add a comment you want to include in the transaction, visible for everyone. + Add a comment you want to include in the transaction, visible for everyone. + + + + Custom message, to be included in the transaction. + Custom message, to be included in the transaction. + + + + Text + Text + + + + Size + Size + SettingsPane @@ -888,27 +918,27 @@ Change will come back to the imported key. Transaction Details - + First Seen First Seen - + Rejected Rejected - + Unconfirmed Unconfirmed - + Mined at Mined at - + %1 blocks ago %1 blocks ago @@ -916,67 +946,67 @@ Change will come back to the imported key. - + Comment Comment - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Size - + %1 bytes %1 bytes - + Is Coinbase Is Coinbase - + Copy transaction-ID Copy transaction-ID - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses @@ -1036,119 +1066,119 @@ Change will come back to the imported key. Protect your wallet with a password - + Pin to Pay Pin to Pay - + Protect your funds pin to pay Protect your funds - + Fully open, except for sending funds pin to pay Fully open, except for sending funds - + Keeps in sync pin to pay Keeps in sync - + Pin to Open Pin to Open - + Protect your entire wallet pin to open Protect your entire wallet - + Balance and history protected pin to open Balance and history protected - + Requires Pin to view, sync or pay pin to open Requires Pin to view, sync or pay - + Make "%1" wallet require a pin to pay Make "%1" wallet require a pin to pay - + Make "%1" wallet require a pin to open Make "%1" wallet require a pin to open - - + + Wallet already has pin to open applied Wallet already has pin to open applied - + Wallet already has pin to pay applied Wallet already has pin to pay applied - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. - + Password Password - + Wallet Wallet - + Encrypt Encrypt - + Invalid password to open this wallet Invalid password to open this wallet - + Close Close - + Repeat password Repeat password - + Please confirm the password by entering it again Please confirm the password by entering it again - + Typed passwords do not match Typed passwords do not match @@ -1156,17 +1186,17 @@ Change will come back to the imported key. WalletEncryptionStatus - + Pin to Open Pin to Open - + Pin to Pay Pin to Pay - + (Opened) Wallet is decrypted (Opened) @@ -1175,95 +1205,95 @@ Change will come back to the imported key. main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + Offline Offline - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1272,12 +1302,12 @@ Change will come back to the imported key. - + Preparing... Preparing... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index 1a52278..7ed9197 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -397,7 +397,7 @@ El cambio volverá a la clave importada. imported wallet password - imported wallet password + Contraseña del monedero importada @@ -505,7 +505,7 @@ El cambio volverá a la clave importada. This allows adding a public comment, that will be included in the transaction and seen by everyone. - This allows adding a public comment, that will be included in the transaction and seen by everyone. + Esto permite añadir un comentario público que será incluido en la transacción y visto por todos. @@ -781,22 +781,22 @@ El cambio volverá a la clave importada. Public-comment - Public-comment + Comentario-público Add a comment you want to include in the transaction, visible for everyone. - Add a comment you want to include in the transaction, visible for everyone. + Agrega un comentario que quieras incluir en la transacción, visible para todos. Custom message, to be included in the transaction. - Custom message, to be included in the transaction. + Mensaje personalizado, a ser incluido en la transacción. Text - Text + Texto diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts index a09a641..bee17f7 100644 --- a/translations/floweepay-desktop_ha.ts +++ b/translations/floweepay-desktop_ha.ts @@ -19,28 +19,28 @@ Ma'ajiyin taskoki - + Make Primary Haɗin farko - + ★ Primary Mafari - + Protect With Pin... Tsarewa da ɗan makulli... - + Open Open encrypted wallet Budewa - + Close Close encrypted wallet Kullewa @@ -64,92 +64,92 @@ Daidaita matsayi - + Encryption Rufewa - + Password Kwadon shiga - + Open Bude - + Invalid PIN Makulli Mara inganci - + Include balance in total Haɗa ma'aunin chanji baki ɗaya - + Hide in private mode Ɓoye a yanayin sirri - + Address List Jerin adireshi - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Backup details Ajiyayyen bayanai - + Seed-phrase Jimlar iri - + Seed format Tsarin iri - + Derivation Samo asali - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Wannan asusun na tsare da almar sirri (pin-to-pay). Don ganin bayanan ciki akwai buƙatar samar da kalmar wucewa. @@ -162,10 +162,58 @@ Ciniki na ƙarshe + + AddressDbStats + + + IP Addresses + IP Addresses + + + + Total found + Total found + + + + Tried + Tried + + + + Punished count + Punished count + + + + Banned count + Banned count + + + + IP-v4 count + IP-v4 count + + + + IP-v6 count + IP-v6 count + + + + Pardon the Banned + Pardon the Banned + + + + Close + Kulle + + NetView - + Peers (%1) Takwarori (%1) @@ -173,48 +221,38 @@ - + Address network address (IP) Adireshi - + Start-height: %1 Fara-tsawo: %1 - + ban-score: %1 Tsame-ci: %1 - - initializing connection - Farawa haɗi - - - - Verifying peer - Tabbatar da tsari - - - - Assigning... - Sanyawa... - - - + Peer for wallet: %1 Haɗin asusu %1 - - Peer for initial wallet - Haɗi na asusun farko + + Disconnect Peer + Disconnect Peer - + + Ban Peer + Ban Peer + + + Close Kulle @@ -222,61 +260,56 @@ NewAccountCreateBasicAccount - - This creates a new empty wallet with simple multi-address capability - Wannan yana haifar da sabon asusu mara komai tare da sauƙin adireshi da yawa + + Create a new empty wallet with simple multi-address capability + Wannan yana haifar da sabon asusu mara komai tare da sauƙin adireshi da yawa - + Name Suna - - Go - Tafi - - - - Advanced Options - Zaɓuɓɓuka na ci gaba - - - + Force Single Address Tilasta Adireshi Guda Daya - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Lokacin da aka kunna, wannan asusun za'a iyakance shi ga adireshi ɗaya. Wannan yana tabbatar da maɓallin keɓaɓɓen maɓalli ɗaya ne kawai zai buƙaci a yi masa tallafi + + + Go + Tafi + NewAccountCreateHDAccount - - This creates a new empty wallet with smart creation of addresses from a single seed-phrase + + Create a new wallet with smart creation of addresses from a single seed-phrase Wannan yana haifar da sabon asusu mara komai tare da ƙirƙirar adireshi masu wayo daga jimla iri ɗaya - + Name Suna - + Go Tafi - + Advanced Options Zaɓuɓɓuka na ci gaba - + Derivation Samo asali @@ -284,205 +317,194 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Ataimaka a shigar da sirrin asusu don shigo da shi. Wannan na iya zama jimla-jinin ko maɓalli na sirri. - - - - Secret - The seed-phrase or private key - Sirri - - - - Example: %1 - placeholder text - Misali: %1 - - - - Name - Suna - - - - Private key - description of type - Keɓaɓɓen maɓalli - - - - BIP 39 seed-phrase (interpreted as Electrum format) - description of type - BIP 39 zuriyar jimla (an fassara shi azaman Electrum) - - - - BIP 39 seed-phrase - description of type - BIP 39 zuriyar jimla + + Select import method + Select import method - Electrum seed-phrase - description of type - Zuriyar jimlar Electrum + Camera + Camera - - Unrecognized word - Word from the seed-phrases lexicon - Kalmar da ba'a gane ba + + OR + Ko - - Import wallet - Ɗauko asusu + + Secret as text + The seed-phrase or private key + Secret as text - - Advanced Options - Zaɓuɓɓuka na ci gaba + + Unknown word(s) found + Unknown word(s) found - + + Address to import + Address to import + + + Force Single Address Tilasta Adireshi Guda - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - Old Electrum Phrase - Tsohon Zuriyar jimlar Electrum + + + Oldest Transaction + Tsohon ciniki - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Lokacin gano Electrum ya gaza, kuma kun tabbata an ƙirƙira shi a cikin wallet ɗin, kunna wannan zaɓi. + + Check Age + online check for wallet age + Check Age - - Start Height - Fara tsawo + + Nothing found for wallet + Nothing found for wallet - + + + New Wallet Name + New Wallet Name + + + + + Start + Start + + + + Password + Kwadon shiga + + + + imported wallet password + imported wallet password + + + + Discover Details + online check for wallet details + Discover Details + + + Derivation Samo asali - - Alternate phrase - Madadin magana + + Nothing found for seed + Nothing found for seed NewAccountPane - - New Bitcoin Cash Wallet - Sabon asusun Bitcoin Cash + + New HD wallet + New HD wallet - - Basic - Na asali - - - - Private keys based - Property of a wallet - Maɓallai masu zaman kansu - - - - Difficult to backup - Context: wallet type - Wahalar dawo da ajiya - - - - Great for brief usage - Context: wallet type - Mai girma don taƙaitaccen amfani - - - - HD wallet - Asusun HD - - - + Seed-phrase based Context: wallet type Tushen jimlar iri - + Easy to backup Context: wallet type Sauƙi wurin dawo da ajiya - + Most compatible The most compatible wallet type Mafi dacewa - - Import - Shigo da + + Import Existing Wallet + Shigo da asusun da ke akwai - + Imports seed-phrase Shigo da jumla iri-iri - + Imports private key Shigo da maɓalli na sirri - - Basic wallet with private keys - Asusu na asali tare da maɓallan sirri + + New Basic Wallet + New Basic Wallet - - Seed-phrase wallet - Tushen jimlar iri + + Private keys based + Property of a wallet + Maɓallai masu zaman kansu - - Import of an existing wallet - Shigo da asusun da ke akwai + + Difficult to backup + Context: wallet type + Wahalar dawo da ajiya + + + + Great for brief usage + Context: wallet type + Mai girma don taƙaitaccen amfani PaymentTweakingPanel - + Add Detail Ƙara Dalla-dalla - + Coin Selector Zaɓin Tsabar kudi - + To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. Domin soke tsoffin zaɓin tsabar kudi waɗanda ake amfani da su don yin mu'amala, zaku iya ƙara 'tsabar kudi r' inda tsabar kudi inda za'a iya gani. + + + Comment + Comment + + + + This allows adding a public comment, that will be included in the transaction and seen by everyone. + This allows adding a public comment, that will be included in the transaction and seen by everyone. + ReceiveTransactionPane @@ -502,52 +524,52 @@ Change will come back to the imported key. Tsarin Shiga na gudu... - + Checking Dubawa - - Transaction high risk - Haɗarin cinikayya + + High risk transaction + High risk transaction - + Payment Seen Anga shaidar biya - + Payment Accepted An Karɓa Biya - + Payment Settled Biya An daidaita - + Instant payment failed. Wait for confirmation. (double spent proof received) Biyan nan take ya kasa. Jira tabbaci. (Anga shaida guda biyu da aka kashe) - + Description Bayani - + Amount Adadi - + Clear Share - + Done An gama @@ -555,146 +577,147 @@ Change will come back to the imported key. SendTransactionPane - + Confirm delete Tabbatar da gogewa - + Do you really want to delete this detail? Shin kuna son share wannan dalla-dalla? - + Add Destination Ƙara madakata - + Prepare Shirya - + Enter your PIN Shigar da lambar tsaro - - + + Warning Gargaɗi - + Payment request warnings: Gargadin neman biyan kuɗi: - + Transaction Details Cikakken Bayanin Kasuwanci - + Not prepared yet Ba'a shirya ba tukuna - + Copy transaction-ID Kwafi shaidar ma'amala-ID - + Fee Kudin - + Transaction size Girman ciniki - + %1 bytes %1 bytes - + Fee per byte Kudin kowane byte - + %1 sat/byte fee %1 sat/bytes - + Send Aika - + Destination Madakata - + Max available The maximum balance available Mafi girman samuwa - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Shigar da Adireshin Kuɗi na Bitcoin - + + Copy Address Kwafi Adireshi - + Amount Adadi - + Max Matsakaici - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + Continue Ci gaba - + Cancel Soke - + Coin Selector Zaɓin Tsabar kudi - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -703,56 +726,81 @@ Change will come back to the imported key. - + Total Number of coins Jimilla - + Needed Ake Bukata - + Selected An zaɓa - + Value Daraja - + Locked coins will never be used for payments. Right-click for menu. Kullallun tsabar kudi ba za a taɓa amfani da su don biyan kuɗi ba. Danna-dama don menu. - + Age Shekaru - + Unselect All Cire Zaɓi Duk - + Select All Zaɓi Duk - + Unlock coin Buɗe tsabar kudi - + Lock coin Kulle tsabar kudi + + + Public-comment + Public-comment + + + + Add a comment you want to include in the transaction, visible for everyone. + Add a comment you want to include in the transaction, visible for everyone. + + + + Custom message, to be included in the transaction. + Custom message, to be included in the transaction. + + + + Text + Text + + + + Size + Size + SettingsPane @@ -762,60 +810,251 @@ Change will come back to the imported key. Saituna - + Unit Naúrar - + Show Bitcoin Cash value on Activity page Nuna ƙimar Bitcoin Cash akan shafin Aiki - + Show Block Notifications Nuna Sanarwa da ta toshe - + When a new block is mined, Flowee Pay shows a desktop notification Lokacin da aka haƙa sabon toshe, Flowee Pay yana nuna sanarwar tebur - + Night Mode Yanayin dare - + Private Mode Yanayin sirri - + Hides private wallets while enabled Ɓoye sirrin asusu yayin kunnawa - + + Font sizing + Girman haruffa + + + Version Siga - + Library Version Sigar Laburare - + Synchronization Haɗa Aiki tare - + Network Status Matsayin hanyar sadarwa + + + Address Stats + Address Stats + + + + Transaction + + + Miner Reward + Miner Reward + + + + Fused + Fused + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + rejected + rejected + + + + TransactionDetails + + + Transaction Details + Cikakken Bayanin Kasuwanci + + + + First Seen + First Seen + + + + Rejected + Rejected + + + + Unconfirmed + Ba'a tabbatar ba + + + + Mined at + An haƙo daga + + + + %1 blocks ago + + %1 blocks ago + %1 blocks ago + + + + + Comment + Comment + + + + Fees paid + An biya kudade + + + + %1 Satoshi / 1000 bytes + %1 Satoshi / 1000 bytes + + + + Size + Size + + + + %1 bytes + %1 bytes + + + + Is Coinbase + Is Coinbase + + + + Copy transaction-ID + Kwafi shaidar ma'amala-ID + + + + Fused from my addresses + An haɗe daga adiresoshin na + + + + Sent from my addresses + An aiko daga adiresoshin na + + + + Sent from addresses + An aiko daga adiresoshi + + + + Fused into my addresses + An haɗe cikin adiresoshin na + + + + Received at addresses + An karɓa a adiresoshi + + + + Received at my addresses + An karɓa a adireshi na + + + + TransactionInfoSmall + + + Transaction is rejected + Transaction is rejected + + + + Processing + Processing + + + + Mined + Mined + + + + %1 blocks ago + Confirmations + + %1 blocks ago + %1 blocks ago + + + + + Fees + Fees + + + + Copy transaction-ID + Kwafi shaidar ma'amala-ID + + + + Holds a token + Holds a token + + + + Opening Website + Buɗe Yanar Gizon + WalletEncryption @@ -825,119 +1064,119 @@ Change will come back to the imported key. Kare asusun ku da kalmar sirri - + Pin to Pay Matsa lamba don biya - + Protect your funds pin to pay Kare kuɗin ku - + Fully open, except for sending funds pin to pay Cikakken buɗewa, Sai dai don aika kudi - + Keeps in sync pin to pay Yana ci gaba da daidaitawa - + Pin to Open Pin don Buɗe - + Protect your entire wallet pin to open Kare dukkan asusun ku - + Balance and history protected pin to open Ma'auni da tarihi a tsare suke - + Requires Pin to view, sync or pay pin to open Da buƙatar Pin don dubawa, daidaitawa ko biya - + Make "%1" wallet require a pin to pay Yi "%1" asusu yana buƙatar Pin don biya - + Make "%1" wallet require a pin to open Yi "%1" asusu yana buƙatar Pin don buɗewa - - + + Wallet already has pin to open applied Asusun tuni yana da Pin don buɗewa - + Wallet already has pin to pay applied Asusun tuni yana da Pin don biya - + Your wallet will get partially encrypted and payments will become impossible without a password. If you don't have a backup of this wallet, make one first. Asusun ku zai sami rufaffen ɓangarori kuma biyan kuɗi zai yi wuya ba tare da kalmar sirri ba. Idan babu ajiyar wannan asusun ɗin, fara yin ɗaya. - + Your full wallet gets encrypted, opening it will need a password. If you don't have a backup of this wallet, make one first. Cikakken asusun ku yana ɓoye, domin buɗe shi za'a buƙaci kalmar sirri. Idan ba ku da ajiyar wannan asusun, fara yin ɗaya. - + Password Kwadon shiga - + Wallet Asusu - + Encrypt Rufewa - + Invalid password to open this wallet Kalmar sirri mara inganci don buɗe wannan asusu - + Close Kulle - + Repeat password Maimaita kalmar sirri - + Please confirm the password by entering it again Da fatan za a tabbatar da kalmar wucewa ta hanyar sake shigar da shi - + Typed passwords do not match Rubutun kalmomin shiga ba su dace ba @@ -945,222 +1184,114 @@ Change will come back to the imported key. WalletEncryptionStatus - - Pin to Pay - Matsa lamba don biya - - - + Pin to Open Pin don Buɗe - + + Pin to Pay + Matsa lamba don biya + + + (Opened) Wallet is decrypted (An buɗe) - - WalletTransaction - - - Miner Reward - Ladan Ma'adinai - - - - Fused - Fuskanci - - - - Received - An samu - - - - Moved - Motsa - - - - Sent - An aika - - - - rejected - an ƙii - - - - unconfirmed - ba'a tabbatar ba - - - - WalletTransactionDetails - - - Copy transaction-ID - Kwafi shaidar ma'amala-ID - - - - Status - Matsayi - - - - rejected - an ƙii - - - - unconfirmed - ba'a tabbatar ba - - - - %1 confirmations (mined in block %2) - - %1 tabbatarwa (ma'adinai a tubali %2) - %1 tabbatarwa (ma'adinai a tubali %2) - - - - - Copy block height - Kwafi tsayin tubali - - - - Fees - Kudin - - - - Size - Girman - - - - %1 bytes - - %1 bytes - %1 bytes - - - - - Inputs - Abubuwan shigarwa - - - - - Copy Address - Kwafi Adireshi - - - - Outputs - Abubuwan da aka fitar - - main - + Activity Ayyuka - + Archived wallets do not check for activities. Balance may be out of date. Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya tsufa. - + Unarchive Cire Takardu - + This wallet needs a password to open. Wannan asusun tana buƙatar kalmar sirri don buɗewa. - + Password: Kalmar wucewa: - + Invalid password Kalmar shiga mara inganci - + Open Bude - + Send Aika - + Receive Karɓa - + Balance Sauran kudi - + Main balance (money), non specified Babban - + Unconfirmed balance (money) Ba'a tabbatar ba - + Immature balance (money) Rashin cika - + 1 BCH is: %1 1 BCH shi ne: %1 - + Network status Matsayin hanyar sadarwa - + Offline Baya kan na'ura - + Add Bitcoin Cash wallet Sanya asusun Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1169,9 +1300,14 @@ Change will come back to the imported key. - + Preparing... Shiryawa... + + + QR-Scan + QR-Scan + diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 8d9c35b..27788b7 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -62,36 +62,6 @@ Receive Receive - - - Miner Reward - Miner Reward - - - - Fused - Fused - - - - Received - Received - - - - Moved - Moved - - - - Sent - Sent - - - - Rejected - Rejected - AccountPageListItem @@ -180,8 +150,8 @@ - Switches between still in use addresses and formerly used, new empty, addresses - Switches between still in use addresses and formerly used, new empty, addresses + Switches between unused and used Bitcoin addresses + Switches between unused and used Bitcoin addresses @@ -204,15 +174,25 @@ Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet + + + Re-scan Chain + Re-scan Chain + + + + Remove Wallet + Remove Wallet + AccountSelectorPopup @@ -240,7 +220,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -309,7 +289,7 @@ Explore - + ON Enabled. SHORT TEXT! ON @@ -361,112 +341,107 @@ Import Wallet - - - Name - Name - - - - Force Single Address - Force Single Address - - - + Select import method Select import method - + Camera Camera - + OR OR - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Next Next - + Address to import Address to import - + + Force Single Address + Force Single Address + + + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - Nothing found for seed - Nothing found for seed - - - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + + New Wallet Name + New Wallet Name + + + + Start Start - + Password Password - - Optional - Optional + + imported wallet password + imported wallet password - - Details - Details - - - - Lookup Details + + Discover Details online check for wallet details - Lookup Details + Discover Details - - Derivation - Derivation + + Derivation Path + Derivation Path + + + + Nothing found for seed + Nothing found for seed @@ -593,6 +568,11 @@ Change will come back to the imported key. New Bitcoin Cash Wallet New Bitcoin Cash Wallet + + + New HD Wallet + New HD Wallet + Seed-phrase based @@ -611,6 +591,26 @@ Change will come back to the imported key. The most compatible wallet type Most compatible + + + Import Existing Wallet + Import Existing Wallet + + + + Imports seed-phrase + Imports seed-phrase + + + + Imports private key + Imports private key + + + + New Basic Wallet + New Basic Wallet + Private keys based @@ -629,31 +629,6 @@ Change will come back to the imported key. Context: wallet type Great for brief usage - - - Import Existing Wallet - Import Existing Wallet - - - - New HD Wallet - New HD Wallet - - - - Imports seed-phrase - Imports seed-phrase - - - - Imports private key - Imports private key - - - - New Basic Wallet - New Basic Wallet - New Wallet @@ -760,7 +735,7 @@ This ensures only one private key will need to be backed up Destination Address - + Unlock Wallet Unlock Wallet @@ -768,25 +743,25 @@ This ensures only one private key will need to be backed up PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 - + 7d 7 days 7d - + 1m 1 month 1m - + 3m 3 months 3m @@ -858,8 +833,8 @@ This ensures only one private key will need to be backed up - Transaction high risk - Transaction high risk + High risk transaction + High risk transaction @@ -968,6 +943,11 @@ This ensures only one private key will need to be backed up Add a different wallet Add a different wallet + + + Claim a Cash Stamp + Claim a Cash Stamp + TransactionDetails @@ -1137,6 +1117,39 @@ This ensures only one private key will need to be backed up Transaction Details + + TransactionListItem + + + Miner Reward + Miner Reward + + + + Fused + Fused + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + Rejected + Rejected + + UnlockWidget diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index ce84464..540f88b 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -186,7 +186,7 @@ Re-scan Chain - Re-scan Chain + Re-escanear la cadena @@ -425,7 +425,7 @@ El cambio volverá a la clave importada. imported wallet password - imported wallet password + Contraseña del monedero importada diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts index d1e409f..b612673 100644 --- a/translations/floweepay-mobile_ha.ts +++ b/translations/floweepay-mobile_ha.ts @@ -26,21 +26,21 @@ - © 2020-2023 Tom Zander and contributors - © 2020-2023 Tom Zander da masu ba da gudummawa + © 2020-2024 Tom Zander and contributors + © 2020-2024 Tom Zander and contributors - + Project Home Gidan Aikin - + With git repository and issues tracker Tare da ma'ajiyar git da mai bin diddigin batutuwa - + Telegram Telegram @@ -62,46 +62,6 @@ Receive Karɓa - - - Miner Reward - Ladan Ma'adinai - - - - Fused - Fuskanci - - - - Received - An samu - - - - Moved - Motsa - - - - Sent - An aika - - - - Sending - Aikawa - - - - Seen - An gani - - - - Rejected - An ƙii - AccountPageListItem @@ -126,102 +86,113 @@ Ajiyayyen bayanai - + Wallet seed-phrase Asusun Jumla iri-iri - + Seed format Tsarin iri - + + Starting Height height refers to block-height Fara tsawo - + Derivation Path Hanyar Fitowa - + xpub Xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + Wallet keys Maɓallan asusu - + Show Index toggle to show numbers Nuna index - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - - Switches between still in use addresses and formerly used, new empty, addresses - Yana canzawa tsakanin adiresoshin da ake amfani da su da waɗanda aka yi amfani da su a baya, sabon fankon, adireshi + + Switches between unused and used Bitcoin addresses + Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Addresses and keys Adireshi da maɓallai - + Sync Status Daidaita matsayi - + Hide balance in overviews Boye ma'auni a cikin bayyani - + Hide in private mode Boye a yanayin sirri - + Unarchive Wallet Ma'ajiyin taskoki - + Archive Wallet Ma'ajiyin taskoki + + + Re-scan Chain + Re-scan Chain + + + + Remove Wallet + Remove Wallet + AccountSelectorPopup @@ -241,7 +212,7 @@ Akwai buƙatar PIN don buɗewa - + Balance Total Jimlar Ma'auni @@ -249,7 +220,7 @@ AccountSyncState - + Status: Offline Matsayi: Akashe @@ -318,7 +289,7 @@ Bincika - + ON Enabled. SHORT TEXT! Kan @@ -342,22 +313,22 @@ Jigo mai duhu - + Unit Na'ura - + Change Currency (%1) Canja Kuɗi (%1) - + Main View Babban Duba - + Show Bitcoin Cash value Nuna darajar Bitcoin Cash @@ -370,91 +341,106 @@ Ɗauko asusu - - Please enter the secrets of the wallet to import. This can be a seed-phrase or a private key. - Ataimaka a shigar da sirrin asusu don shigo da shi. Wannan na iya zama jimla-jinin ko maɓalli na sirri. + + Select import method + Select import method - - Secret + + Camera + Camera + + + + OR + Ko + + + + Secret as text The seed-phrase or private key - Sirri + Secret as text - - Private key - description of type - Keɓaɓɓen maɓalli + + Unknown word(s) found + Unknown word(s) found - - BIP 39 seed-phrase (interpreted as Electrum) - description of type - BIP 39 zuriyar jimla (an fassara shi azaman Electrum) + + Next + Next - - BIP 39 seed-phrase - description of type - BIP 39 zuriyar jimla + + Address to import + Address to import - - Electrum seed-phrase - description of type - Zuriyar jimlar Electrum - - - - Unrecognized word - Word from the seed-phrases lexicon - Kalmar da ba'a gane ba - - - - Name - Suna - - - + Force Single Address Tilasta Adireshi Guda Daya - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - Old Electrum Phrase - Tsohon Zuriyar jimlar Electrum - - - - When Electrum detection fails, and you are sure it was created in that wallet, enable this option. - Lokacin gano Electrum ya gaza, kuma kun tabbata an ƙirƙira shi a cikin wallet ɗin, kunna wannan zaɓi. - - - + + Oldest Transaction Tsohon ciniki - - Derivation - Samo asali + + Check Age + online check for wallet age + Check Age - - Alternate phrase - Madadin magana + + Nothing found for wallet + Nothing found for wallet - - Create - Ƙirƙiri + + + New Wallet Name + New Wallet Name + + + + + Start + Start + + + + Password + Kwadon shiga + + + + imported wallet password + imported wallet password + + + + Discover Details + online check for wallet details + Discover Details + + + + Derivation Path + Hanyar Fitowa + + + + Nothing found for seed + Nothing found for seed @@ -494,21 +480,21 @@ Change will come back to the imported key. - Requests for payment can be approved automatically using Instant Pay. - Ana iya amincewa da buƙatun biyan kuɗi ta atomatik ta amfani da Biyan Nan take. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. + Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. - + Protected wallets can not be used for InstaPay because they require a PIN before usage Ba za a iya amfani da kariyar asusu ɗin don InstaPay ba saboda suna buƙatar PIN kafin amfani - + Enable Instant Pay for this wallet Kunna Biyan Nan take na wannan asusun - + Maximum Amount Makurar Adadi @@ -583,120 +569,110 @@ Change will come back to the imported key. - Create a New Wallet - Ƙirƙiri sabon asusu + New HD Wallet + New HD Wallet - - HD wallet - Asusun HD - - - + Seed-phrase based Context: wallet type Tushen jimlar iri - + Easy to backup Context: wallet type Sauƙi wurin dawo da ajiya - + Most compatible The most compatible wallet type Mafi dacewa - - Basic - Na asali + + Import Existing Wallet + Shigo da asusun da ke akwai - + + Imports seed-phrase + Shigo da jumla iri-iri + + + + Imports private key + Shigo da maɓalli na sirri + + + + New Basic Wallet + New Basic Wallet + + + Private keys based Property of a wallet Maɓallai masu zaman kansu - + Difficult to backup Context: wallet type Wahalar dawo da ajiya - + Great for brief usage Context: wallet type Mai girma don taƙaitaccen amfani - - Import Existing Wallet - Shigo da asusun da ke akwai - - - - Import - Shigo da - - - - Imports seed-phrase - Shigo da jumla iri-iri - - - - Imports private key - Shigo da maɓalli na sirri - - - + New Wallet Sabon Asusu - + This creates a new empty wallet with simple multi-address capability Wannan yana haifar da sabon asusu mara komai tare da sauƙin adireshi da yawa - - + + Name Suna - + Force Single Address Tilasta Adireshi Guda - + When enabled, this wallet will be limited to one address. This ensures only one private key will need to be backed up Lokacin da aka kunna, wannan asusun za'a iyakance shi ga adireshi ɗaya. Wannan yana tabbatar da maɓallin keɓaɓɓen maɓalli ɗaya ne kawai zai buƙaci a yi masa tallafi - - + + Create Ƙirƙiri - + New HD-Wallet Sabon Asusun HD - + This creates a new wallet that can be backed up with a seed-phrase Wannan yana haifar da sabon asusu wanda za'a iya tallafawa ko dawo dashi tare da jimlar iri - + Derivation Samo asali @@ -757,7 +733,7 @@ This ensures only one private key will need to be backed up Adireshin Zuwa - + Unlock Wallet Buɗe Asusu @@ -765,25 +741,25 @@ This ensures only one private key will need to be backed up PriceDetails - + 1 BCH is: %1 Price of a whole bitcoin cash 1 BCH is: %1 - + 7d 7 days 7d - + 1m 1 month 1m - + 3m 3 months 3m @@ -797,29 +773,6 @@ This ensures only one private key will need to be backed up Ireiren kuɗaɗe - - QRScannerOverlay - - - Paste - Wallafa - - - - Failed - Ba a yi nasara ba - - - - Instant Pay limit is %1 - Iyakar Biyan Nan take shine %1 - - - - Selected wallet: '%1' - Asusun da aka zaɓa: '%1' - - ReceiveTab @@ -878,8 +831,8 @@ This ensures only one private key will need to be backed up - Transaction high risk - Haɗarin cinikayya + High risk transaction + High risk transaction @@ -988,6 +941,11 @@ This ensures only one private key will need to be backed up Add a different wallet Ƙara wani asusu na daban + + + Claim a Cash Stamp + Claim a Cash Stamp + TransactionDetails @@ -997,27 +955,37 @@ This ensures only one private key will need to be backed up Cikakken Bayanin Kasuwanci - + + Open in Explorer + Open in Explorer + + + Transaction Hash Zanta Ma'amala - - Rejected - An ƙii + + First Seen + First Seen - + + Rejected + Rejected + + + Unconfirmed Ba a tabbatar ba - + Mined at An haƙo daga - + %1 blocks ago %1 tubalan da suka wuce @@ -1025,144 +993,161 @@ This ensures only one private key will need to be backed up - + Transaction comment Sharhin Ma'amala - + Size: %1 bytes Girma: %1 bytes - + Coinbase Coinbase - + Fees paid An biya kudade - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses An haɗe daga adiresoshin na - + Sent from my addresses An aiko daga adiresoshin na - + Sent from addresses An aiko daga adiresoshi - - - Copy Address - Kwafi Adireshi - - - + Fused into my addresses An haɗe cikin adiresoshin na - + Received at addresses An karɓa a adiresoshi - + Received at my addresses An karɓa a adireshi na - TxInfoSmall + TransactionInfoSmall - + Transaction is rejected - An ƙi ma'amalar ciniki + Transaction is rejected - + Processing - Sarrafawa + Processing - + Mined - Haƙar ma'adinai + Mined - + %1 blocks ago Confirmations - - %1 Tubalin da suka gabata - %1 tubalan da suka wuce + + %1 blocks ago + %1 blocks ago - + Miner Reward - Ladan Ma'adinai + Miner Reward - + Fees - Kudin + Fees - + Received - An samu + Received - + Payment to self - Biyan kuɗi ga kai + Payment to self - + Sent - An aika + Sent - + Holds a token - Rike alama + Holds a token - + Sent to - An aika zuwa + Sent to - - Value now - Daraja yanzu - - - - Value then - Daraja zuwa ga - - - + Transaction Details Cikakken Bayanin Kasuwanci + + TransactionListItem + + + Miner Reward + Miner Reward + + + + Fused + Fused + + + + Received + Received + + + + Moved + Moved + + + + Sent + Sent + + + + Rejected + Rejected + + UnlockWidget @@ -1171,7 +1156,7 @@ This ensures only one private key will need to be backed up Shiga da lambar wucewar asusu - + Open open wallet with PIN Bude diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index 1cf5f5c..e7716a9 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -33,5 +33,6 @@ module-send-sweep_de.qm module-send-sweep_pl.qm module-send-sweep_en.qm + module-send-sweep_ha.qm diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts index 9fcfb72..276afee 100644 --- a/translations/module-build-transaction_ha.ts +++ b/translations/module-build-transaction_ha.ts @@ -40,7 +40,7 @@ - + Add Destination Ƙara madakata @@ -110,7 +110,7 @@ - + Copy Address Kwafi Adireshi @@ -131,42 +131,37 @@ Adireshin Kuɗi na Bitcoin - - Paste - Wallafa - - - + Warning Gargaɗi - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + I am certain Na tabbata - + Drag to Edit Jawo don Gyarawa - + Drag to Delete Jawo don gogewa - + Unlock Wallet Buɗe Asusu - + Prepare Payment... Shirya Biya... diff --git a/translations/module-peers-view_en.ts b/translations/module-peers-view_en.ts index 79a1e01..ccadf6f 100644 --- a/translations/module-peers-view_en.ts +++ b/translations/module-peers-view_en.ts @@ -14,53 +14,54 @@ Statistics - + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Opening Connection - + Validating peer Validating peer - + Validated Validated - + Good Peer + A useful peer Good Peer - + Peer for wallet: %1 Peer for wallet: %1 - + Disconnect Peer Disconnect Peer - + Ban Peer Ban Peer diff --git a/translations/module-peers-view_ha.ts b/translations/module-peers-view_ha.ts index df60e4d..cf47cff 100644 --- a/translations/module-peers-view_ha.ts +++ b/translations/module-peers-view_ha.ts @@ -4,45 +4,66 @@ NetView - + Peers Takwarori - + + Statistics + Statistics + + + Address network address (IP) Adireshi - + Start-height: %1 Fara-tsawo: %1 - + ban-score: %1 Tsame-ci: %1 - - initializing connection - Farawa haɗi + + Opening Connection + Opening Connection - - Verifying peer - Tabbatar da tsari + + Validating peer + Validating peer - + + Validated + Validated + + + + Good Peer + A useful peer + Good Peer + + + Peer for wallet: %1 Haɗin asusu %1 - - Peer for wallet - Takwaran asusu + + Disconnect Peer + Disconnect Peer + + + + Ban Peer + Ban Peer @@ -63,4 +84,52 @@ Matsayin hanyar sadarwa + + StatsPage + + + IP-Address Statistics + IP-Address Statistics + + + + Counts + Counts + + + + Total found + Total found + + + + Tried + Tried + + + + Misbehaving IPs + Misbehaving IPs + + + + Bad + Bad + + + + Banned + Banned + + + + Network + Network + + + + Pardon the Banned + Pardon the Banned + + diff --git a/translations/module-send-sweep_en.ts b/translations/module-send-sweep_en.ts index 05d1115..9de41db 100644 --- a/translations/module-send-sweep_en.ts +++ b/translations/module-send-sweep_en.ts @@ -1,103 +1,105 @@ - - + + SendPage - - Sweep coins - + + Sweep coins + Sweep coins - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds - - Sweeping from address: - + + Sweeping from address: + Sweeping from address: - - Found %1 coins on address. - this is a simple number - - - + + Found %1 coins on address. + this is a simple number + + Found %1 coins on address. + Found %1 coins on address. + - - Ignoring %1 tokens. - Number of CashTokens - - - + + Ignoring %1 tokens. + Number of CashTokens + + Ignoring %1 tokens. + Ignoring %1 tokens. + - - Failed to understand QR - + + Failed to understand QR + Failed to understand QR - - Indexer results invalid. Please try again. - + + Indexer results invalid. Please try again. + Indexer results invalid. Please try again. - - Transfer to: - + + Transfer to: + Transfer to: - - Sending Payment - + + Sending Payment + Sending Payment - - Payment Sent - + + Payment Sent + Payment Sent - - Failed - + + Failed + Failed - - Transaction rejected by network - + + Transaction rejected by network + Transaction rejected by network - - The payment has been sent to: - Followed by the address - + + The payment has been sent to: + Followed by the address + The payment has been sent to: - - Close - + + Close + Close - - + + SendSweepModuleInfo - - Sweep & Send - + + Sweep & Send + Sweep & Send - - Allows sweeping a paper-wallet, moving the contents to your own wallet - + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Allows sweeping a paper-wallet, moving the contents to your own wallet - - Sweep Paper Wallet - + + Sweep Paper Wallet + Sweep Paper Wallet - + diff --git a/translations/module-send-sweep_ha.ts b/translations/module-send-sweep_ha.ts new file mode 100644 index 0000000..4fdee41 --- /dev/null +++ b/translations/module-send-sweep_ha.ts @@ -0,0 +1,105 @@ + + + + + SendPage + + + Sweep coins + Sweep coins + + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + + + Sweeping from address: + Sweeping from address: + + + + Found %1 coins on address. + this is a simple number + + Found %1 coins on address. + Found %1 coins on address. + + + + + Ignoring %1 tokens. + Number of CashTokens + + Ignoring %1 tokens. + Ignoring %1 tokens. + + + + + Failed to understand QR + Failed to understand QR + + + + Indexer results invalid. Please try again. + Indexer results invalid. Please try again. + + + + Transfer to: + Transfer to: + + + + Sending Payment + Aika Biyan Kuɗi + + + + Payment Sent + An aika Biya + + + + Failed + Ba a yi nasara ba + + + + Transaction rejected by network + hanyar sadarwa ta ƙi ciniki + + + + The payment has been sent to: + Followed by the address + The payment has been sent to: + + + + Close + Kulle + + + + SendSweepModuleInfo + + + Sweep & Send + Sweep & Send + + + + Allows sweeping a paper-wallet, moving the contents to your own wallet + Allows sweeping a paper-wallet, moving the contents to your own wallet + + + + Sweep Paper Wallet + Sweep Paper Wallet + + + -- 2.54.0 From 520241ba9b480709a54c2e933803cd35caf1843f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Dec 2024 15:51:55 +0100 Subject: [PATCH 1070/1428] Remove support for old Qt This removes the support for older than qt65 for mobile. Qt65 was released April 2023, making the code we now release not needed anymore. End of 2024, and us moving to 6.8 feels like we're safe in removing it. --- src/CameraController.cpp | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index c6a63ae..9877c69 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -32,13 +32,7 @@ #include #include #include - -#if defined(TARGET_OS_Android) && QT_VERSION < QT_VERSION_CHECK(6, 5, 0) -# include -#endif -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) #include -#endif enum AskingState { NotAsked, @@ -520,7 +514,6 @@ void CameraController::startRequest(QRScanner *request) } if (d->state == NotAsked) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) switch (QCoreApplication::instance()->checkPermission(QCameraPermission {})) { case Qt::PermissionStatus::Granted: d->state = Authorized; break; case Qt::PermissionStatus::Denied: d->state = Denied; break; @@ -541,37 +534,6 @@ void CameraController::startRequest(QRScanner *request) }); break; } -#else -# ifdef TARGET_OS_Android - // QCoreApplication::requestPermission was only introduced in Qt6.5 - d->state = Asking; - auto future = QtAndroidPrivate::checkPermission(QtAndroidPrivate::Camera); - future.then([=](QtAndroidPrivate::PermissionResult res) { - if (res == QtAndroidPrivate::PermissionResult::Authorized) { - d->state = Authorized; - emit startCheckState(); - } - else { - // then ask - auto future = QtAndroidPrivate::requestPermission(QtAndroidPrivate::Camera); - future.then([=](QtAndroidPrivate::PermissionResult res) { - if (res == QtAndroidPrivate::Authorized) { - d->state = Authorized; - // move the actual turning on of the camera to the next event. - // please notice that this reply came in a different thread than the main one. - // We must move back to the main one before doing anything. - emit startCheckState(); - } else { - d->state = Denied; - } - }); - } - }); - return; -# else - d->state = Authorized; -# endif -#endif } // give the overlay screen time to appear before // activating the camera. -- 2.54.0 From 0dbff50f2bd7c2d4c14b53758be0e3dc30eebaa8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Dec 2024 16:56:10 +0100 Subject: [PATCH 1071/1428] Rename for consistency and cleanup This cleans up the frame after usage in order to make sure that the next iteration of scanning will happen based on new data. --- src/CameraController.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 9877c69..af40791 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -71,7 +71,7 @@ public: int streamWidth = -1; int streamHeight = -1; - QRScanningThread *m_scanningThread = nullptr; + QRScanningThread *scanningThread = nullptr; CameraController *q; }; @@ -241,10 +241,10 @@ void CameraControllerPrivate::checkState() currentFrame = frame; }); - assert(m_scanningThread == nullptr); - m_scanningThread = new QRScanningThread(this); - QObject::connect (m_scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); - m_scanningThread->start(); + assert(scanningThread == nullptr); + scanningThread = new QRScanningThread(this); + QObject::connect (scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection); + scanningThread->start(); logDebug() << "Camera active is now true"; emit q->cameraActiveChanged(); // this emit makes QML activate the camera @@ -263,7 +263,7 @@ QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) void QRScanningThread::run() { - auto lastFrameScanned = time(nullptr); + auto lastFrameScanned = 0; while (true) { const auto now = time(nullptr); auto sleep = 34 - (now - lastFrameScanned); // assume 30 - FPS @@ -551,7 +551,7 @@ void CameraController::abortRequest(QRScanner *request) d->lock.unlock(); emit cameraActiveChanged(); - if (d->m_scanningThread == nullptr) { + if (d->scanningThread == nullptr) { // then the above would have no effect; qrScanFinished(); } @@ -625,7 +625,7 @@ bool CameraController::pasteData(const QString &string) // stop camera d->cameraStarted = false; emit cameraActiveChanged(); - if (d->m_scanningThread == nullptr) { + if (d->scanningThread == nullptr) { // then the above emit would have no effect; qrScanFinished(); } @@ -682,10 +682,11 @@ bool CameraController::visible() const void CameraController::qrScanFinished() { QString resultText; - if (d->m_scanningThread) { - resultText = d->m_scanningThread->text; - d->m_scanningThread->deleteLater(); - d->m_scanningThread = nullptr; + if (d->scanningThread) { + resultText = d->scanningThread->text; + d->scanningThread->deleteLater(); + d->scanningThread = nullptr; + d->currentFrame = QVideoFrame(); } // stop copying video frames assert(d->videoSink); -- 2.54.0 From f3de74d7b32079157f9c334f225bb61710002018 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 21 Dec 2024 12:50:27 +0100 Subject: [PATCH 1072/1428] Add subtext --- modules/build-transaction/BuildTransactionModuleInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 1323c3a..9f9b5d6 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -25,6 +25,7 @@ ModuleInfo * BuildTransactionModuleInfo::build() auto sendButton = new ModuleSection(ModuleSection::ActionTabItem, module); sendButton->setText(tr("Build Transaction")); + sendButton->setSubtext(tr("Manually select templates")); // notice that the directory it is in is registered in the data.qrc header sendButton->setStartQMLFile("qrc:/build-transaction/PayToOthers.qml"); module->addSection(sendButton); -- 2.54.0 From b64082dfa4ea6a1987a2ed936a2e12b1af985ef9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 21 Dec 2024 12:50:53 +0100 Subject: [PATCH 1073/1428] Add icon feature to TextButton --- guis/mobile/TextButton.qml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index a2c6c6d..04245f8 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,15 +28,30 @@ Item { property alias text: label.text property alias subtext: smallLabel.text /// When true, show an arrow indicating we open a new page on click - property bool showPageIcon: false + property bool showPageIcon: false // TODO rename + + /// when showPageIcon is false, place an image on the right side instead property string imageSource: "" - property int imageWidth: 16 - property int imageHeight: 16 + property int imageWidth: 20 + property int imageHeight: 20 + + /// Add an icon before the label + property string iconSource: "" + + Image { + id: icon + visible: root.iconSource !== "" + source: root.iconSource + width: 28 + height: 28 + anchors.verticalCenter: parent.verticalCenter + } Flowee.Label { id: label y: 10 - width: parent.width + x: icon.visible ? 38 : 0 + width: parent.width - x wrapMode: Text.WordWrap color: enabled ? palette.windowText : palette.brightText } @@ -47,7 +62,8 @@ Item { font.pixelSize: label.font.pixelSize * 0.8 font.bold: false color: palette.brightText - width: parent.width + width: label.width + x: label.x wrapMode: Text.WordWrap } MouseArea { -- 2.54.0 From 184ca5136a63f99aaddfdbd8d662aa5b2355b85a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 21 Dec 2024 13:04:19 +0100 Subject: [PATCH 1074/1428] Draft UX idea --- guis/mobile/MainView.qml | 58 ++++++++++++++++++ guis/mobile/SendTransactionsTab.qml | 92 +++++++++++++++++++++++------ 2 files changed, 131 insertions(+), 19 deletions(-) diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 679e6b8..f3556c9 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -37,6 +37,64 @@ MainViewBase { anchors.leftMargin: 10 anchors.rightMargin: 10 } + FocusScope { + id: apps + property string icon: "qrc:/apps" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string title: qsTr("Apps") + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + Column { + width: parent.width + spacing: 10 + PageTitledBox { + width: parent.width + title: qsTr("Often Used Apps") + TextButton { + width: parent.width + text: qsTr("Sweep Paper Wallet") + showPageIcon: true + } + TextButton { + width: parent.width + text: qsTr("Peers View") + showPageIcon: true + } + } + PageTitledBox { + width: parent.width + title: "Apps" + TextButton { + text: qsTr("Make Wallet Backup") + showPageIcon: true + } + TextButton { + width: parent.width + text: qsTr("Block History") + showPageIcon: true + } + TextButton { + width: parent.width + text: qsTr("Price History") + showPageIcon: true + } + } + + PageTitledBox { + width: parent.width + title: qsTr("Token Apps") + TextButton { + text: qsTr("Create NFT") + showPageIcon: true + } + TextButton { + text: qsTr("Create Airdrop") + showPageIcon: true + } + } + } + } // only visible on AccountHistory for now. onFilterIconClicked: popupOverlay.open(filterPopup, null) diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index d1067eb..ba70c31 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,26 +23,80 @@ FocusScope { property string icon: "qrc:/sending" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); property string title: qsTr("Send") - ColumnLayout { - width: parent.width - 20 - x: 10 - y: 10 + Flickable { + anchors.fill: parent + contentHeight: col.height + boundsBehavior: Flickable.StopAtBounds + ColumnLayout { + id: col + width: parent.width - 20 + x: 10 + y: 10 + spacing: 10 - InstaPayConfigButton { - } + PageTitledBox { + width: parent.width + title: qsTr("Start Payment") - TextButton { - text: qsTr("Start Payment") - showPageIcon: true - onClicked: thePile.push("PayWithQR.qml") - } - Repeater { - model: ModuleManager.sendMenuItems - TextButton { - text: modelData.text - subtext: modelData.subtext - showPageIcon: true - onClicked: thePile.push(modelData.qml) + TextButton { + text: qsTr("Scan QR") + showPageIcon: true + iconSource: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; + onClicked: thePile.push("PayWithQR.qml") + } + TextButton { + text: qsTr("Load Photo with QR") + showPageIcon: true + } + TextButton { + text: qsTr("Paste") + iconSource: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + showPageIcon: true + } + TextButton { + text: qsTr("NFC Payment") + showPageIcon: true + } + + Repeater { + model: ModuleManager.sendMenuItems + TextButton { + text: modelData.text + subtext: modelData.subtext + showPageIcon: true + onClicked: thePile.push(modelData.qml) + } + } + TextButton { + text: qsTr("Transfer All") + subtext: qsTr("Move between wallets") + showPageIcon: true + } + } + + PageTitledBox { + width: parent.width + title: qsTr("Options") + + TextButton { + // TODO this is copied from AccountsList.qml, avoid duplication... + text: qsTr("Default Wallet") + showPageIcon: true + visible: !portfolio.singleAccountSetup + subtext: { + for (let a of portfolio.rawAccounts) { + if (a.isPrimaryAccount) { + var defaultAccount = a.name; + break; + } + } + + qsTr("%1 is used on startup").arg(defaultAccount); + } + onClicked: thePile.push("./SelectDefaultAccountPage.qml"); + } + + InstaPayConfigButton { } } } } -- 2.54.0 From 3800093643da2f99df90fb153bc0de4a17f7fd73 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 14:41:46 +0100 Subject: [PATCH 1075/1428] Rework 'Send' page. Part 1. The initial design has done well for over 2 years, but problems are starting to show. This does a bit of cleanup in the UX and many cleanups in the underlying architecture that were the result of those UX choices. We remove the clipboard (paste) concept from the camera pipeline completely and simply make it a new top-level button "paste" on the send page. This helps discovery AND helps architecture! The both workflows now also become 2 stage affairs, when the button is pressed we open a page that does the scanning or pasting and then introspects the actual data in order to redirect to the right page. This means that we auto-detect if the scanned item is an address or a private key or whatever, and handle it appropriately without needing any user interaction. --- guis/Flowee/QRScanner.qml | 5 +- guis/mobile.qrc | 4 + guis/mobile/AccountHistory.qml | 2 +- guis/mobile/ImportWalletPage.qml | 5 +- guis/mobile/PayWithQR.qml | 24 ++++- guis/mobile/ScanQRPage.qml | 75 ++++++++++++++++ guis/mobile/SendTransactionsTab.qml | 74 +++++++++++++--- guis/mobile/WorkflowStarter.js | 43 +++++++++ guis/mobile/images/shrug-light.svg | 8 ++ guis/mobile/images/shrug.svg | 8 ++ guis/mobile/main.qml | 2 +- modules/send-sweep/SendPage.qml | 10 ++- src/CameraController.cpp | 130 ++++++++++++++-------------- src/CameraController.h | 2 + src/QMLClipboardHelper.cpp | 110 +++++++++++++++-------- src/QMLClipboardHelper.h | 17 ++-- src/QRScanner.cpp | 11 +-- src/QRScanner.h | 14 +-- 18 files changed, 405 insertions(+), 139 deletions(-) create mode 100644 guis/mobile/ScanQRPage.qml create mode 100644 guis/mobile/WorkflowStarter.js create mode 100644 guis/mobile/images/shrug-light.svg create mode 100644 guis/mobile/images/shrug.svg diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index 15c3895..509708d 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -108,6 +108,7 @@ FocusScope { } } +/* Item { id: pasteFrame x: 50 @@ -185,10 +186,12 @@ FocusScope { target: pasteButton } } +*/ Rectangle { id: flashFrame - anchors.top: pasteFrame.top + anchors.top: topBar.bottom + anchors.topMargin: 6 anchors.right: parent.right anchors.rightMargin: 50 radius: 6 diff --git a/guis/mobile.qrc b/guis/mobile.qrc index a55a076..6f6a02e 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -42,9 +42,12 @@ mobile/images/module-mainmenu-light.svg mobile/images/filter-light.svg mobile/images/hat.svg + mobile/images/shrug.svg + mobile/images/shrug-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js + mobile/WorkflowStarter.js mobile/About.qml mobile/AccountHistory.qml mobile/AccountPageListItem.qml @@ -74,6 +77,7 @@ mobile/PriceDetails.qml mobile/PriceInputWidget.qml mobile/ReceiveTab.qml + mobile/ScanQRPage.qml mobile/SelectDefaultAccountPage.qml mobile/SendTransactionsTab.qml mobile/SlideToApprove.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index a669444..ad7dc05 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -147,7 +147,7 @@ ListView { Flowee.ImageButton { id: startScan source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - onClicked: thePile.push("PayWithQR.qml") + onClicked: thePile.push("ScanQRPage.qml") iconSize: 70 text: qsTr("Pay") } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index eab100c..0176ba5 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -45,6 +45,8 @@ Page { ] state: "entryPage" + property alias secret: secretText.text + backHandler: function handler() { // we practically have two or 3 pages inside this on Page object, plus a popup! // we should make the 'back' button be aware of this. @@ -169,9 +171,8 @@ Page { } QRScanner { id: scanner - scanType: QRScanner.SeedOrPrivKey onFinished: { - if (scanResult !== "") + if ((scanType === QRScanner.Seed || QRScanner.PrivateKeyWIF) && scanResult !== "") secretText.text = scanResult; // make sure to give focus back to this page after the camera took it. scanButton.forceActiveFocus(); diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 79e2318..295e703 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -54,7 +54,7 @@ Page { menuItems: { // only have menu items as long as we are effectively // showing this page and not some overlay. - if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) { + if (payment.broadcastStatus === Payment.NotStarted) { // a QR _with_ a bch-amount will turn off editing of amount-to-send if (allowEditAmount) return [ showTargetAddress, sendAllAction ]; @@ -66,7 +66,23 @@ Page { // if true, show widgets to edit the amount-to-send property bool allowEditAmount: true + function start(paymentAddress) { + let success = payment.pasteTargetAddress(paymentAddress); + if (!success) { + scannedUrlFaultyDialog. + scannedUrlFaultyDialog.open(); + } + + // should the price be included in the QR code, don't show editing widgets. + root.allowEditAmount = payment.paymentAmount <= 0; + if (root.allowEditAmount) + priceInput.takeFocus(); + else + root.takeFocus(); + } + Item { // data +/* QRScanner { id: scanner scanType: QRScanner.PaymentDetails @@ -99,6 +115,7 @@ Page { root.takeFocus(); } } +*/ Payment { id: payment account: portfolio.current @@ -133,8 +150,9 @@ Page { Flowee.Dialog { id: scannedUrlFaultyDialog title: qsTr("Invalid QR code") + property string scanResult: "" standardButtons: QQC2.DialogButtonBox.Close - onRejected: thePile.pop(); // remove this entire page + onRejected: thePile.pop(); contentComponent: dialogForFaultyUrl } Component { @@ -160,7 +178,7 @@ Page { } Flowee.Label { id: detailsLabel - text: qsTr("Scanned text:
%1
").arg(scanner.scanResult); + text: qsTr("Scanned text:
%1
").arg(scannedUrlFaultyDialog.scanResult); visible: false font.pixelSize: mainText.pixelSize * 0.8 wrapMode: Text.Wrap diff --git a/guis/mobile/ScanQRPage.qml b/guis/mobile/ScanQRPage.qml new file mode 100644 index 0000000..23f9042 --- /dev/null +++ b/guis/mobile/ScanQRPage.qml @@ -0,0 +1,75 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay; +import "WorkflowStarter.js" as WorkflowStarter + +Page { + id: root + + Item { // data + QRScanner { + id: scanner + autostart: true + onFinished: { + var type = scanType; + if (type === QRScanner.InvalidType) { // scanning interrupted + thePile.pop(); + } + else if (type === QRScanner.Seed || type === QRScanner.PrivateKeyWIF) { + if (WorkflowStarter.startSweep(scanResult)) + return; + // plugin missing, proceed to start the import page flow. + WorkflowStarter.startImportWithSecret(scanResult); + } + else if (type === QRScanner.PaymentDetails) { + var item = thePile.replace("PayWithQR.qml"); + item.start(scanResult); + } + else if (type === QRScanner.PaymentDetailsTestnet) { + // TODO if we end up supporting the testnet, this may be useful + shrug.visible = true; + detectedText.text = scanResult; + } + } + } + } + + Image { + id: shrug + y: 200 + visible: false + anchors.horizontalCenter: parent.horizontalCenter + width: 200 + height: 170 + source: "qrc:/shrug" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + } + + Flowee.Label { + id: detectedText + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: shrug.bottom + anchors.topMargin: 10 + width: root.width - 20 + x: 10 + wrapMode: Text.WrapAnywhere + } +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index ba70c31..3633b12 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -17,6 +17,10 @@ */ import QtQuick import QtQuick.Layouts +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay; +import "WorkflowStarter.js" as WorkflowStarter FocusScope { id: root @@ -42,20 +46,13 @@ FocusScope { text: qsTr("Scan QR") showPageIcon: true iconSource: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; - onClicked: thePile.push("PayWithQR.qml") - } - TextButton { - text: qsTr("Load Photo with QR") - showPageIcon: true + onClicked: thePile.push("ScanQRPage.qml") } TextButton { text: qsTr("Paste") iconSource: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); showPageIcon: true - } - TextButton { - text: qsTr("NFC Payment") - showPageIcon: true + onClicked: thePile.push(pastePage); } Repeater { @@ -67,11 +64,6 @@ FocusScope { onClicked: thePile.push(modelData.qml) } } - TextButton { - text: qsTr("Transfer All") - subtext: qsTr("Move between wallets") - showPageIcon: true - } } PageTitledBox { @@ -100,4 +92,58 @@ FocusScope { } } } + + Component { + id: pastePage + Page { + Item { // data + ClipboardHelper { + id: cbh + + onClipboardScanned: { + let t = cbh.type; + if (t === ClipboardHelper.Addresses + || t === ClipboardHelper.LegacyAddresses + || t === ClipboardHelper.AddressUrl) { + var item = thePile.replace("PayWithQR.qml"); + item.start(text); + return; + } + else if (t === ClipboardHelper.PrivateKey) { + if (WorkflowStarter.startSweep(text)) + return; + } + + // if plugin missing, handle privatekey here. + if (t === ClipboardHelper.MnemonicSeed || t === ClipboardHelper.PrivateKey) { + WorkflowStarter.startImportWithSecret(text); + } + else { + shrug.visible = true; + detectedText.text = text; + } + } + } + } + Image { + id: shrug + y: 200 + visible: false + anchors.horizontalCenter: parent.horizontalCenter + width: 200 + height: 170 + source: "qrc:/shrug" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + } + + Flowee.Label { + id: detectedText + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: shrug.bottom + anchors.topMargin: 10 + width: root.width - 20 + x: 10 + wrapMode: Text.WrapAnywhere + } + } + } } diff --git a/guis/mobile/WorkflowStarter.js b/guis/mobile/WorkflowStarter.js new file mode 100644 index 0000000..98a06a4 --- /dev/null +++ b/guis/mobile/WorkflowStarter.js @@ -0,0 +1,43 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +/// opens wif sweep page, or returns false if unavailable +function startSweep(wif) { + for (var mod of ModuleManager.registeredModules) { + if (mod.id === "sendSweepModule") { + for (var section of mod.sections) { + if (section.isActionTabItem) { + thePile.replace(section.qml, { "secret": wif }, + QQC2.StackView.Immediate); + return true; + } + } + break; + } + } + return false; +} + +/// Start import. +function startImportWithSecret(secret) { + thePile.push("ImportWalletPage.qml", + {"secret": secret, + "backHandler": function handler() { thePile.pop(); thePile.pop(); thePile.pop; }}, + QQC2.StackView.Immediate); +} diff --git a/guis/mobile/images/shrug-light.svg b/guis/mobile/images/shrug-light.svg new file mode 100644 index 0000000..2f3d99b --- /dev/null +++ b/guis/mobile/images/shrug-light.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/guis/mobile/images/shrug.svg b/guis/mobile/images/shrug.svg new file mode 100644 index 0000000..d3cc9c3 --- /dev/null +++ b/guis/mobile/images/shrug.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index df369bd..f85261a 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -97,7 +97,7 @@ ApplicationWindow { id: rootFocusScope anchors.fill: parent - StackView { + QQC2.StackView { id: thePile anchors.fill: parent initialItem: "./Loading.qml" diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index 9e3ec8a..e790693 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -27,15 +27,18 @@ Mobile.Page { id: root headerText: qsTr("Sweep coins") + property alias secret: sweeper.privKey + Item { // data +/* QRScanner { id: scanner - scanType: QRScanner.PrivateKeyWIF autostart: Intent.sweepKey === "" helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") onFinished: { var rc = scanResult - if (rc === "") { // scanning interrupted + if (rc === "" // scanning interrupted + || scanType !== QRScanner.PrivateKeyWIF) { thePile.pop(); return; } @@ -48,7 +51,7 @@ Mobile.Page { * if it has then we need to set the privatekey and at * the same time clear out the property to prepare for * next usage. - */ + * / var fromIntent = Intent.sweepKey if (fromIntent !== "") { Intent.sweepKey = ""; // prepare for next usage. @@ -56,6 +59,7 @@ Mobile.Page { } } } +*/ SweepHandler { id: sweeper diff --git a/src/CameraController.cpp b/src/CameraController.cpp index af40791..8397e66 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -82,6 +82,7 @@ public: explicit QRScanningThread(CameraControllerPrivate *parent); QString text; + QRScanner::ScanType scanType; protected: void run(); @@ -94,7 +95,6 @@ private: // this was released in December 2023, so we'll be using the old stuff // for quite a bit longer to keep stuff compiling on older systems. ZXing::DecodeHints m_decodeHints; - QRScanner::ScanType m_scanType; }; @@ -255,7 +255,7 @@ void CameraControllerPrivate::checkState() QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) : m_parent(parent), - m_scanType(parent->scanRequest->scanType()) + scanType(QRScanner::InvalidType) { m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); m_decodeHints.setTryHarder(true); @@ -285,48 +285,65 @@ void QRScanningThread::run() const auto &bytes = result.bytes(); // logInfo() << "result:" << QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); - switch (m_scanType) { - case QRScanner::PrivateKeyWIF: - case QRScanner::SeedOrPrivKey: { - // we expect WIF encoded private keys here - // first, when it starts with 'bch-wif:' this helps, but needs to be cut off. - const bool wifPrefix = bytes.size() >= 58 && bytes.size() < 63 - && (bytes[8] == 'K' || bytes[8] == 'L') - && 0 == memcmp(&bytes[0], "bch-wif:", 8); + // Test if the QR held a Private key + // We support WIF encoded private keys. + // first, when it starts with 'bch-wif:' this helps, but needs to be cut off. + const bool wifPrefix = bytes.size() >= 58 && bytes.size() < 63 + && (bytes[8] == 'K' || bytes[8] == 'L') + && 0 == memcmp(&bytes[0], "bch-wif:", 8); - if (wifPrefix || (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L'))) { - // might be one!! - const size_t prefixSize = wifPrefix ? 8 : 0; - const std::string str(reinterpret_cast(bytes.data() + prefixSize), bytes.size() - prefixSize); - std::vector dummy; - if (Base58::decodeCheck(str, dummy)) { - // good enough for me. Further checking is done by the app, we just exit scanning now. - text = QString::fromUtf8(str); - return; - } + if (wifPrefix || (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L'))) { + // might be one!! + const size_t prefixSize = wifPrefix ? 8 : 0; + const std::string str(reinterpret_cast(bytes.data() + prefixSize), bytes.size() - prefixSize); + std::vector dummy; + if (Base58::decodeCheck(str, dummy)) { + // good enough for me. Further checking is done by the app, we just exit scanning now. + scanType = QRScanner::PrivateKeyWIF; + text = QString::fromUtf8(str); + return; } - if (m_scanType == QRScanner::PrivateKeyWIF) - break; - // no privkey, ok. Then check if its a seed :-) + } - /* - * The Seed QR would obviously just use the 12 or 24 words sentence in a QR. - * Simple. - * - * But a pretty big wallet has instead put a bit more in the QR which ends up having - * the following content: - * - * 1|this holds twelve words|livenet|m/44'/0'/0'|false - * - * No clue what the leading 1 and the trailing false are about. All wallets I tried - * have those :shrug: - * - * Lets try to recognize those two types of QR. - * ------ - * While seeds can be verified with their checksum, here we just check if the general - * pattern fits. - * Only normal characters and spaces are allowed in the basic seed, and 12 words or 24 words. - */ + // Ok, what about a bip21 style url, or a plain address? + // -> starts with bitcoincash: (which is 12 chars, including that colon) + if (bytes.size() > 12 + 40 && memcmp("bitcoincash:", bytes.data(), 12) == 0) { + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + scanType = QRScanner::PaymentDetails; + return; + } + if (bytes.size() > 40 && bytes.size() < 45 && (bytes[0] == 'q' || bytes[0] == 'p')) { + // possibly a raw bitcoin cash address. + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + scanType = QRScanner::PaymentDetails; + return; + } + if (bytes.size() > 8 + 40 && memcmp("bchtest:", bytes.data(), 8) == 0) { + text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); + scanType = QRScanner::PaymentDetailsTestnet; + return; + } + + // not those then, ok. Then check if its a seed :-) + /* + * The Seed QR would obviously just use the 12 or 24 words sentence in a QR. + * Simple. + * + * But a pretty big wallet has instead put a bit more in the QR which ends up having + * the following content: + * + * 1|this holds twelve words|livenet|m/44'/0'/0'|false + * + * No clue what the leading 1 and the trailing false are about. All wallets I tried + * have those :shrug: + * + * Lets try to recognize those two types of QR. + * ------ + * While seeds can be verified with their checksum, here we just check if the general + * pattern fits. + * Only normal characters and spaces are allowed in the basic seed, and 12 words or 24 words. + */ + if (bytes.size() > 12 * 3 + 11) { // at least enough chars for a seed. QString possibleSeed = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); if (possibleSeed.startsWith("1|") && possibleSeed.endsWith("|livenet|m/44'/0'/0'|false")) possibleSeed = possibleSeed.mid(2, possibleSeed.length() - 28); @@ -336,8 +353,9 @@ void QRScanningThread::run() bool failedChecks = false; for (auto i = 0; i < possibleSeed.size(); ++i) { auto c = possibleSeed.at(i); - if (c.isDigit() || c.isSymbol()) + if (c.isDigit() || c.isSymbol()) { failedChecks = true; + } else if (c.isSpace()) { if (seenSpace || i == 0) failedChecks = true; // double space or leading space @@ -353,30 +371,10 @@ void QRScanningThread::run() if (seenSpace == false && wordCount > 0) ++wordCount; // one more word not registered due to lack of space after. if (!failedChecks && (wordCount == 12 || wordCount == 24)) { + scanType = QRScanner::Seed; text = possibleSeed; - return; // makes the 'QThread::finished()' signal get emitted. - - } - break; - } - case QRScanner::PaymentDetails: - // starts with bitcoincash: (which is 12 chars, including that colon) - if (bytes.size() > 12 + 40 && memcmp("bitcoincash:", bytes.data(), 12) == 0) { - text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); return; } - if (bytes.size() > 40 && bytes.size() < 45 && (bytes[0] == 'q' || bytes[0] == 'p')) { - // possibly a raw bitcoin cash address. - text = QString::fromUtf8(reinterpret_cast(bytes.data()), bytes.size()); - return; - } - break; - case QRScanner::PaymentDetailsTestnet: - assert(false); // TODO - return; - default: - logFatal() << "Unknown scanType provided"; - return; } } } @@ -611,6 +609,7 @@ void CameraController::setTorchEnabled(bool on) emit torchEnabledChanged(); } +#if 0 bool CameraController::pasteData(const QString &string) { if (d->scanRequest == nullptr) @@ -633,6 +632,7 @@ bool CameraController::pasteData(const QString &string) } return !string.isEmpty(); } +#endif void CameraController::setCamera(QObject *object) { @@ -682,8 +682,10 @@ bool CameraController::visible() const void CameraController::qrScanFinished() { QString resultText; + QRScanner::ScanType scanType = QRScanner::InvalidType; if (d->scanningThread) { resultText = d->scanningThread->text; + scanType = d->scanningThread->scanType; d->scanningThread->deleteLater(); d->scanningThread = nullptr; d->currentFrame = QVideoFrame(); @@ -696,7 +698,7 @@ void CameraController::qrScanFinished() emit visibleChanged(); setHelpText(QString()); if (d->scanRequest) { - d->scanRequest->finishedScan(resultText, QRScanner::Camera); + d->scanRequest->finishedScan(resultText, scanType); d->scanRequest = nullptr; } diff --git a/src/CameraController.h b/src/CameraController.h index 70477a3..0281a6d 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -57,10 +57,12 @@ public: void abortRequest(QRScanner *request); Q_INVOKABLE void abort(); +#if 0 /** * Try to complete the current scan request by instead taking the \a string. */ Q_INVOKABLE bool pasteData(const QString &string); +#endif void setCamera(QObject *object); QObject *camera() const; diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index c537e65..499c81b 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -25,8 +25,7 @@ #include QMLClipboardHelper::QMLClipboardHelper(QObject *parent) - : QObject{parent}, - m_filter(Addresses) + : QObject{parent} { // when the app regains main-app status, check for changes in the clipboard. auto guiApp = qobject_cast(QCoreApplication::instance()); @@ -54,7 +53,9 @@ void QMLClipboardHelper::parseClipboard() text = mimeData->html(); if (text.isEmpty() || m_filter == NoFilter) { - setClipboardText(text); + setClipboardText(text, Empty); + + emit clipboardScanned(); return; } // often found whitespace, make it all spaces. @@ -63,64 +64,100 @@ void QMLClipboardHelper::parseClipboard() text = text.replace(QLatin1String("\r"), QLatin1String(" ")); text = text.trimmed(); - const QString prefix = QString::fromStdString(chainPrefix()) + ":"; - QString result; auto type = FloweePay::instance()->identifyString(text); if (type == WalletEnums::Unknown || type == WalletEnums::PartialMnemonicWithTypo || type == WalletEnums::PartialMnemonic) { // try harder - auto index = text.indexOf(prefix); - if (index >= 0) { - auto end = text.indexOf(' ', index + prefix.size()); - result = text.mid(index, end); + QString partial(text); + QString prefix = QString::fromStdString(chainPrefix()) + ":"; + while (true) { // find all 'words' starting with our chain prefix. + auto index = partial.indexOf(prefix); + if (index < 0) + break; + auto end = partial.indexOf(' ', index + prefix.size()); + QString tillSpace = partial.mid(index, end); + const int endOfAddress = tillSpace.indexOf('?'); + if (endOfAddress > 0) { + type = FloweePay::instance()->identifyString(tillSpace.left(endOfAddress)); + if (type == WalletEnums::CashPKH || type == WalletEnums::CashSH) { + text = tillSpace; + break; + } + } + // look further. + partial = partial.mid(index + 1); } - else { - // find the address if it doesn't have the prefix. + + prefix = "bch-wif:"; + while (true) { // find all 'words' starting with "bch-wif:" + auto index = partial.indexOf(prefix); + if (index < 0) + break; + index += prefix.size(); + auto end = partial.indexOf(' ', index); + if (end < 0) + end = partial.size(); + if (end - index >= 50 && end - index < 55) { + QString wif = partial.mid(index, end); + type = FloweePay::instance()->identifyString(wif); + if (type == WalletEnums::PrivateKey) { + text = wif; + break; + } + } + + // look further. + partial = partial.mid(index + 1); + } + if (type != WalletEnums::CashPKH && type != WalletEnums::CashSH && type != WalletEnums::PrivateKey) { + // still no success. + // try to find the address even if it doesn't have the prefix. for (auto &word : text.split(' ', Qt::SkipEmptyParts)) { if (word.length() > 40 && word.length() < 50) { - auto id = FloweePay::instance()->identifyString(prefix + word); - if (id == WalletEnums::CashPKH || id == WalletEnums::CashSH) { - result = prefix + word; + QString address = prefix + word; + type = FloweePay::instance()->identifyString(address); + if (type == WalletEnums::CashPKH || type == WalletEnums::CashSH) { + text = address; break; } } } } - type = FloweePay::instance()->identifyString(result); - text = result; } - bool itsAHit = false; - if ((type == WalletEnums::Unknown || type == WalletEnums::PartialMnemonicWithTypo - || type == WalletEnums::PartialMnemonic) - && m_filter.testAnyFlag(AddressUrl)) { // try finding AddressUrl (aka bip21) + Type typeFound = NoFilter; + if (m_filter.testAnyFlag(Addresses) + && (type == WalletEnums::CashPKH || type == WalletEnums::CashSH)) { + typeFound = Addresses; const int endOfAddress = text.indexOf('?'); - if (endOfAddress > 0 && text.startsWith(prefix)) - itsAHit = true; + if (endOfAddress > 0) { + const QString prefix = QString::fromStdString(chainPrefix()) + ":"; + if (text.startsWith(prefix)) + typeFound = AddressUrl; + } } - else if (m_filter.testAnyFlag(Addresses) - && (type == WalletEnums::CashPKH || type == WalletEnums::CashSH)) - itsAHit = true; else if (m_filter.testAnyFlag(LegacyAddresses) && (type == WalletEnums::LegacyPKH || type == WalletEnums::LegacySH)) - itsAHit = true; + typeFound = LegacyAddresses; else if (m_filter.testAnyFlag(PrivateKey) && type == WalletEnums::PrivateKey) - itsAHit = true; + typeFound = PrivateKey; else if (m_filter.testAnyFlag(MnemonicSeed) && (type == WalletEnums::CorrectMnemonic || type == WalletEnums::ElectrumMnemonic)) - itsAHit = true; + typeFound = MnemonicSeed; else if (m_filter.testAnyFlag(XPub) && type == WalletEnums::XPub) - itsAHit = true; + typeFound = XPub; else if (m_filter.testAnyFlag(XPriv) && type == WalletEnums::XPriv) - itsAHit = true; + typeFound = XPriv; - if (itsAHit) - setClipboardText(text); + if (typeFound != NoFilter) + setClipboardText(text, typeFound); + emit clipboardScanned(); } -void QMLClipboardHelper::setClipboardText(const QString &text) +void QMLClipboardHelper::setClipboardText(const QString &text, Type type) { - if (m_text == text) + if (m_text == text && m_type == type) return; m_text = text; + m_type = type; emit textChanged(); } @@ -151,6 +188,11 @@ QString QMLClipboardHelper::clipboardText() const return m_text; } +QMLClipboardHelper::Type QMLClipboardHelper::type() const +{ + return m_type; +} + void QMLClipboardHelper::setFilter(QMLClipboardHelper::Types filter) { if (m_filter == filter) diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index baaf833..ff6900e 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -42,6 +42,7 @@ class QMLClipboardHelper : public QObject Q_OBJECT Q_PROPERTY(QString text READ clipboardText NOTIFY textChanged) Q_PROPERTY(Types filter READ filter WRITE setFilter NOTIFY filterChanged) + Q_PROPERTY(Type type READ type NOTIFY textChanged) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) public: explicit QMLClipboardHelper(QObject *parent = nullptr); @@ -52,9 +53,12 @@ public: LegacyAddresses = 2, AddressUrl = 4, PrivateKey = 8, - MnemonicSeed = 16, - XPub = 32, - XPriv = 64, + MnemonicSeed = 0x10, + XPub = 0x20, + XPriv = 0x40, + + AnyType = 0x4F, + Empty = 0x4000 }; Q_ENUM(Type) Q_DECLARE_FLAGS(Types, Type) @@ -63,6 +67,7 @@ public: void setFilter(Types filters); Types filter() const; + Type type() const; bool enabled() const; void setEnabled(bool newEnabled); @@ -71,16 +76,18 @@ signals: void textChanged(); void filterChanged(); void enabledChanged(); + void clipboardScanned(); private: void parseClipboard(); - void setClipboardText(const QString &text); + void setClipboardText(const QString &text, Type type); void delayedParse(); - Types m_filter; + Types m_filter = AnyType; QString m_text; bool m_delayedParseStarted = false; bool m_enabled = true; + Type m_type = Empty; }; #endif diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index 7eb9018..a1bc560 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -36,8 +36,6 @@ void QRScanner::start() { #ifndef NO_MULTIMEDIA resetScanResult(); - if (m_scanType == InvalidType) - throw std::runtime_error("Required property scanType not set"); setIsScanning(true); FloweePay::instance()->cameraController()->startRequest(this); #endif @@ -64,11 +62,13 @@ void QRScanner::setScanType(ScanType type) emit scanTypeChanged(); } -void QRScanner::finishedScan(const QString &result, ResultSource source) +void QRScanner::finishedScan(const QString &result, ScanType type) { m_scanResult = result; - m_resultSource = source; + // m_resultSource = source; + m_scanType = type; emit scanResultChanged(); + emit scanTypeChanged(); emit finished(); setIsScanning(false); } @@ -133,7 +133,8 @@ void QRScanner::setHelpText(const QString &newHelpText) emit helpTextChanged(); } +/* QRScanner::ResultSource QRScanner::resultSource() const { return m_resultSource; -} +}*/ diff --git a/src/QRScanner.h b/src/QRScanner.h index 408f178..78b29bb 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -24,11 +24,11 @@ class QRScanner : public QObject { Q_OBJECT - Q_PROPERTY(ScanType scanType READ scanType WRITE setScanType NOTIFY scanTypeChanged REQUIRED) + Q_PROPERTY(ScanType scanType READ scanType NOTIFY scanTypeChanged) Q_PROPERTY(QString scanResult READ scanResult NOTIFY scanResultChanged) Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) - Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) + // Q_PROPERTY(ResultSource resultSource READ resultSource NOTIFY scanResultChanged) /// Set a help text to be displayed at the top of the scanning-page. Q_PROPERTY(QString helpText READ helpText WRITE setHelpText NOTIFY helpTextChanged FINAL) public: @@ -38,7 +38,7 @@ public: Q_INVOKABLE void abort(); enum ScanType { - SeedOrPrivKey, + Seed, PaymentDetails, PaymentDetailsTestnet, PrivateKeyWIF, @@ -49,14 +49,16 @@ public: ScanType scanType() const; void setScanType(ScanType type); +/* enum ResultSource { Camera, Clipboard }; Q_ENUM(ResultSource) +*/ /// Notice that resultString may be empty if we didn't scan any valid QR - void finishedScan(const QString &resultString, ResultSource source); + void finishedScan(const QString &resultString, ScanType foundDataType); QString scanResult() const; void resetScanResult(); @@ -67,7 +69,7 @@ public: bool isScanning() const; void setIsScanning(bool now); - ResultSource resultSource() const; + // ResultSource resultSource() const; QString helpText() const; void setHelpText(const QString &newHelpText); @@ -92,7 +94,7 @@ signals: private: ScanType m_scanType; - ResultSource m_resultSource = Camera; + // ResultSource m_resultSource = Camera; QString m_scanResult; QString m_helpText; bool m_autostart = false; -- 2.54.0 From 644241255f2a358bbb06da35c9e091b70c5642af Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 15:02:01 +0100 Subject: [PATCH 1076/1428] Revert "API level rename of 'send' to 'action'" This reverts commit 9f69241bbb574192dee57f4dc182e36248da9fe1. Reason for revert is that instead of renaming the send, we're now creating a new tab instead. So we'll keep "Send" and add a new one as well. --- guis/mobile/ExploreModules.qml | 2 +- guis/mobile/StartupScreen.qml | 2 +- modules/build-transaction/BuildTransactionModuleInfo.cpp | 2 +- modules/example/ExampleModuleInfo.cpp | 2 +- modules/send-sweep/SendSweepModuleInfo.cpp | 2 +- src/ModuleInfo.cpp | 2 +- src/ModuleManager.cpp | 4 ++-- src/ModuleSection.h | 8 ++++---- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 0919339..9ae1412 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -155,7 +155,7 @@ Page { property QtObject section: { // find the section our icon represents. for (let s of modelData.sections) { - if (s.isActionTabItem) + if (s.isSendMethod) return s; } return null; // module doesn't have such a section. diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 1781a2f..878a181 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -190,7 +190,7 @@ Page { for (var mod of ModuleManager.registeredModules) { if (mod.id === "sendSweepModule") { for (var section of mod.sections) { - if (section.isActionTabItem) + if (section.isSendMethod) return section; } break; diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 9f9b5d6..6abe677 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -23,7 +23,7 @@ ModuleInfo * BuildTransactionModuleInfo::build() module->setTitle(tr("Create Transactions")); module->setDescription(tr("This module allows building more powerful transactions in one simple user interface.")); - auto sendButton = new ModuleSection(ModuleSection::ActionTabItem, module); + auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); sendButton->setText(tr("Build Transaction")); sendButton->setSubtext(tr("Manually select templates")); // notice that the directory it is in is registered in the data.qrc header diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index ac352ec..cf17323 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -28,7 +28,7 @@ ModuleInfo * ExampleModuleInfo::build() // A section is a definition on where in the application this module is able // to be plugged-in. - auto sendButtonExample = new ModuleSection(ModuleSection::ActionTabItem, info); + auto sendButtonExample = new ModuleSection(ModuleSection::SendMethod, info); sendButtonExample->setText(tr("Example Module")); sendButtonExample->setSubtext(tr("This is some helptext")); // notice that the directory it is in, is registered in the example-data.qrc file diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index a5775bb..3de4664 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -27,7 +27,7 @@ ModuleInfo * SendSweepModuleInfo::build() } SendSweepModuleInfo::SendSweepModuleInfo() - : m_actionTabItem(new ModuleSection(ModuleSection::ActionTabItem, this)) + : m_actionTabItem(new ModuleSection(ModuleSection::SendMethod, this)) { setId("sendSweepModule"); setTitle(tr("Sweep & Send")); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 8aba13f..c36d03b 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -108,7 +108,7 @@ bool ModuleInfo::hasUISections() const for (auto section : m_sections) { if (section->isMainMenuMethod()) return true; - if (section->isActionTabItem()) + if (section->isSendMethod()) return true; } return false; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index c7ac86d..d73cd3e 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -75,7 +75,7 @@ ModuleManager::ModuleManager(QObject *parent) for (auto *s : m->sections()) { connect (s, &ModuleSection::enabledChanged, this, [=]() { switch (s->type()) { - case ModuleSection::ActionTabItem: + case ModuleSection::SendMethod: emit sendMenuSectionsChanged(); case ModuleSection::MainMenuItem: emit mainMenuSectionsChanged(); @@ -242,7 +242,7 @@ QList ModuleManager::sendMenuSections() const QList answer; for (const auto *m : m_modules) { for (auto *s : m->sections()) { - if (s->enabled() && s->type() == ModuleSection::ActionTabItem) + if (s->enabled() && s->type() == ModuleSection::SendMethod) answer.append(s); } } diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 71e45aa..3ddc071 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -33,14 +33,14 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) - Q_PROPERTY(bool isActionTabItem READ isActionTabItem CONSTANT) + Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) Q_PROPERTY(bool isStartScreenType READ isStartScreenType CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: /// The placement in the main app of this section. enum SectionType { - ActionTabItem, ///< An list-item on the 'action' tab (aka send-tab). + SendMethod, ///< A specific way to send coin, shown in list of send-methods. MainMenuItem, ///< A text-button in the main menu StartScreenType, ///< A screen displayed on top of the main app at startup. CustomSectionType, ///< Not normally shown type, but fetchable by id. @@ -75,8 +75,8 @@ public: QString startQMLFile() const; SectionType type() const; - bool isActionTabItem() const { - return m_type == ActionTabItem; + bool isSendMethod() const { + return m_type == SendMethod; } bool isMainMenuMethod() const { return m_type == MainMenuItem; -- 2.54.0 From 33799f0c1fd4a014a5f4d2925f5145de6dfe87dc Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 17:23:27 +0100 Subject: [PATCH 1077/1428] Add new tab 'Explore'. Continuing the 'rework send page' series. This moves stuff that had no business being on the "Send" page to live on a new tab instead. Prime example was the 'sweep' module that creates a transaction we send, but to ourselves. So it's far fetched that it fits in 'send'. --- guis/mobile.qrc | 4 ++ guis/mobile/ExploreModules.qml | 18 +++++- guis/mobile/MainView.qml | 66 +++++++++------------- guis/mobile/images/exploretab-light.svg | 5 ++ guis/mobile/images/exploretab.svg | 5 ++ guis/mobile/images/more-light.svg | 7 +++ guis/mobile/images/more.svg | 7 +++ modules/peers-view/PeersViewModuleInfo.cpp | 13 +++-- modules/send-sweep/SendSweepModuleInfo.cpp | 2 +- src/ModuleInfo.cpp | 2 + src/ModuleManager.cpp | 22 ++++++++ src/ModuleManager.h | 7 +++ src/ModuleSection.h | 6 ++ 13 files changed, 119 insertions(+), 45 deletions(-) create mode 100644 guis/mobile/images/exploretab-light.svg create mode 100644 guis/mobile/images/exploretab.svg create mode 100644 guis/mobile/images/more-light.svg create mode 100644 guis/mobile/images/more.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6f6a02e..c44da70 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -40,10 +40,14 @@ mobile/images/num-keyboard.svg mobile/images/module-mainmenu.svg mobile/images/module-mainmenu-light.svg + mobile/images/exploretab.svg + mobile/images/exploretab-light.svg mobile/images/filter-light.svg mobile/images/hat.svg mobile/images/shrug.svg mobile/images/shrug-light.svg + mobile/images/more.svg + mobile/images/more-light.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 9ae1412..fad48f5 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -175,7 +175,23 @@ Page { if (s.isMainMenuMethod) return s; } - return null; // module doesn't have such a section. + return null; + } + } + Image { + source: "qrc:/exploretab" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 16 + height: width + anchors.verticalCenter: parent.verticalCenter + visible: section != null + opacity: (visible && section.enabled) ? 0.8 : 0.3 + property QtObject section: { + // find the section our icon represents. + for (let s of modelData.sections) { + if (s.isForExploreTab) + return s; + } + return null; } } } diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index f3556c9..956e066 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -39,8 +39,8 @@ MainViewBase { } FocusScope { id: apps - property string icon: "qrc:/apps" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - property string title: qsTr("Apps") + property string icon: "qrc:/exploretab" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string title: qsTr("Explore") anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 @@ -48,51 +48,39 @@ MainViewBase { Column { width: parent.width spacing: 10 - PageTitledBox { - width: parent.width - title: qsTr("Often Used Apps") - TextButton { - width: parent.width - text: qsTr("Sweep Paper Wallet") - showPageIcon: true - } - TextButton { - width: parent.width - text: qsTr("Peers View") - showPageIcon: true - } - } - PageTitledBox { - width: parent.width - title: "Apps" - TextButton { - text: qsTr("Make Wallet Backup") - showPageIcon: true - } - TextButton { - width: parent.width - text: qsTr("Block History") - showPageIcon: true - } - TextButton { - width: parent.width - text: qsTr("Price History") - showPageIcon: true - } - } PageTitledBox { width: parent.width - title: qsTr("Token Apps") - TextButton { - text: qsTr("Create NFT") - showPageIcon: true + title: qsTr("Often Used") + visible: content.length > 1 + Repeater { + model: ModuleManager.oftenUsedItems + TextButton { + text: modelData.text + subtext: modelData.subtext + showPageIcon: true + onClicked: thePile.push(modelData.qml) + } } + } + + Repeater { + model: ModuleManager.exploreTabItems TextButton { - text: qsTr("Create Airdrop") + text: modelData.text + subtext: modelData.subtext showPageIcon: true + onClicked: thePile.push(modelData.qml) } } + + TextButton { + width: parent.width + text: qsTr("Find More") + iconSource: "qrc:/more" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; + showPageIcon: true + onClicked: thePile.push("ExploreModules.qml") + } } } diff --git a/guis/mobile/images/exploretab-light.svg b/guis/mobile/images/exploretab-light.svg new file mode 100644 index 0000000..8897251 --- /dev/null +++ b/guis/mobile/images/exploretab-light.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/guis/mobile/images/exploretab.svg b/guis/mobile/images/exploretab.svg new file mode 100644 index 0000000..974cb9a --- /dev/null +++ b/guis/mobile/images/exploretab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/guis/mobile/images/more-light.svg b/guis/mobile/images/more-light.svg new file mode 100644 index 0000000..079297b --- /dev/null +++ b/guis/mobile/images/more-light.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/guis/mobile/images/more.svg b/guis/mobile/images/more.svg new file mode 100644 index 0000000..a5d4eb3 --- /dev/null +++ b/guis/mobile/images/more.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/modules/peers-view/PeersViewModuleInfo.cpp b/modules/peers-view/PeersViewModuleInfo.cpp index 5b538c1..9c52c98 100644 --- a/modules/peers-view/PeersViewModuleInfo.cpp +++ b/modules/peers-view/PeersViewModuleInfo.cpp @@ -26,10 +26,15 @@ ModuleInfo * PeersViewModuleInfo::build() "often called 'peers'.")); // info->setIconSource("qrc:/example/example.svg"); - auto menuExample = new ModuleSection(ModuleSection::MainMenuItem, info); - menuExample->setText(tr("Network Details")); - menuExample->setStartQMLFile("qrc:/peers-view/NetView.qml"); - info->addSection(menuExample); + auto menuOption = new ModuleSection(ModuleSection::MainMenuItem, info); + menuOption->setText(tr("Network Details")); + menuOption->setStartQMLFile("qrc:/peers-view/NetView.qml"); + info->addSection(menuOption); + + auto other = new ModuleSection(ModuleSection::OtherSectionType, info); + other->setText(menuOption->text()); + other->setStartQMLFile(menuOption->startQMLFile()); + info->addSection(other); return info; } diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index 3de4664..e7025a7 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -27,7 +27,7 @@ ModuleInfo * SendSweepModuleInfo::build() } SendSweepModuleInfo::SendSweepModuleInfo() - : m_actionTabItem(new ModuleSection(ModuleSection::SendMethod, this)) + : m_actionTabItem(new ModuleSection(ModuleSection::OtherSectionType, this)) { setId("sendSweepModule"); setTitle(tr("Sweep & Send")); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index c36d03b..a8aeafa 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -110,6 +110,8 @@ bool ModuleInfo::hasUISections() const return true; if (section->isSendMethod()) return true; + if (section->isForExploreTab()) + return true; } return false; } diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index d73cd3e..ede59bd 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -79,6 +79,8 @@ ModuleManager::ModuleManager(QObject *parent) emit sendMenuSectionsChanged(); case ModuleSection::MainMenuItem: emit mainMenuSectionsChanged(); + case ModuleSection::OtherSectionType: + emit exploreTabItemsChanged(); default: break; } @@ -261,6 +263,26 @@ QList ModuleManager::mainMenuSections() const return answer; } +QList ModuleManager::exploreTabItems() const +{ + QList answer; + for (const auto *m : m_modules) { + for (auto *s : m->sections()) { + if (s->enabled() && s->type() == ModuleSection::OtherSectionType) + answer.append(s); + } + } + return answer; +} + +QList ModuleManager::oftenUsedItems() const +{ + QList answer; + // TODO + return answer; +} + + ModuleSection *ModuleManager::sectionOnPlugin(const QString &pluginId, const QString §ionId) const { for (const auto *m : m_modules) { diff --git a/src/ModuleManager.h b/src/ModuleManager.h index 5664543..f5de030 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -39,6 +39,9 @@ class ModuleManager : public QObject * \see ModduleSection::SectionType */ Q_PROPERTY(QList mainMenuItems READ sendMenuSections NOTIFY mainMenuSectionsChanged) + + Q_PROPERTY(QList exploreTabItems READ exploreTabItems NOTIFY exploreTabItemsChanged) + Q_PROPERTY(QList oftenUsedItems READ oftenUsedItems NOTIFY oftenUsedItemsChanged) public: explicit ModuleManager(QObject *parent = nullptr); ~ModuleManager(); @@ -51,6 +54,8 @@ public: // lists per type QList sendMenuSections() const; QList mainMenuSections() const; + QList exploreTabItems() const; + QList oftenUsedItems() const; Q_INVOKABLE ModuleSection* sectionOnPlugin(const QString &pluginId, const QString §ionId) const; @@ -59,6 +64,8 @@ public: signals: void sendMenuSectionsChanged(); void mainMenuSectionsChanged(); + void oftenUsedItemsChanged(); + void exploreTabItemsChanged(); private: void load(); diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 3ddc071..5074398 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -36,6 +36,7 @@ class ModuleSection : public QObject Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) Q_PROPERTY(bool isStartScreenType READ isStartScreenType CONSTANT) + Q_PROPERTY(bool isForExploreTab READ isForExploreTab CONSTANT) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) public: /// The placement in the main app of this section. @@ -44,6 +45,7 @@ public: MainMenuItem, ///< A text-button in the main menu StartScreenType, ///< A screen displayed on top of the main app at startup. CustomSectionType, ///< Not normally shown type, but fetchable by id. + OtherSectionType, ///< Show this on the 'Explore' tab. // BuildTxComponent, ///< Inside the 'build-transactions' this adds a new buildingblock. }; @@ -85,6 +87,10 @@ public: return m_type == StartScreenType; } + bool isForExploreTab() const { + return m_type == OtherSectionType; + } + bool enabled() const; /** * This is set by the user or restored from the save file upon restart. -- 2.54.0 From 29fa685c098bba7d3fd2e5e7807b7c9b3edc169f Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 17:37:04 +0100 Subject: [PATCH 1078/1428] Fix isPayment property for QR scanner page --- guis/Flowee/QRScanner.qml | 84 -------------------------------------- guis/mobile/ScanQRPage.qml | 1 + src/CameraController.cpp | 3 +- src/CameraController.h | 4 +- src/QRScanner.cpp | 15 +++++-- src/QRScanner.h | 16 ++++---- 6 files changed, 22 insertions(+), 101 deletions(-) diff --git a/guis/Flowee/QRScanner.qml b/guis/Flowee/QRScanner.qml index 509708d..3f43963 100644 --- a/guis/Flowee/QRScanner.qml +++ b/guis/Flowee/QRScanner.qml @@ -108,86 +108,6 @@ FocusScope { } } -/* - Item { - id: pasteFrame - x: 50 - anchors.top: topBar.bottom - anchors.topMargin: 6 - width: pasteButton.width - height: pasteButton.height - - Rectangle { - // hiding this rect has a great effect in - // light mode to make paste look disabled. - color: palette.base - anchors.fill: parent - visible: cbh.text !== "" - radius: 6 - } - ImageButton { - id: pasteButton - source: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - text: qsTr("Paste") - opacity: { - // in dark mode the contrast is too great when the button - // should look disabled. So we lower it when needed. - if (Pay.useDarkSkin && cbh.text === "") - return 0.5; - return 1; - } - onClicked: { - var err = pasteFeedback.visible = !CameraController.pasteData(cbh.text); - pasteFeedback.visible = err; - if (err) - shaker.start(); - } - } - - ClipboardHelper { - id: cbh - filter: { - if (CameraController.isPayment) - return ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses | ClipboardHelper.AddressUrl - var type = CameraController.scanType; - if (type === QRScanner.PrivateKeyWIF) - return ClipboardHelper.PrivateKey; - if (type === QRScanner.SeedOrPrivKey) - return ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed; - return 1024; - } - enabled: CameraController.cameraActive - } - - Rectangle { - id: pasteFeedback - color: mainWindow.errorRedBg - border.color: palette.toolTipText - border.width: 2 - width: errorLabel.width + 10 - height: errorLabel.height + 10 - radius: 5 - anchors.top: pasteButton.bottom - visible: false - - Label { - id: errorLabel - anchors.centerIn: parent - text: qsTr("Failed") - } - Timer { - interval: 7000 - running: parent.visible - onTriggered: parent.visible = false - } - } - ObjectShaker { - id: shaker - target: pasteButton - } - } -*/ - Rectangle { id: flashFrame anchors.top: topBar.bottom @@ -241,10 +161,6 @@ FocusScope { if (!CameraController.isPayment) return bareText; - // slight hack, make this field be re-calculated on camera active change in order to ensure - // we have the latest limit (which doesn't have a signal of its own) - var dummy = CameraController.cameraActive; - let cur = portfolio.current; if (cur === null || !cur.allowInstaPay) return bareText; diff --git a/guis/mobile/ScanQRPage.qml b/guis/mobile/ScanQRPage.qml index 23f9042..705ce23 100644 --- a/guis/mobile/ScanQRPage.qml +++ b/guis/mobile/ScanQRPage.qml @@ -29,6 +29,7 @@ Page { QRScanner { id: scanner autostart: true + isPayment: true // well, thats the main intent anyway onFinished: { var type = scanType; if (type === QRScanner.InvalidType) { // scanning interrupted diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 8397e66..83bf913 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -565,8 +565,7 @@ bool CameraController::isPayment() const { if (d->scanRequest == nullptr) return false; - return d->scanRequest->scanType() == QRScanner::PaymentDetails - || d->scanRequest->scanType() == QRScanner::PaymentDetailsTestnet; + return d->scanRequest->isPayment(); } bool CameraController::torchEnabled() const diff --git a/src/CameraController.h b/src/CameraController.h index 0281a6d..e57a5a7 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -40,8 +40,7 @@ class CameraController : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) - /// if the QRScanner::ScanType is a payment. - Q_PROPERTY(bool isPayment READ isPayment NOTIFY isScanTypeChanged FINAL) + Q_PROPERTY(bool isPayment READ isPayment NOTIFY cameraActiveChanged FINAL) Q_PROPERTY(QRScanner::ScanType scanType READ scanType NOTIFY isScanTypeChanged FINAL) Q_PROPERTY(bool loadCamera READ loadCamera NOTIFY loadCameraChanged) Q_PROPERTY(bool cameraActive READ cameraActive NOTIFY cameraActiveChanged) @@ -73,6 +72,7 @@ public: bool loadCamera() const; bool cameraActive() const; bool visible() const; + // we open the camera with a high expectation of the QR being a payment link. bool isPayment() const; bool torchEnabled() const; void setTorchEnabled(bool on); diff --git a/src/QRScanner.cpp b/src/QRScanner.cpp index a1bc560..ef1cb6a 100644 --- a/src/QRScanner.cpp +++ b/src/QRScanner.cpp @@ -133,8 +133,15 @@ void QRScanner::setHelpText(const QString &newHelpText) emit helpTextChanged(); } -/* -QRScanner::ResultSource QRScanner::resultSource() const +void QRScanner::setIsPayment(bool isPayment) { - return m_resultSource; -}*/ + if (m_isPayment == isPayment) + return; + m_isPayment = isPayment; + emit isPaymentChanged(); +} + +bool QRScanner::isPayment() const +{ + return m_isPayment; +} diff --git a/src/QRScanner.h b/src/QRScanner.h index 78b29bb..291ac31 100644 --- a/src/QRScanner.h +++ b/src/QRScanner.h @@ -25,6 +25,7 @@ class QRScanner : public QObject { Q_OBJECT Q_PROPERTY(ScanType scanType READ scanType NOTIFY scanTypeChanged) + Q_PROPERTY(bool isPayment READ isPayment WRITE setIsPayment NOTIFY isPaymentChanged FINAL) Q_PROPERTY(QString scanResult READ scanResult NOTIFY scanResultChanged) Q_PROPERTY(bool autostart READ autostart WRITE setAutostart NOTIFY autostartChanged) Q_PROPERTY(bool isScanning READ isScanning NOTIFY isScanningChanged) @@ -49,13 +50,6 @@ public: ScanType scanType() const; void setScanType(ScanType type); -/* - enum ResultSource { - Camera, - Clipboard - }; - Q_ENUM(ResultSource) -*/ /// Notice that resultString may be empty if we didn't scan any valid QR void finishedScan(const QString &resultString, ScanType foundDataType); @@ -69,11 +63,13 @@ public: bool isScanning() const; void setIsScanning(bool now); - // ResultSource resultSource() const; - QString helpText() const; void setHelpText(const QString &newHelpText); + // we open the camera with a high expectation of the QR being a payment link. + void setIsPayment(bool isPayment); + bool isPayment() const; + private slots: void completed(); @@ -84,6 +80,7 @@ signals: void autostartChanged(); void isScanningChanged(); void helpTextChanged(); + void isPaymentChanged(); /** * This signal is emitted when autostart has been skipped. @@ -99,6 +96,7 @@ private: QString m_helpText; bool m_autostart = false; bool m_isScanning = false; + bool m_isPayment = false; }; #endif -- 2.54.0 From 3721573253fb693f8174d1abb8b2abae772520a1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 22:18:56 +0100 Subject: [PATCH 1079/1428] Make example module show up in the explore tab --- modules/example/ExampleModuleInfo.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/example/ExampleModuleInfo.cpp b/modules/example/ExampleModuleInfo.cpp index cf17323..4f9236e 100644 --- a/modules/example/ExampleModuleInfo.cpp +++ b/modules/example/ExampleModuleInfo.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,5 +42,11 @@ ModuleInfo * ExampleModuleInfo::build() menuExample->setStartQMLFile("qrc:/example/ExamplePage.qml"); info->addSection(menuExample); + auto last = new ModuleSection(ModuleSection::OtherSectionType, info); + last->setText(menuExample->text()); + last->setSubtext(menuExample->subtext()); + last->setStartQMLFile(menuExample->startQMLFile()); + info->addSection(last); + return info; } -- 2.54.0 From 3e2c9b957c7ad43165aaffde92cfced13413c173 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 22:44:33 +0100 Subject: [PATCH 1080/1428] Remove unneeded repetition of defintions --- src/main.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 716df76..bb7da44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,13 +76,6 @@ struct ECC_State // defined in qml_path_helper.cpp.in void handleLocalQml(QQmlApplicationEngine &engine); -// defined in the utils file -struct CommandLineParserData; -CommandLineParserData* createCLD(QGuiApplication &app); -void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld); -std::unique_ptr handleStaticChain(CommandLineParserData *cld); -void initLogger(CommandLineParserData *cld); -void setupCallbacks(UserIntent *pi); int main(int argc, char *argv[]) { -- 2.54.0 From e200766dda9bf7010cbc8c019f7614c7cca875f2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Dec 2024 22:44:38 +0100 Subject: [PATCH 1081/1428] Split camera and intent out of the handling pages In the send-sweep module as well as in the PayWithQR this removes the UserIntent handling and instead moves that to the main.qml exclusively. Additionally in the send-sweep module the camera work is split into its own page, like in the other parts of the app. This helps us avoiding hacks when we want the main functionality without the camera. This is the last of the series of reworks, we should have all former functionality working again. --- guis/mobile/PayWithQR.qml | 52 ---------------------- guis/mobile/SendTransactionsTab.qml | 1 + guis/mobile/StartupScreen.qml | 14 +----- guis/mobile/WorkflowStarter.js | 16 +++---- guis/mobile/main.qml | 17 ++++--- modules/send-sweep/SendPage.qml | 31 ------------- modules/send-sweep/SendSweepModuleInfo.cpp | 4 +- modules/send-sweep/StartScan.qml | 41 +++++++++++++++++ modules/send-sweep/send-sweep-data.qrc | 1 + src/UserIntent.cpp | 6 +-- src/UserIntent.h | 2 +- 11 files changed, 67 insertions(+), 118 deletions(-) create mode 100644 modules/send-sweep/StartScan.qml diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 295e703..81b448e 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -82,40 +82,6 @@ Page { } Item { // data -/* - QRScanner { - id: scanner - scanType: QRScanner.PaymentDetails - autostart: Intent.paymentUrl === "" - onFinished: { - var rc = scanResult - if (rc === "") { // scanning interrupted - thePile.pop(); - return; - } - // if the scanner got bypassed with a 'paste' then - // we don't allow instapay - if (resultSource === QRScanner.Clipboard) - payment.instaPay = false; - - // check if payment has been done from 'simple' payment-protocol - showTargetAddress.checked = payment.simpleAddressTarget; - - // Take the entire QR-url and let the Payment object parse it. - // this updates things like amount, comment and indeed address. - let success = payment.pasteTargetAddress(rc); - if (!success) - scannedUrlFaultyDialog.open(); - - // should the price be included in the QR code, don't show editing widgets. - root.allowEditAmount = payment.paymentAmount <= 0; - if (root.allowEditAmount) - priceInput.takeFocus(); - else - root.takeFocus(); - } - } -*/ Payment { id: payment account: portfolio.current @@ -123,24 +89,6 @@ Page { autoPrepare: true instaPay: true - Component.onCompleted: { - /* - The application can be started with a click on a payment link, - in that case the link gets made available in the following property - and we start a payment protocol with the value. - Afterwards we reset the property to avoid the next opening of this - screen repeating the payment. - */ - - var paymentProtcolUrl = Intent.paymentUrl; - if (paymentProtcolUrl !== "") { - scanner.autostart = false; - payment.targetAddress = paymentProtcolUrl; - Intent.paymentUrl = ""; - root.allowEditAmount = payment.paymentAmount <= 0; - } - } - // easier testing values (for when you don't have a camera) // ps. in case of testing, you want above instaPay: property => false!! // paymentAmount: 100000000 diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 3633b12..deb0de4 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -110,6 +110,7 @@ FocusScope { return; } else if (t === ClipboardHelper.PrivateKey) { +console.log("hahaha"); if (WorkflowStarter.startSweep(text)) return; } diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 878a181..6e34213 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -186,18 +186,8 @@ Page { width: button2Text.width + 40 color: "#178b3a" anchors.horizontalCenter: parent.horizontalCenter - property QtObject infoObject: { - for (var mod of ModuleManager.registeredModules) { - if (mod.id === "sendSweepModule") { - for (var section of mod.sections) { - if (section.isSendMethod) - return section; - } - break; - } - } - return null; - } + property QtObject infoObject: + ModuleManager.sectionOnPlugin("sendSweepModule", "main"); visible: infoObject !== null diff --git a/guis/mobile/WorkflowStarter.js b/guis/mobile/WorkflowStarter.js index 98a06a4..b6ed4f3 100644 --- a/guis/mobile/WorkflowStarter.js +++ b/guis/mobile/WorkflowStarter.js @@ -19,17 +19,11 @@ /// opens wif sweep page, or returns false if unavailable function startSweep(wif) { - for (var mod of ModuleManager.registeredModules) { - if (mod.id === "sendSweepModule") { - for (var section of mod.sections) { - if (section.isActionTabItem) { - thePile.replace(section.qml, { "secret": wif }, - QQC2.StackView.Immediate); - return true; - } - } - break; - } + var s = ModuleManager.sectionOnPlugin("sendSweepModule", "main"); + if (s) { + thePile.replace(s.qml, { "secret": wif }, + QQC2.StackView.Immediate); + return true; } return false; } diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index f85261a..38828ef 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -69,20 +69,24 @@ ApplicationWindow { Connections { target: Intent function onPaymentUrlChanged() { - if (Intent.paymentUrl != "") - thePile.pushSpecialPage("./PayWithQR.qml", true) + if (Intent.paymentUrl != "") { + let page = thePile.pushSpecialPage("./PayWithQR.qml", true) + page.secret = Intent.paymentUrl; + } } function onSweepKeyChanged() { if (Intent.sweepKey != "") { var s = ModuleManager.sectionOnPlugin("sendSweepModule", "main"); - if (s) - thePile.pushSpecialPage(s.qml, true); + if (s) { + let page = thePile.pushSpecialPage(s.qml, true); + page.secret = Intent.paymentUrl; + } } } function onStartPaymentScannerChanged() { if (Intent.startPaymentScanner) { Intent.startPaymentScanner = false; - thePile.pushSpecialPage("./PayWithQR.qml", false) + thePile.pushSpecialPage("./ScanQRPage.qml", false) } } } @@ -123,10 +127,11 @@ ApplicationWindow { pop(); } - push(item); + var newPage = push(item); tempPageIndex = depth; if (typeof(exitAfter) === "boolean") exitAfterPopTemp = exitAfter; + return newPage; } Keys.onPressed: (event)=> { diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index e790693..afebe61 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -30,37 +30,6 @@ Mobile.Page { property alias secret: sweeper.privKey Item { // data -/* - QRScanner { - id: scanner - autostart: Intent.sweepKey === "" - helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") - onFinished: { - var rc = scanResult - if (rc === "" // scanning interrupted - || scanType !== QRScanner.PrivateKeyWIF) { - thePile.pop(); - return; - } - root.forceActiveFocus(); - sweeper.privKey = rc; - } - onAutostartSkipped: { - /* - * This page can be opened as part of the Intent feature, - * if it has then we need to set the privatekey and at - * the same time clear out the property to prepare for - * next usage. - * / - var fromIntent = Intent.sweepKey - if (fromIntent !== "") { - Intent.sweepKey = ""; // prepare for next usage. - sweeper.privKey = fromIntent; - } - } - } -*/ - SweepHandler { id: sweeper account: pay.portfolio.current diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index e7025a7..e93e202 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -34,13 +34,13 @@ SendSweepModuleInfo::SendSweepModuleInfo() setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); m_actionTabItem->setText(tr("Sweep Paper Wallet")); - m_actionTabItem->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); + m_actionTabItem->setStartQMLFile("qrc:/send-sweep/StartScan.qml"); addSection(m_actionTabItem); // this one is used to have a full screen page at application start, // but we only enable this when the 'bch-wif' scheme was used to start Flowee Pay auto introScreenSection = new ModuleSection(ModuleSection::CustomSectionType, this); - introScreenSection->setStartQMLFile(m_actionTabItem->startQMLFile()); + introScreenSection->setStartQMLFile("qrc:/send-sweep/SendPage.qml"); introScreenSection->setSectionId("main"); addSection(introScreenSection); } diff --git a/modules/send-sweep/StartScan.qml b/modules/send-sweep/StartScan.qml new file mode 100644 index 0000000..1876cee --- /dev/null +++ b/modules/send-sweep/StartScan.qml @@ -0,0 +1,41 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import "../mobile" as Mobile; +import Flowee.org.pay; + +Mobile.Page { + Item { // data + QRScanner { + id: scanner + autostart: true + helpText: qsTr("Scan QR (WIF) to find funds", "Please note that WIF and QR are names") + onFinished: { + var rc = scanResult + if (rc === "" // scanning interrupted + || scanType !== QRScanner.PrivateKeyWIF) { + thePile.pop(); + return; + } + thePile.replace("SendPage.qml", { "secret": rc }, + QQC2.StackView.Immediate); + } + } + } +} diff --git a/modules/send-sweep/send-sweep-data.qrc b/modules/send-sweep/send-sweep-data.qrc index 3eb99a7..3ff341b 100644 --- a/modules/send-sweep/send-sweep-data.qrc +++ b/modules/send-sweep/send-sweep-data.qrc @@ -1,5 +1,6 @@ SendPage.qml + StartScan.qml diff --git a/src/UserIntent.cpp b/src/UserIntent.cpp index 5a36cad..e1ca0e9 100644 --- a/src/UserIntent.cpp +++ b/src/UserIntent.cpp @@ -65,11 +65,11 @@ QString UserIntent::sweepKey() const return m_sweepKey; } -void UserIntent::setSweepKey(const QString &newSweepAddress) +void UserIntent::setSweepKey(const QString &wif) { - if (m_sweepKey == newSweepAddress) + if (m_sweepKey == wif) return; - m_sweepKey = newSweepAddress; + m_sweepKey = wif; emit sweepKeyChanged(); } diff --git a/src/UserIntent.h b/src/UserIntent.h index d42f758..24ca217 100644 --- a/src/UserIntent.h +++ b/src/UserIntent.h @@ -24,7 +24,7 @@ public: void setPaymentUrl(const QString &newPaymentUrl); QString sweepKey() const; - void setSweepKey(const QString &newSweepAddress); + void setSweepKey(const QString &wif); bool startPaymentScanner() const; void setStartPaymentScanner(bool on); -- 2.54.0 From 86ced6f747cbfc4c7737f6dcdbce2130538b0b34 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 18:44:30 +0100 Subject: [PATCH 1082/1428] Avoid duplication, move to file. --- guis/mobile.qrc | 1 + guis/mobile/AccountsList.qml | 17 +---------- guis/mobile/SelectDefaultConfigButton.qml | 35 +++++++++++++++++++++++ guis/mobile/SendTransactionsTab.qml | 18 +----------- 4 files changed, 38 insertions(+), 33 deletions(-) create mode 100644 guis/mobile/SelectDefaultConfigButton.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c44da70..7c8f011 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -83,6 +83,7 @@ mobile/ReceiveTab.qml mobile/ScanQRPage.qml mobile/SelectDefaultAccountPage.qml + mobile/SelectDefaultConfigButton.qml mobile/SendTransactionsTab.qml mobile/SlideToApprove.qml mobile/StartupScreen.qml diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 97c8ff5..2c61167 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -54,22 +54,7 @@ Page { visible: !singleAccountSetup height: visible ? implicitHeight: 0 - TextButton { - showPageIcon: true - text: qsTr("Default Wallet") - visible: !portfolio.singleAccountSetup - subtext: { - for (let a of portfolio.rawAccounts) { - if (a.isPrimaryAccount) { - var defaultAccount = a.name; - break; - } - } - - qsTr("%1 is used on startup").arg(defaultAccount); - } - onClicked: thePile.push("./SelectDefaultAccountPage.qml"); - } + SelectDefaultConfigButton { } TextButton { text: Pay.privateMode ? qsTr("Exit Private Mode") : qsTr("Enter Private Mode") subtext: Pay.privateMode ? qsTr("Reveals wallets marked private") diff --git a/guis/mobile/SelectDefaultConfigButton.qml b/guis/mobile/SelectDefaultConfigButton.qml new file mode 100644 index 0000000..9baad21 --- /dev/null +++ b/guis/mobile/SelectDefaultConfigButton.qml @@ -0,0 +1,35 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +TextButton { + showPageIcon: true + text: qsTr("Default Wallet") + visible: !portfolio.singleAccountSetup + subtext: { + for (let a of portfolio.rawAccounts) { + if (a.isPrimaryAccount) { + var defaultAccount = a.name; + break; + } + } + + qsTr("%1 is used on startup").arg(defaultAccount); + } + onClicked: thePile.push("./SelectDefaultAccountPage.qml"); +} diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index deb0de4..ff7003b 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -70,23 +70,7 @@ FocusScope { width: parent.width title: qsTr("Options") - TextButton { - // TODO this is copied from AccountsList.qml, avoid duplication... - text: qsTr("Default Wallet") - showPageIcon: true - visible: !portfolio.singleAccountSetup - subtext: { - for (let a of portfolio.rawAccounts) { - if (a.isPrimaryAccount) { - var defaultAccount = a.name; - break; - } - } - - qsTr("%1 is used on startup").arg(defaultAccount); - } - onClicked: thePile.push("./SelectDefaultAccountPage.qml"); - } + SelectDefaultConfigButton { } InstaPayConfigButton { } } -- 2.54.0 From 1b1bf7164b7205f2ff76b05580b6ce429d889ce2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 18:51:55 +0100 Subject: [PATCH 1083/1428] Rename property and be consistent. The TextButton already had an 'image' and now got an 'icon' added. So having the fallback of the image have a name with icon in it was just confusing. --- guis/mobile/About.qml | 2 +- guis/mobile/AccountPageListItem.qml | 45 +---------------------- guis/mobile/GuiSettings.qml | 2 +- guis/mobile/InstaPayConfigButton.qml | 2 +- guis/mobile/LockApplication.qml | 17 --------- guis/mobile/MainView.qml | 6 +-- guis/mobile/MenuOverlay.qml | 4 +- guis/mobile/SelectDefaultConfigButton.qml | 2 +- guis/mobile/SendTransactionsTab.qml | 6 +-- guis/mobile/TextButton.qml | 6 +-- guis/mobile/TransactionInfoSmall.qml | 2 +- 11 files changed, 18 insertions(+), 76 deletions(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 313a2d1..91673ed 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -52,7 +52,7 @@ Page { TextButton { text: qsTr("Credits") subtext: qsTr("© 2020-2024 Tom Zander and contributors") - showPageIcon: true + pageButton: true onClicked: thePile.push(creditsPage) Component { diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 6305d41..2e1bc30 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -74,7 +74,7 @@ QQC2.Control { TextButton { text: qsTr("Backup information") - showPageIcon: true + pageButton: true Layout.fillWidth: true enabled: root.account.isDecrypted onClicked: thePile.push(root.account.isHDWallet ? hdBackupDetails : backupDetails); @@ -304,52 +304,11 @@ QQC2.Control { } } - /* - TextButton { - visible: !root.account.needsPinToOpen - showPageIcon: true - text: { - if (!root.account.needsPinToPay) - return qsTr("Enable Pin to Pay") - if (!root.account.needsPinToOpen) - return qsTr("Convert to Pin to Open") - return ""; // already fully encrypted - } - subtext: { - if (root.account.needsPinToPay) - return qsTr("Pin to Pay is enabled"); - return "Wallet is not protected"; - } - - onClicked: {} // TODO - } - - Rectangle { - id: decryptButton - height: decryptButtonText.height +20 - width: decryptButtonText.width + 30 - visible: !root.account.isDecrypted - radius: 5 - color: Pay.useDarkSkin ? "#c2cacc" : "#bcc3c5" - Text { - id: decryptButtonText - text: qsTr("Open Wallet") - anchors.centerIn: parent - color: "black" - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.ArrowCursor - // TODO Give opportunity to decrypt here. - } - } - */ - TextButton { text: qsTr("Addresses and keys") visible: root.account.isHDWallet enabled: !root.account.needsPinToOpen || root.account.isDecrypted - showPageIcon: true + pageButton: true Layout.fillWidth: true onClicked: thePile.push(backupDetails); } diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index a7d90a6..a7736c6 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -162,7 +162,7 @@ Page { TextButton { Layout.fillWidth: true text: qsTr("Change Currency (%1)").arg(Fiat.currencyName) - showPageIcon: true + pageButton: true onClicked: thePile.push("./CurrencySelector.qml") } diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index 54115e9..6167b3d 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -49,7 +49,7 @@ TextButton { return qsTr("Limit set to: %1").arg(Fiat.formattedPrice(limit)); } - showPageIcon: true + pageButton: true onClicked: { var newPage = thePile.push("./InstaPayConfigPage.qml") newPage.account = root.account; diff --git a/guis/mobile/LockApplication.qml b/guis/mobile/LockApplication.qml index bb7394b..5c4ee52 100644 --- a/guis/mobile/LockApplication.qml +++ b/guis/mobile/LockApplication.qml @@ -135,23 +135,6 @@ Page { Behavior on y { NumberAnimation { } } } } - - /* - PageTitledBox { - title: qsTr("Encrypt your wallet data") - width: parent.width - - TextButton { - text: qsTr("Require PIN to Pay on wallet") - subtext: qsTr("Secure against spending") - showPageIcon: true - } - TextButton { - text: qsTr("Require PIN to Open wallet") - subtext: qsTr("Secure against all usage") - showPageIcon: true - } - } */ } // the invisible drag handler is separate but on similar to the input widget. diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 956e066..793f449 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -58,7 +58,7 @@ MainViewBase { TextButton { text: modelData.text subtext: modelData.subtext - showPageIcon: true + pageButton: true onClicked: thePile.push(modelData.qml) } } @@ -69,7 +69,7 @@ MainViewBase { TextButton { text: modelData.text subtext: modelData.subtext - showPageIcon: true + pageButton: true onClicked: thePile.push(modelData.qml) } } @@ -78,7 +78,7 @@ MainViewBase { width: parent.width text: qsTr("Find More") iconSource: "qrc:/more" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; - showPageIcon: true + pageButton: true onClicked: thePile.push("ExploreModules.qml") } } diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 13e2bfa..ba09abd 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -178,7 +178,7 @@ Item { model: MenuModel TextButton { text: model.name - showPageIcon: true + pageButton: true onClicked: { var target = model.target if (target !== "") { @@ -310,7 +310,7 @@ Item { TextButton { id: addWalletButton text: qsTr("Add Wallet") - showPageIcon: true + pageButton: true anchors.left: horizontalBar.right anchors.leftMargin: 6 anchors.right: parent.right diff --git a/guis/mobile/SelectDefaultConfigButton.qml b/guis/mobile/SelectDefaultConfigButton.qml index 9baad21..41c680b 100644 --- a/guis/mobile/SelectDefaultConfigButton.qml +++ b/guis/mobile/SelectDefaultConfigButton.qml @@ -18,7 +18,7 @@ import QtQuick TextButton { - showPageIcon: true + pageButton: true text: qsTr("Default Wallet") visible: !portfolio.singleAccountSetup subtext: { diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index ff7003b..44bc74c 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -44,14 +44,14 @@ FocusScope { TextButton { text: qsTr("Scan QR") - showPageIcon: true + pageButton: true iconSource: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; onClicked: thePile.push("ScanQRPage.qml") } TextButton { text: qsTr("Paste") iconSource: "qrc:/edit-clipboard" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - showPageIcon: true + pageButton: true onClicked: thePile.push(pastePage); } @@ -60,7 +60,7 @@ FocusScope { TextButton { text: modelData.text subtext: modelData.subtext - showPageIcon: true + pageButton: true onClicked: thePile.push(modelData.qml) } } diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 04245f8..179652e 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -28,9 +28,9 @@ Item { property alias text: label.text property alias subtext: smallLabel.text /// When true, show an arrow indicating we open a new page on click - property bool showPageIcon: false // TODO rename + property bool pageButton: false - /// when showPageIcon is false, place an image on the right side instead + /// when pageButton is false, place an image on the right side instead property string imageSource: "" property int imageWidth: 20 property int imageHeight: 20 @@ -73,7 +73,7 @@ Item { Loader { sourceComponent: { - if (root.showPageIcon) + if (root.pageButton) return pageIcon; if (root.imageSource !== "") return genericImage; diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 55094e0..b58154c 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -141,7 +141,7 @@ ColumnLayout { TextButton { id: txDetailsButton text: qsTr("Transaction Details") - showPageIcon: true + pageButton: true onClicked: { var newItem = thePile.push("./TransactionDetails.qml") popupOverlay.close(); -- 2.54.0 From 2fff1ebcb4708837441ca860fe5156f5eff8754d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 19:03:25 +0100 Subject: [PATCH 1084/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 0cc3f95..2037ad5 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="33" android:versionName="2024.12.1"> diff --git a/src/main.cpp b/src/main.cpp index bb7da44..427b408 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.12.0"); + qapp.setApplicationVersion("2024.12.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 341be443bbc3224808e202169b57a83e0d36f7e3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 22:07:07 +0100 Subject: [PATCH 1085/1428] Avoid confusion --- guis/desktop/Transaction.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index c9b70f2..9a9e51a 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -85,12 +85,12 @@ Rectangle { font.pointSize: mainLabel.font.pointSize * 0.9 color: txRoot.isRejected ? (Pay.useDarkSkin ? "#ec2327" : "#b41214") : palette.windowText - Component.onCompleted: updateText() + Component.onCompleted: date.updateText() Timer { interval: 60000 running: !txRoot.isRejected repeat: true - onTriggered: parent.updateText() + onTriggered: date.updateText() } } -- 2.54.0 From 4559b68f9313dcc5847691447bdb5ea89fa8db11 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 22:26:54 +0100 Subject: [PATCH 1086/1428] Avoid duplication, create utils file This moves the heuristic to exist only once and avoids duplicating it. Additionally this increases the checks accuracy. Last, the 'moved' price in desktop now is white to indicate it is not a balance change. --- guis/Flowee/FiatTxInfo.qml | 13 ++---------- guis/Utils.js | 29 +++++++++++++++++++++++++++ guis/desktop.qrc | 1 + guis/desktop/Transaction.qml | 6 ++++-- guis/desktop/TransactionInfoSmall.qml | 8 ++------ guis/mobile.qrc | 1 + guis/mobile/AccountHistory.qml | 8 ++------ 7 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 guis/Utils.js diff --git a/guis/Flowee/FiatTxInfo.qml b/guis/Flowee/FiatTxInfo.qml index 62012a6..21dfe2c 100644 --- a/guis/Flowee/FiatTxInfo.qml +++ b/guis/Flowee/FiatTxInfo.qml @@ -18,23 +18,14 @@ import QtQuick import QtQuick.Layouts import "../Flowee" as Flowee +import "../Utils.js" as Utils GridLayout { id: root columns: 2 required property QtObject txInfo - // Is this transaction a 'move between addresses' tx. - // This is a heuristic and not available in the model, which is why its in the view. - property bool isMoved: { - if (txInfo === null) - return false; - if (txInfo.isCoinbase || txInfo.isFused - || txInfo.fundsIn === 0) - return false; - var amount = txInfo.fundsOut - txInfo.fundsIn - return amount < 0 && amount > -2500 // then the diff is likely just fees. - } + property bool isMoved: Utils.isMoved(txInfo) property double amountBch: { if (txInfo === null) return 0; diff --git a/guis/Utils.js b/guis/Utils.js new file mode 100644 index 0000000..36e6a24 --- /dev/null +++ b/guis/Utils.js @@ -0,0 +1,29 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// Is this transaction a 'move between addresses' tx. +// This is a heuristic and not available in the model, which is why its in the view. +function isMoved(txInfo) { + if (txInfo === null) + return false; + if (txInfo.isCoinbase || txInfo.isFused + || txInfo.fundsIn === 0 || txInfo.fundsOut === 0) + return false; + var diff = txInfo.fundsOut - txInfo.fundsIn + return diff < 0 && diff > -2500 // then the diff is likely just fees. +} diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 55ca1c0..d6db54d 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -17,6 +17,7 @@ desktop/defaults.ini desktop/ConfigItem.qml ControlColors.js + Utils.js desktop/main.qml desktop/AccountConfigMenu.qml desktop/AccountDetails.qml diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index 9a9e51a..f284495 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Controls as QQC2 import "../Flowee" as Flowee +import "../Utils.js" as Utils Rectangle { id: txRoot @@ -32,6 +33,7 @@ Rectangle { property int minedHeight: model.height property bool isRejected: minedHeight === -2 // -2 is the magic block-height indicating 'rejected' + property bool isMoved: Utils.isMoved(model); /* we have @@ -66,8 +68,7 @@ Rectangle { return qsTr("Fused") if (model.fundsIn === 0) return qsTr("Received") - let diff = model.fundsOut - model.fundsIn; - if (diff < 0 && diff > -1000) // then the diff is likely just fees. + if (isMoved) return qsTr("Moved"); return qsTr("Sent") } @@ -144,6 +145,7 @@ Rectangle { } anchors.top: mainLabel.top anchors.right: parent.right + colorize: !isMoved } Flowee.Label { anchors.top: mainLabel.top diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index 9f265fa..6e5b2b0 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -19,6 +19,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee +import "../Utils.js" as Utils Item { id: root @@ -28,12 +29,7 @@ Item { property QtObject infoObject: null property int minedHeight: model.height // local cache property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' - property bool isMoved: { - if (model.isCoinbase || model.isFused || model.fundsIn === 0) - return false; - var amount = model.fundsOut - model.fundsIn - return amount < 0 && amount > -2500 // then the diff is likely just fees. - } + property bool isMoved: Utils.isMoved(model); property double amountBch: isMoved ? model.fundsIn : (model.fundsOut - model.fundsIn) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 7c8f011..3128ebb 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -51,6 +51,7 @@ mobile/main.qml mobile/defaults.ini ControlColors.js + Utils.js mobile/WorkflowStarter.js mobile/About.qml mobile/AccountHistory.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index ad7dc05..beb0985 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Controls as QQC2 import "../Flowee" as Flowee +import "../Utils.js" as Utils import Flowee.org.pay; ListView { @@ -196,12 +197,7 @@ ListView { property var placementInGroup: model.placementInGroup // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. - property bool isMoved: { - if (model.isCoinbase || model.isFused || model.fundsIn === 0) - return false; - var amount = model.fundsOut - model.fundsIn - return amount < 0 && amount > -2500 // then the diff is likely just fees. - } + property bool isMoved: Utils.isMoved(model); width: root.width height: 80 -- 2.54.0 From 580faaa67ae1089f7950acba56f64d9a63e185ee Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 22:31:34 +0100 Subject: [PATCH 1087/1428] Don't show fees when not know. When the fees return zero, this is almost certainly due to us lacking the information to determine the fees paid. --- guis/desktop/TransactionDetails.qml | 2 +- guis/mobile/TransactionDetails.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 60d505f..fd9b75e 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -158,7 +158,7 @@ QQC2.ApplicationWindow { Flowee.Label { id: feesSection text: qsTr("Fees paid") + ":" - visible: txInfo.fees >= 0 + visible: txInfo.fees > 0 Layout.alignment: Qt.AlignRight } Flowee.Label { diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index 26b98f8..ccbf227 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -168,7 +168,7 @@ Page { // this account created. PageTitledBox { title: qsTr("Fees paid") - visible: infoObject != null && infoObject.fees >= 0 + visible: infoObject != null && infoObject.fees > 0 Flowee.Label { text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) -- 2.54.0 From a66352b6d1a3b650936f0ead835b7edb3bdec03e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 22:45:22 +0100 Subject: [PATCH 1088/1428] Avoid quick toggling of dark-mode The toggle on some devices takes a second or 2, as such it may be tempting to press multiple times waiting for the change. This hardcodes a 3 second wait to increase the UX a little. --- guis/mobile/MenuOverlay.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index ba09abd..8f8f282 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -98,9 +98,14 @@ Item { MouseArea { anchors.fill: parent anchors.margins: -10 + property date colorChanged: new Date(2000) onClicked: { + var now = new Date(); + if (now - colorChanged < 3 * 1000) + return; Pay.skinFollowsPlatform = false; Pay.useDarkSkin = !Pay.useDarkSkin; + colorChanged = now; } } } -- 2.54.0 From 3ba6d494ca6bea325e94119127ec028dc7438db6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 22:55:09 +0100 Subject: [PATCH 1089/1428] like mobile, hide network status when synched --- guis/desktop/main.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 403bf1e..18067aa 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -646,10 +646,13 @@ ApplicationWindow { width: balances.width Label { + id: netStatus text: qsTr("Network status") opacity: 0.6 + visible: isLoading || !portfolio.current.uptodate } Label { + visible: netStatus.visible id: syncIndicator text: { if (isLoading) @@ -666,6 +669,7 @@ ApplicationWindow { Item { // spacer width: 1 height: 20 + visible: netStatus.visible } Repeater { // the portfolio listing our accounts width: parent.width -- 2.54.0 From 7383f1eb3ce33243c10820e40cfca57897d8495a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 23:36:27 +0100 Subject: [PATCH 1090/1428] Add progress indicators on wallet list items. --- guis/desktop/AccountListItem.qml | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 66c0c99..f896ae2 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ Item { height: column.height + 18 signal clicked; + property int startPos: account.initialBlockHeight + //background Rectangle { property bool selected: portfolio.current === account @@ -44,6 +46,46 @@ Item { return Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue; return Pay.useDarkSkin ? "#EEE" : "#7b7f7f" } + + Rectangle { + height: parent.height + width: { + let startPos = root.startPos; + let end = Pay.expectedChainHeight + if (startPos == 0) + return 0; + let currentPos = root.account.lastBlockSynched; + let totalDistance = end - startPos; + let ourProgress = currentPos - startPos; + return parent.width * (ourProgress / totalDistance); + } + radius: parent.radius + border.width: parent.border.width + border.color: "#00000000" + color: mainWindow.floweeGreen + opacity: root.account.uptodate ? 0 : 0.2 + Behavior on opacity { NumberAnimation {} } + } + Rectangle { + height: parent.height + anchors.bottom: parent.bottom + width: { + let startPos = root.startPos; + let end = Pay.expectedChainHeight + if (startPos == 0) + return 0; + let currentPos = root.account.lastBlockSynched2; + let totalDistance = end - startPos; + let ourProgress = currentPos - startPos; + return parent.width * (ourProgress / totalDistance); + } + radius: parent.radius + border.width: parent.border.width + border.color: "#00000000" + color: mainWindow.floweeGreen + opacity: root.account.uptodate ? 0 : 0.2 + Behavior on opacity { NumberAnimation {} } + } } Column { -- 2.54.0 From 3c0e99ca87fe6841e4842d68c2e06a6f88be6020 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Dec 2024 23:39:09 +0100 Subject: [PATCH 1091/1428] remove commented out code --- guis/desktop/AccountListItem.qml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index f896ae2..3de8b81 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -94,11 +94,6 @@ Item { x: 20 y: 6 width: parent.width // - 13 - /* - Flowee.Label { - text: "Savings Account" - opacity: 0.6 - }*/ Flowee.Label { text: root.account.name font.bold: true -- 2.54.0 From 46c33bb4d1afc7a6d01599ee4d0acfc70fe02350 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Dec 2024 14:49:23 +0100 Subject: [PATCH 1092/1428] Correct QtCreator's handling of cmake. Move the added files to be in the list instead of in the call. --- modules/send-sweep/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/send-sweep/CMakeLists.txt b/modules/send-sweep/CMakeLists.txt index 4f2c657..119c1d0 100644 --- a/modules/send-sweep/CMakeLists.txt +++ b/modules/send-sweep/CMakeLists.txt @@ -19,8 +19,8 @@ project(send-sweep) set (SOURCES SendSweepModuleInfo.cpp QMLSweepHandler.cpp + TransactionsFetcher.cpp ) -add_library (send-sweep_module_lib STATIC ${SOURCES} - TransactionsFetcher.h TransactionsFetcher.cpp) +add_library (send-sweep_module_lib STATIC ${SOURCES}) target_link_libraries(send-sweep_module_lib pay_lib) -- 2.54.0 From 94da00709d17424764a0fe398c71b52a09b7e8b2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Dec 2024 15:09:20 +0100 Subject: [PATCH 1093/1428] Start new module --- .../big-transfer/BigTransferModuleInfo.cpp | 46 ++++++++++++++++ modules/big-transfer/BigTransferModuleInfo.h | 33 +++++++++++ modules/big-transfer/CMakeLists.txt | 25 +++++++++ modules/big-transfer/Main.qml | 55 +++++++++++++++++++ modules/big-transfer/QMLTransferManager.cpp | 28 ++++++++++ modules/big-transfer/QMLTransferManager.h | 37 +++++++++++++ modules/big-transfer/README.md | 14 +++++ modules/big-transfer/transfer-all-data.qrc | 5 ++ 8 files changed, 243 insertions(+) create mode 100644 modules/big-transfer/BigTransferModuleInfo.cpp create mode 100644 modules/big-transfer/BigTransferModuleInfo.h create mode 100644 modules/big-transfer/CMakeLists.txt create mode 100644 modules/big-transfer/Main.qml create mode 100644 modules/big-transfer/QMLTransferManager.cpp create mode 100644 modules/big-transfer/QMLTransferManager.h create mode 100644 modules/big-transfer/README.md create mode 100644 modules/big-transfer/transfer-all-data.qrc diff --git a/modules/big-transfer/BigTransferModuleInfo.cpp b/modules/big-transfer/BigTransferModuleInfo.cpp new file mode 100644 index 0000000..66757c2 --- /dev/null +++ b/modules/big-transfer/BigTransferModuleInfo.cpp @@ -0,0 +1,46 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "BigTransferModuleInfo.h" +#include "QMLTransferManager.h" + +#include // for the qmlRegisterType + +ModuleInfo * BigTransferModuleInfo::build() +{ + qmlRegisterType("Flowee.org.pay.bigtransfer", 1, 0, "TransferManager"); + return new BigTransferModuleInfo(); +} + +BigTransferModuleInfo::BigTransferModuleInfo() +{ + setId("bigTransfer"); + setTitle(tr("Transfer All")); + setDescription(tr("Move all coins to another wallet, with one transaction per coin to preserve anonimity.")); + + ModuleSection *sendTab = new ModuleSection(ModuleSection::SendMethod, this); + sendTab->setText(tr("Transfer All Coins")); + sendTab->setSubtext(tr("Move coins to another wallet")); + sendTab->setStartQMLFile("qrc:/bigtransfer/Main.qml"); + addSection(sendTab); + + ModuleSection *app = new ModuleSection(ModuleSection::OtherSectionType, this); + app->setText(tr("Transfer All")); + app->setSubtext(sendTab->subtext()); + app->setStartQMLFile(sendTab->startQMLFile()); + addSection(app); +} diff --git a/modules/big-transfer/BigTransferModuleInfo.h b/modules/big-transfer/BigTransferModuleInfo.h new file mode 100644 index 0000000..e73164a --- /dev/null +++ b/modules/big-transfer/BigTransferModuleInfo.h @@ -0,0 +1,33 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class BigTransferModuleInfo : public ModuleInfo +{ + Q_OBJECT +public: + static ModuleInfo *build(); + + BigTransferModuleInfo(); + + static const char *translationUnit() { + return "module-transfer-all"; + } +}; diff --git a/modules/big-transfer/CMakeLists.txt b/modules/big-transfer/CMakeLists.txt new file mode 100644 index 0000000..7fd0d16 --- /dev/null +++ b/modules/big-transfer/CMakeLists.txt @@ -0,0 +1,25 @@ +# This file is part of the Flowee project +# Copyright (C) 2024 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(bigtransfer) + +set (SOURCES + BigTransferModuleInfo.cpp + QMLTransferManager.cpp +) +add_library (big-transfer_module_lib STATIC ${SOURCES}) +target_link_libraries(big-transfer_module_lib pay_lib) + diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml new file mode 100644 index 0000000..ac53485 --- /dev/null +++ b/modules/big-transfer/Main.qml @@ -0,0 +1,55 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import "../mobile" as Mobile; +import Flowee.org.pay; +import Flowee.org.pay.bigtransfer; + +Mobile.Page { + id: root + headerText: qsTr("Transfer All") + + + property QtObject initialWallet: portfolio.current + Item { + // data + TransferManager { + id: bigTransfer + } + } + + Column { + anchors.fill: parent + spacing: 10 + + Mobile.PageTitledBox { + title: qsTr("Start Wallet") + // Wallet selector. + // Amount of addresses in use. + // Amount of fused coins. + + } + Mobile.PageTitledBox { + title: qsTr("Destination Wallet") + // wallet selector, with an entry "create new HD wallet" + } + + // button to start. + } +} diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp new file mode 100644 index 0000000..a5f2cdf --- /dev/null +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -0,0 +1,28 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "QMLTransferManager.h" + + +QMLTransferManager::QMLTransferManager(QObject *parent) + : QObject(parent) +{ +} + +void QMLTransferManager::start() +{ +} diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h new file mode 100644 index 0000000..1c15d0b --- /dev/null +++ b/modules/big-transfer/QMLTransferManager.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TRANSFERMANAGER_H +#define TRANSFERMANAGER_H + +#include + +class QMLTransferManager : public QObject +{ + Q_OBJECT +public: + QMLTransferManager(QObject *parent = nullptr); + +private slots: + void start(); + +private: + AccountInfo *m_fromAccount = nullptr; + AccountInfo *m_toAccount = nullptr; +}; + +#endif diff --git a/modules/big-transfer/README.md b/modules/big-transfer/README.md new file mode 100644 index 0000000..f9442e6 --- /dev/null +++ b/modules/big-transfer/README.md @@ -0,0 +1,14 @@ +This module provides a screen that allows a wallet to have most or all +of its content transferred to another wallet, without giving up anonimity. + +If you have a large number of coins in a wallet then the normal "send all" +method will simply merge all coins into one transaction. The downside of +creating one transaction is that it indicates to anyone checking the +blockchain that all those addresses are owned by the same person. Thereby +giving up any anonimity those addresses would have provided. + +This module instead creates a transaction per address that is in use in +the old wallet and picks a fresh address from the target wallet in order +to avoid the obvious joining that would give away our anonimity. + + diff --git a/modules/big-transfer/transfer-all-data.qrc b/modules/big-transfer/transfer-all-data.qrc new file mode 100644 index 0000000..280fde2 --- /dev/null +++ b/modules/big-transfer/transfer-all-data.qrc @@ -0,0 +1,5 @@ + + + Main.qml + + -- 2.54.0 From 533a20c769b3515b896b017952bc98f70518bae4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 26 Dec 2024 18:42:46 +0100 Subject: [PATCH 1094/1428] Follow property rename in Flowee qml widget. --- modules/build-transaction/PayToOthers.qml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 88284dc..97f848a 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -611,16 +611,14 @@ Page { TextButton { text: qsTr("Add Destination") - showPageIcon: true + pageButton: true onClicked: pushToThePile(destinationEditPage, payment.addExtraOutput()); } -/* - TextButton { + /*TextButton { text: qsTr("Add Detail...") - showPageIcon: true + pageButton: true onClicked: thePile.push(paymentDetailSelector); - } -*/ + }*/ Flowee.Button { property bool walletNeedsDecryptFirst: !payment.account.isDecrypted && payment.account.needsPinToPay; text: walletNeedsDecryptFirst ? qsTr("Unlock Wallet") : qsTr("Prepare Payment...") -- 2.54.0 From 453cfa51e220ef683d4406df7d849ebe46dd5135 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 26 Dec 2024 19:14:17 +0100 Subject: [PATCH 1095/1428] Add comment. --- guis/mobile/AccountSelectorPopup.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 447cb79..34cdfbe 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -20,6 +20,7 @@ import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee +// see also AccountSelectorWidget.qml QQC2.Popup { id: root closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent -- 2.54.0 From 3ccd90cc4b0716a33729c7e1547b2ae9b01b8eba Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 26 Dec 2024 23:27:24 +0100 Subject: [PATCH 1096/1428] Add most of the workflow to the bigtransfer module We don't actually send any transactions yet. --- modules/big-transfer/Main.qml | 79 +++++-- modules/big-transfer/QMLTransferManager.cpp | 249 +++++++++++++++++++- modules/big-transfer/QMLTransferManager.h | 80 ++++++- modules/big-transfer/ShowPrepared.qml | 90 +++++++ modules/big-transfer/transfer-all-data.qrc | 1 + src/Wallet.cpp | 19 ++ src/Wallet.h | 6 + src/Wallet_support.cpp | 20 +- 8 files changed, 515 insertions(+), 29 deletions(-) create mode 100644 modules/big-transfer/ShowPrepared.qml diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml index ac53485..795b81a 100644 --- a/modules/big-transfer/Main.qml +++ b/modules/big-transfer/Main.qml @@ -15,41 +15,82 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick; +import QtQuick.Controls as QQC2; +import QtQuick.Layouts; import "../mobile" as Mobile; +import "../Flowee" as Flowee; import Flowee.org.pay; import Flowee.org.pay.bigtransfer; Mobile.Page { id: root headerText: qsTr("Transfer All") + property alias initialWallet: fromWalletSelector.startingAccount - - property QtObject initialWallet: portfolio.current - Item { - // data - TransferManager { - id: bigTransfer - } - } - - Column { - anchors.fill: parent + ColumnLayout { + width: parent.width spacing: 10 - Mobile.PageTitledBox { - title: qsTr("Start Wallet") - // Wallet selector. - // Amount of addresses in use. - // Amount of fused coins. + Flowee.Label { + Layout.fillWidth: true + wrapMode: Text.WordWrap + text: qsTr("Move all coins to another wallet, with one transaction per coin to preserve anonimity.") + } + Mobile.PageTitledBox { + title: qsTr("Starting Wallet") + + // TODO Popup with just the name + Mobile.AccountSelectorWidget { + id: fromWalletSelector + onSelectedAccountChanged: transferManager.fromAccount = selectedAccount; + } + GridLayout { + columns: 2 + columnSpacing: 10 + rowSpacing: 10 + Flowee.Label { + text: qsTr("Addresses") + ":" + } + Flowee.Label { + text: transferManager.addressCount + } + Flowee.Label { + text: qsTr("Coins") + ":" + } + Flowee.Label { + text: transferManager.coinCount + } + } } Mobile.PageTitledBox { title: qsTr("Destination Wallet") - // wallet selector, with an entry "create new HD wallet" + // TODO Popup with just the name + // TODO add an entry "create new HD wallet" + Mobile.AccountSelectorWidget { + startingAccount: null + onSelectedAccountChanged: transferManager.toAccount = selectedAccount; + } } // button to start. + Flowee.Button { + Layout.alignment: Qt.AlignRight + text: qsTr("Prepare...") + + enabled: transferManager.allOk + onClicked: { + transferManager.prepare(); + thePile.push("ShowPrepared.qml", { "transferManager" : transferManager } ); + } + } + } + + Item { + // data + TransferManager { + id: transferManager + } } } diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index a5f2cdf..5794879 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -16,6 +16,11 @@ * along with this program. If not, see . */ #include "QMLTransferManager.h" +#include "FloweePay.h" + +#include +#include +#include QMLTransferManager::QMLTransferManager(QObject *parent) @@ -23,6 +28,248 @@ QMLTransferManager::QMLTransferManager(QObject *parent) { } -void QMLTransferManager::start() +void QMLTransferManager::prepare() +{ + if (m_prepareRunning) + return; + assert(m_fromAccount); + assert(m_toAccount); + assert(m_fromAccount != m_toAccount); + m_prepareRunning = true; + freeReservations(); // also clears the transactions list. + + // the following may take a bit longer if the wallet is large. + // do this a tad later in order to avoid the button press not + // seeming to do anything. + const Wallet *wIn = m_fromAccount->wallet(); + Wallet *wTo = m_toAccount->wallet(); + QTimer::singleShot(100, [=]() { + // we create one transaction for each address (aka private key). + // Regardless how many inputs this creates in a transaction. + const auto walletSecrets = wIn->walletSecrets(); + for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) { + const auto utxos = wIn->unspentOutputsForKey(i->first); + bool foundOne = false; + for (const auto &utxo : utxos) { + // NOTICE in future we may want to support token moving as well. + if (wIn->txOutput(utxo).hasCashToken == false) { + foundOne = true; + break; + } + } + + if (foundOne) { + PreviewTx *prev = new PreviewTx(this); + prev->m_utxos = utxos; + for (const auto &utxo : utxos) { + prev->m_value += wIn->utxoOutputValue(utxo); + } + const auto &fromSecret = i->second; + prev->m_from = new Address(renderAddress(fromSecret.address), + fromSecret.cloakedAddress(), prev); + + int reservation = wTo->reserveUnusedAddress(prev->m_toP2PKH, Wallet::ChangePath); + assert(!m_reservations.contains(reservation)); + m_reservations.insert(reservation); + + const auto toSecrets = wTo->walletSecrets(); + auto outSecret = toSecrets.find(reservation); + assert(outSecret != toSecrets.end()); + assert(prev->m_toP2PKH == outSecret->second.address); + prev->m_to = new Address(renderAddress(prev->m_toP2PKH), + outSecret->second.cloakedAddress(), prev); + m_transactions.append(prev); + } + } + + transactionsChanged(); + m_prepareRunning = false; + }); +} + +void QMLTransferManager::send() +{ + assert(m_fromAccount); + assert(m_toAccount); + const Wallet *wIn = m_fromAccount->wallet(); + + // create each transaction for real. + for (auto tx_ : m_transactions) { + auto *tx = qobject_cast(tx_); + TransactionBuilder builder; + builder.setFeeTarget(1000); + builder.setAnonimize(true); + uint64_t value = 0; + for (auto ref : tx->m_utxos) { + auto output = wIn->txOutput(ref); + if (output.hasCashToken) + continue; + value += output.outputValue; + builder.appendInput(wIn->txid(ref), ref.outputIndex()); + auto priv = wIn->unlockKey(ref); + if (priv.sigType == Wallet::NotUsedYet) + priv.sigType = Wallet::SignedAsSchnorr; + TransactionBuilder::SignatureType typeToUse = + (priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr; + assert(priv.key.isValid()); + builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); + } + + builder.appendOutput(value); + builder.pushOutputPay2Address(tx->m_toP2PKH); + builder.setOutputFeeSource(0); + + auto file = builder.createTransaction(); + // TODO send + } +} + +int QMLTransferManager::coinCount() const +{ + return m_coinCount; +} + +bool QMLTransferManager::allOk() const +{ + if (m_addressCount == 0) + return false; + if (m_fromAccount == nullptr) + return false; + if (m_toAccount == nullptr) + return false; + return true; +} + +void QMLTransferManager::setCoinCount(int c) +{ + if (m_coinCount == c) + return; + m_coinCount = c; + emit coinCountChanged(); +} + +// we made reservations on the target account, if we don't +// use them we need to free them. +void QMLTransferManager::freeReservations() +{ + if (m_toAccount == nullptr) + return; + auto *wallet = m_toAccount->wallet(); + for (int privId : m_reservations) { + wallet->unreserveAddress(privId); + } + m_reservations.clear(); + qDeleteAll(m_transactions); + m_transactions.clear(); + emit transactionsChanged(); +} + +QList QMLTransferManager::transactions() const +{ + return m_transactions; +} + +int QMLTransferManager::addressCount() const +{ + return m_addressCount; +} + +void QMLTransferManager::setAddressCount(int c) +{ + if (m_addressCount == c) + return; + m_addressCount = c; + emit addressCountChanged(); +} + +AccountInfo *QMLTransferManager::toAccount() const +{ + return m_toAccount; +} + +void QMLTransferManager::setToAccount(AccountInfo *newToAccount) +{ + if (m_toAccount == newToAccount) + return; + freeReservations(); + m_toAccount = newToAccount; + emit toAccountChanged(); + emit allOkChanged(); +} + +AccountInfo *QMLTransferManager::fromAccount() const +{ + return m_fromAccount; +} + +void QMLTransferManager::setFromAccount(AccountInfo *account) +{ + if (m_fromAccount == account) + return; + m_fromAccount = account; + emit fromAccountChanged(); + int addresses = 0; + int totalCoins = 0; + if (account->wallet()) { + const Wallet *wallet = account->wallet(); + const auto walletSecrets = wallet->walletSecrets(); + for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) { + auto details = wallet->fetchKeyDetails(i->first); + if (details.coins > 0) { + ++addresses; + totalCoins += details.coins; + } + } + } + setAddressCount(addresses); + setCoinCount(totalCoins); + emit allOkChanged(); +} + + +// ----------------------------------------------------------- + +Address::Address(const QString &address, const QString &cloaked, QObject *parent) + : QObject(parent), + m_address(address), + m_cloaked(cloaked) { } + +QString Address::address() const +{ + return m_address; +} + +QString Address::cloakedAddress() const +{ + return m_cloaked; +} + + +// ----------------------------------------------------------- + +PreviewTx::PreviewTx(QObject *parent) + : QObject(parent) +{ +} + +int64_t PreviewTx::value() const +{ + return m_value; +} + +QObject *PreviewTx::from() const +{ + return m_from; +} + +QObject *PreviewTx::to() const +{ + return m_to; +} + +int PreviewTx::inputCount() const +{ + return m_utxos.size(); +} diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h index 1c15d0b..6e36f3e 100644 --- a/modules/big-transfer/QMLTransferManager.h +++ b/modules/big-transfer/QMLTransferManager.h @@ -19,19 +19,95 @@ #define TRANSFERMANAGER_H #include +#include class QMLTransferManager : public QObject { Q_OBJECT + Q_PROPERTY(AccountInfo *fromAccount READ fromAccount WRITE setFromAccount NOTIFY fromAccountChanged FINAL) + Q_PROPERTY(AccountInfo *toAccount READ toAccount WRITE setToAccount NOTIFY toAccountChanged FINAL) + Q_PROPERTY(QList transactions READ transactions NOTIFY transactionsChanged FINAL) + Q_PROPERTY(int addressCount READ addressCount NOTIFY addressCountChanged FINAL) + Q_PROPERTY(int coinCount READ coinCount NOTIFY coinCountChanged FINAL) + Q_PROPERTY(bool allOk READ allOk NOTIFY allOkChanged FINAL) public: QMLTransferManager(QObject *parent = nullptr); -private slots: - void start(); + AccountInfo *fromAccount() const; + void setFromAccount(AccountInfo *newFromAccount); + + AccountInfo *toAccount() const; + void setToAccount(AccountInfo *newToAccount); + + // num of addresss on the 'from' account + int addressCount() const; + // total num of coins on the 'from' account + int coinCount() const; + + bool allOk() const; + + Q_INVOKABLE void prepare(); + Q_INVOKABLE void send(); + + QList transactions() const; + +signals: + void fromAccountChanged(); + void toAccountChanged(); + void addressCountChanged(); + void coinCountChanged(); + void allOkChanged(); + void transactionsChanged(); private: + void setAddressCount(int newAddressCount); + void setCoinCount(int newCoinCount); + void freeReservations(); + AccountInfo *m_fromAccount = nullptr; AccountInfo *m_toAccount = nullptr; + int m_addressCount = 0; + int m_coinCount = 0; + bool m_prepareRunning = false; + QList m_transactions; // PreviewTx instances + QSet m_reservations; +}; + +class Address : public QObject { + Q_OBJECT + Q_PROPERTY(QString address READ address CONSTANT FINAL) + Q_PROPERTY(QString cloakedAddress READ cloakedAddress CONSTANT FINAL) +public: + explicit Address(const QString &address, const QString &cloaked, QObject *parent); + + QString address() const; + QString cloakedAddress() const; + +private: + QString m_address; + QString m_cloaked; +}; + +class PreviewTx : public QObject { + Q_OBJECT + Q_PROPERTY(int64_t value READ value CONSTANT FINAL) + Q_PROPERTY(int inputCount READ inputCount CONSTANT FINAL) + Q_PROPERTY(QObject* from READ from CONSTANT FINAL) + Q_PROPERTY(QObject* to READ to CONSTANT FINAL) +public: + PreviewTx(QObject *parent = nullptr); + + int64_t value() const; + QObject *from() const; + QObject *to() const; + int inputCount() const; + + int64_t m_value = 0; + Address *m_from = nullptr; + Address *m_to = nullptr; + + std::vector m_utxos; + KeyId m_toP2PKH; }; #endif diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml new file mode 100644 index 0000000..207ce85 --- /dev/null +++ b/modules/big-transfer/ShowPrepared.qml @@ -0,0 +1,90 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick; +import QtQuick.Controls as QQC2; +import QtQuick.Layouts; +import "../mobile" as Mobile; +import "../Flowee" as Flowee; +import Flowee.org.pay; +import Flowee.org.pay.bigtransfer; + +Mobile.Page { + id: root + required property QtObject transferManager; + headerText: qsTr("Verify %1 Transactions", "", transferManager.transactions.length).arg(transferManager.transactions.length); + + ListView { + width: parent.width + anchors.top: parent.top + anchors.bottom: slideToApprove.top + anchors.bottomMargin: 10 + clip: true + model: root.transferManager.transactions + delegate: Rectangle { + width: ListView.view.width + property bool tall: indexLabel.width + 6 + fromLabel.width + 10 + + pointerLabel.width + 10 + toLabel.width > width; + height: { + var h = 5 + fromLabel.height + 5; + if (tall) + h += pointerLabel.height + toLabel.height; + return h; + } + color: (index % 2) === 0 ? palette.base : palette.alternateBase + Flowee.Label { + id: indexLabel + text: index + 1 + y: 5 + } + Flowee.AddressLabel { + id: fromLabel + y: 5 + x: indexLabel.width + 6 + width: Math.min(implicitWidth, parent.width - x) + txInfo: model.from + highlight: false + } + + Flowee.Label { + id: pointerLabel + text: "🠮" + x: parent.tall ? parent.width / 3 : (fromLabel.x + fromLabel.width + 10); + y: parent.tall ? fromLabel.height + 11 : 5 + } + + Flowee.AddressLabel { + id: toLabel + width: Math.min(implicitWidth, parent.width) + txInfo: model.to + highlight: false + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + } + } + } + + Mobile.SlideToApprove { + id: slideToApprove + onActivated: root.transferManager.send(); + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottomMargin: 10 + } +} diff --git a/modules/big-transfer/transfer-all-data.qrc b/modules/big-transfer/transfer-all-data.qrc index 280fde2..4d4ced5 100644 --- a/modules/big-transfer/transfer-all-data.qrc +++ b/modules/big-transfer/transfer-all-data.qrc @@ -1,5 +1,6 @@ Main.qml + ShowPrepared.qml diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 52e9bbf..948e209 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -786,6 +786,25 @@ int Wallet::findPrivKeyId(const KeyId &address) const return -1; } +std::vector Wallet::unspentOutputsForKey(int privKeyId) const +{ + std::vector list; + QMutexLocker locker(&m_lock); + for (const auto &row : m_walletTransactions) { + const WalletTransaction &wt = row.second; + for (auto i = wt.outputs.begin(); i != wt.outputs.end(); ++i) { + if (i->second.walletSecretId == privKeyId) { + OutputRef ref(row.first, i->first); + if (m_unspentOutputs.find(ref.encoded()) != m_unspentOutputs.end()) { + list.push_back(ref); + } + } + } + } + + return list; +} + bool Wallet::isHDWallet() const { return m_hdData.get() != nullptr; diff --git a/src/Wallet.h b/src/Wallet.h index fdb87da..2d799db 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -323,6 +323,8 @@ public: bool reserved = false; // in-mem-only void setPrivKey(const Streaming::ConstBuffer &data); + + QString cloakedAddress() const; }; /// Return the private keys and details owned by this wallet. @@ -335,6 +337,10 @@ public: }; KeyDetails fetchKeyDetails(int privKeyId) const; int findPrivKeyId(const KeyId &address) const; + /** + * Return all the utxo's by ref that would be unlocked with the argument private key id. + */ + std::vector unspentOutputsForKey(int privKeyId) const; /// Returns true if this wallet is backed by a Hierarchically Deterministic seed. bool isHDWallet() const; diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 8230426..392d841 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -140,6 +140,18 @@ bool Wallet::WalletTransaction::isRejected() const // ////////////////////////////////////////////////// +QString Wallet::WalletSecret::cloakedAddress() const +{ + if (fromChangeChain) { + assert(fromHdWallet); + return tr("Change #%1").arg(hdDerivationIndex); + } + else if (fromHdWallet) { + return tr("Main #%1").arg(hdDerivationIndex); + } + return QString(); // no cloak available. +} + void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) { assert(info); @@ -205,13 +217,7 @@ void Wallet::fetchTransactionInfo(TransactionInfo *info, int txIndex) assert(secretIter != m_walletSecrets.end()); const auto &secret = secretIter->second; in->setAddress(renderAddress(secret.address)); - if (secret.fromChangeChain) { - assert(secret.fromHdWallet); - in->setCloakedAddress(tr("Change #%1").arg(secret.hdDerivationIndex)); - } - else if (secret.fromHdWallet) { - in->setCloakedAddress(tr("Main #%1").arg(secret.hdDerivationIndex)); - } + in->setCloakedAddress(secret.cloakedAddress()); in->setFromFused(w->second.isCashFusionTx); info->m_inputs[pair.first] = in; } -- 2.54.0 From a18b5de86e81e0eb116a71b6f80320077cd288d2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Dec 2024 19:12:27 +0100 Subject: [PATCH 1097/1428] Add missing copyright header --- guis/Flowee/ProgressCheckIcon.qml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml index 570f49c..2ba8092 100644 --- a/guis/Flowee/ProgressCheckIcon.qml +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -1,3 +1,20 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import QtQuick import QtQuick.Shapes -- 2.54.0 From 8af9c8eac72680b4adb18024b5c32950d6d691bf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Dec 2024 19:13:22 +0100 Subject: [PATCH 1098/1428] Make functional and not too bad looking This is probably a good first milestone, the UX is pretty decent and the wanted functionality is there. Various todo's are still there which mostly of the cleanup type. --- modules/big-transfer/Main.qml | 4 +- modules/big-transfer/QMLTransferManager.cpp | 127 ++++++++---- modules/big-transfer/QMLTransferManager.h | 24 ++- modules/big-transfer/ShowPrepared.qml | 216 ++++++++++++++++---- 4 files changed, 292 insertions(+), 79 deletions(-) diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml index 795b81a..885c3ae 100644 --- a/modules/big-transfer/Main.qml +++ b/modules/big-transfer/Main.qml @@ -40,7 +40,7 @@ Mobile.Page { Mobile.PageTitledBox { title: qsTr("Starting Wallet") - + // TODO Popup with just the name Mobile.AccountSelectorWidget { id: fromWalletSelector @@ -79,7 +79,7 @@ Mobile.Page { Layout.alignment: Qt.AlignRight text: qsTr("Prepare...") - enabled: transferManager.allOk + enabled: transferManager.inputsOk onClicked: { transferManager.prepare(); thePile.push("ShowPrepared.qml", { "transferManager" : transferManager } ); diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index 5794879..d49874a 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -17,6 +17,7 @@ */ #include "QMLTransferManager.h" #include "FloweePay.h" +#include "TxInfoObject.h" #include #include @@ -43,7 +44,7 @@ void QMLTransferManager::prepare() // seeming to do anything. const Wallet *wIn = m_fromAccount->wallet(); Wallet *wTo = m_toAccount->wallet(); - QTimer::singleShot(100, [=]() { + QTimer::singleShot(100, this, [=]() { // we create one transaction for each address (aka private key). // Regardless how many inputs this creates in a transaction. const auto walletSecrets = wIn->walletSecrets(); @@ -82,46 +83,44 @@ void QMLTransferManager::prepare() } } - transactionsChanged(); + emit transactionsChanged(); + emit unsentTxCountChanged(); m_prepareRunning = false; }); } -void QMLTransferManager::send() +void QMLTransferManager::send(QObject *previewTx) +{ + assert(previewTx); + auto data = qobject_cast(previewTx); + if (!data) + return; + if (data->m_sent) + return; + // create the actual minable transaction + auto tx = createTx(data); + data->m_sent = true; + + auto wallet = m_toAccount->wallet(); + // call to wallet to mark outputs locked and save tx. + wallet->newTransaction(tx); + wallet->setTransactionComment(tx, tr("Migrated Coin")); + // and broadcast it. + auto txInfo = std::make_shared(m_toAccount->wallet(), tx); + FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(txInfo); + emit data->sentChanged(); + emit unsentTxCountChanged(); +} + +void QMLTransferManager::sendAll() { assert(m_fromAccount); assert(m_toAccount); - const Wallet *wIn = m_fromAccount->wallet(); - - // create each transaction for real. - for (auto tx_ : m_transactions) { - auto *tx = qobject_cast(tx_); - TransactionBuilder builder; - builder.setFeeTarget(1000); - builder.setAnonimize(true); - uint64_t value = 0; - for (auto ref : tx->m_utxos) { - auto output = wIn->txOutput(ref); - if (output.hasCashToken) - continue; - value += output.outputValue; - builder.appendInput(wIn->txid(ref), ref.outputIndex()); - auto priv = wIn->unlockKey(ref); - if (priv.sigType == Wallet::NotUsedYet) - priv.sigType = Wallet::SignedAsSchnorr; - TransactionBuilder::SignatureType typeToUse = - (priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr; - assert(priv.key.isValid()); - builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); - } - - builder.appendOutput(value); - builder.pushOutputPay2Address(tx->m_toP2PKH); - builder.setOutputFeeSource(0); - - auto file = builder.createTransaction(); - // TODO send + for (auto data_ : std::as_const(m_transactions)) { + auto *data = qobject_cast(data_); + send(data); } + emit unsentTxCountChanged(); } int QMLTransferManager::coinCount() const @@ -129,7 +128,18 @@ int QMLTransferManager::coinCount() const return m_coinCount; } -bool QMLTransferManager::allOk() const +int QMLTransferManager::unsentTxCount() const +{ + int count = 0; + for (auto *tx_ : m_transactions) { + auto *tx = qobject_cast(tx_); + if (!tx->m_sent) + ++count; + } + return count; +} + +bool QMLTransferManager::inputsOk() const { if (m_addressCount == 0) return false; @@ -137,7 +147,7 @@ bool QMLTransferManager::allOk() const return false; if (m_toAccount == nullptr) return false; - return true; + return m_toAccount != m_fromAccount; } void QMLTransferManager::setCoinCount(int c) @@ -155,7 +165,7 @@ void QMLTransferManager::freeReservations() if (m_toAccount == nullptr) return; auto *wallet = m_toAccount->wallet(); - for (int privId : m_reservations) { + for (int privId : std::as_const(m_reservations)) { wallet->unreserveAddress(privId); } m_reservations.clear(); @@ -164,6 +174,40 @@ void QMLTransferManager::freeReservations() emit transactionsChanged(); } +Tx QMLTransferManager::createTx(PreviewTx *tx) +{ + assert(tx); + assert(!tx->m_sent); + assert(m_fromAccount); + const Wallet *wIn = m_fromAccount->wallet(); + assert(wIn); + + TransactionBuilder builder; + builder.setFeeTarget(1000); + builder.setAnonimize(true); + uint64_t value = 0; + for (auto ref : tx->m_utxos) { + auto output = wIn->txOutput(ref); + if (output.hasCashToken) + continue; + value += output.outputValue; + builder.appendInput(wIn->txid(ref), ref.outputIndex()); + auto priv = wIn->unlockKey(ref); + if (priv.sigType == Wallet::NotUsedYet) + priv.sigType = Wallet::SignedAsSchnorr; + TransactionBuilder::SignatureType typeToUse = + (priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr; + assert(priv.key.isValid()); + builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); + } + + builder.appendOutput(value); + builder.pushOutputPay2Address(tx->m_toP2PKH); + builder.setOutputFeeSource(0); + + return builder.createTransaction(); +} + QList QMLTransferManager::transactions() const { return m_transactions; @@ -194,7 +238,7 @@ void QMLTransferManager::setToAccount(AccountInfo *newToAccount) freeReservations(); m_toAccount = newToAccount; emit toAccountChanged(); - emit allOkChanged(); + emit inputsOkChanged(); } AccountInfo *QMLTransferManager::fromAccount() const @@ -223,7 +267,7 @@ void QMLTransferManager::setFromAccount(AccountInfo *account) } setAddressCount(addresses); setCoinCount(totalCoins); - emit allOkChanged(); + emit inputsOkChanged(); } @@ -273,3 +317,10 @@ int PreviewTx::inputCount() const { return m_utxos.size(); } + +bool PreviewTx::sent() const +{ + return m_sent; +} + + diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h index 6e36f3e..28bcde7 100644 --- a/modules/big-transfer/QMLTransferManager.h +++ b/modules/big-transfer/QMLTransferManager.h @@ -21,6 +21,8 @@ #include #include +class PreviewTx; + class QMLTransferManager : public QObject { Q_OBJECT @@ -29,7 +31,8 @@ class QMLTransferManager : public QObject Q_PROPERTY(QList transactions READ transactions NOTIFY transactionsChanged FINAL) Q_PROPERTY(int addressCount READ addressCount NOTIFY addressCountChanged FINAL) Q_PROPERTY(int coinCount READ coinCount NOTIFY coinCountChanged FINAL) - Q_PROPERTY(bool allOk READ allOk NOTIFY allOkChanged FINAL) + Q_PROPERTY(int unsentTxCount READ unsentTxCount NOTIFY unsentTxCountChanged FINAL) + Q_PROPERTY(bool inputsOk READ inputsOk NOTIFY inputsOkChanged FINAL) public: QMLTransferManager(QObject *parent = nullptr); @@ -44,10 +47,14 @@ public: // total num of coins on the 'from' account int coinCount() const; - bool allOk() const; + /// how many items in the transactions, which have not yet been approved. + int unsentTxCount() const; + + bool inputsOk() const; Q_INVOKABLE void prepare(); - Q_INVOKABLE void send(); + Q_INVOKABLE void send(QObject *previewTx); + Q_INVOKABLE void sendAll(); QList transactions() const; @@ -56,13 +63,15 @@ signals: void toAccountChanged(); void addressCountChanged(); void coinCountChanged(); - void allOkChanged(); + void inputsOkChanged(); void transactionsChanged(); + void unsentTxCountChanged(); private: void setAddressCount(int newAddressCount); void setCoinCount(int newCoinCount); void freeReservations(); + Tx createTx(PreviewTx *tx); AccountInfo *m_fromAccount = nullptr; AccountInfo *m_toAccount = nullptr; @@ -94,9 +103,12 @@ class PreviewTx : public QObject { Q_PROPERTY(int inputCount READ inputCount CONSTANT FINAL) Q_PROPERTY(QObject* from READ from CONSTANT FINAL) Q_PROPERTY(QObject* to READ to CONSTANT FINAL) + Q_PROPERTY(bool sent READ sent NOTIFY sentChanged FINAL) public: PreviewTx(QObject *parent = nullptr); + bool sent() const; + int64_t value() const; QObject *from() const; QObject *to() const; @@ -105,9 +117,13 @@ public: int64_t m_value = 0; Address *m_from = nullptr; Address *m_to = nullptr; + bool m_sent = false; std::vector m_utxos; KeyId m_toP2PKH; + +signals: + void sentChanged(); }; #endif diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 207ce85..633fcd8 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -26,65 +26,211 @@ import Flowee.org.pay.bigtransfer; Mobile.Page { id: root required property QtObject transferManager; - headerText: qsTr("Verify %1 Transactions", "", transferManager.transactions.length).arg(transferManager.transactions.length); + headerText: qsTr("Verify %1 Transactions", "", reviewList.model.length) + .arg(transferManager.transactions.length); ListView { + id: reviewList width: parent.width anchors.top: parent.top - anchors.bottom: slideToApprove.top - anchors.bottomMargin: 10 + anchors.bottom: infoLabel.top + anchors.bottomMargin: 20 clip: true model: root.transferManager.transactions delegate: Rectangle { + id: delegateRoot width: ListView.view.width - property bool tall: indexLabel.width + 6 + fromLabel.width + 10 - + pointerLabel.width + 10 + toLabel.width > width; - height: { - var h = 5 + fromLabel.height + 5; - if (tall) - h += pointerLabel.height + toLabel.height; - return h; - } color: (index % 2) === 0 ? palette.base : palette.alternateBase - Flowee.Label { - id: indexLabel - text: index + 1 - y: 5 + height: topRow.height + 6 + addresses.height + 10 + property int offset: modelData.sent ? 80 : 0 + opacity: { + if (helpText.opacity > 0 && (dragItem.x === 0 || dragItem > 260)) + return 0.8 - helpText.opacity / 2; + return 1; } - Flowee.AddressLabel { - id: fromLabel + Flowee.Label { + id: topRow + text: index + 1 + ")" y: 5 - x: indexLabel.width + 6 - width: Math.min(implicitWidth, parent.width - x) - txInfo: model.from - highlight: false + x: parent.offset + } + Flowee.BitcoinAmountLabel { + y: 5 + x: topRow.width + topRow.x + 10 + value: modelData.value } - Flowee.Label { - id: pointerLabel - text: "🠮" - x: parent.tall ? parent.width / 3 : (fromLabel.x + fromLabel.width + 10); - y: parent.tall ? fromLabel.height + 11 : 5 + Item { + id: dragItem + width: parent.width + height: parent.height + + onXChanged: { + delegateRoot.offset = x / 2 + if (x > 250) { + root.transferManager.send(modelData); + helpText.visible = false; + delegateRoot.offset = 80 + } + helpText.opacity = dragItem.x / 200 + } + + DragHandler { + id: dragHandler + yAxis.enabled: false + xAxis.enabled: true + xAxis.maximum: 270 // swipe left + xAxis.minimum: 0 // swipe right + enabled: !modelData.sent + onActiveChanged: { + if (active) { + var rect = Qt.rect(0, 0, delegateRoot.width, delegateRoot.height); + var convertedRect = helpText.mapFromItem(delegateRoot, rect); + helpText.base = convertedRect; + helpText.visible = true; + } else { + parent.x = 0; + delegateRoot.offset = modelData.sent ? 80 : 0; + } + } + } } - Flowee.AddressLabel { - id: toLabel - width: Math.min(implicitWidth, parent.width) - txInfo: model.to - highlight: false - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 + Item { + id: addresses + property bool tall: fromLabel.width + 10 + pointerLabel.width + + 10 + toLabel.width > width; + + width: parent.width + y: topRow.height + 6 + x: modelData.sent ? 80 : 0 + height: { + var h = 5 + fromLabel.height + 5; + if (tall) + h += pointerLabel.height + toLabel.height; + return h; + } + opacity: 1 - dragItem.x / 200 + + Flowee.AddressLabel { + id: fromLabel + y: 5 + showCopyIcon: false + width: Math.min(implicitWidth, parent.width - x) + txInfo: modelData.from + highlight: false + } + + Flowee.Label { + id: pointerLabel + text: "⇒" + x: parent.tall ? parent.width / 3 : (fromLabel.x + fromLabel.width + 10); + y: parent.tall ? fromLabel.height + 11 : 5 + } + + Flowee.AddressLabel { + id: toLabel + width: Math.min(implicitWidth, parent.width) + txInfo: modelData.to + highlight: false + showCopyIcon: false + x: parent.tall ? 0 : pointerLabel.x + pointerLabel.width + 10; + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + } + } + + + Flowee.ProgressCheckIcon { + id: bla + showCheck: true + sweepAngle: 270 + scale: 0.4 + transformOrigin: Item.TopLeft + // TODO add proper broadcast feedback + visible: modelData.sent } } } + Flowee.Label { + id: helpText + + property rect base: Qt.rect(0, 0, 1, 1); + + width: parent.width - 20 + anchors.horizontalCenter: parent.horizontalCenter + y: { + var baseCopy = base; + if (baseCopy.width == 1) + return 0; + if (height + 10 < baseCopy.y) // place it above + return baseCopy.y - height - 10 + + return baseCopy.y + baseCopy.height + 20; + } + + text: qsTr("Send Now") + font.pixelSize: 100 + fontSizeMode: Text.HorizontalFit + color: mainWindow.floweeGreen + opacity: 0 + onOpacityChanged: if (opacity === 0) base = Qt.rect(0, 0, 1, 1); // reset + Behavior on opacity { NumberAnimation { } } + } + + Flowee.Label { + id: infoLabel + width: parent.width + text: { + if (root.transferManager.unsentTxCount < 2) + return ""; + return qsTr("Create and send all %1 transactions", "", + root.transferManager.unsentTxCount).arg(root.transferManager.unsentTxCount); + + } + // height: text === "" ? 0 : implicitHeight + anchors.bottom: slideToApprove.top + anchors.bottomMargin: 10 + wrapMode: Text.WordWrap + } + Mobile.SlideToApprove { id: slideToApprove - onActivated: root.transferManager.send(); + visible: root.transferManager.unsentTxCount > 0 + onActivated: root.transferManager.sendAll(); anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottomMargin: 10 } + Rectangle { // TODO merge with the one from the send-sweep module. + id: closeButtonBig + color: "#e8e8e8" + radius: 10 + width: parent.width - 20 + visible: root.transferManager.unsentTxCount === 0 + x: 10 + height: closeButtonLabel.height + 25 + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + Flowee.Label { + id: closeButtonLabel + text: qsTr("Close") + anchors.centerIn: parent + color: background.color + } + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + focus: true + onClicked: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); + thePile.pop(); + } + } + } } -- 2.54.0 From ec32cb5f8f1a5c3723e49127dc6bc193b2a298fb Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Dec 2024 19:15:22 +0100 Subject: [PATCH 1099/1428] fix grammar --- modules/send-sweep/SendPage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index afebe61..a73a72d 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -166,7 +166,7 @@ Mobile.Page { QQC2.Control { id: broadcastFeedback - anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. + anchors.leftMargin: -10 // go against the margins Page gave us, to show more fullscreen. anchors.rightMargin: -10 anchors.fill: parent font.pixelSize: root.font.pixelSize * 1.2 -- 2.54.0 From 42df2fb2c8289572a0636775a715cbbbac16abbd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 29 Dec 2024 20:09:04 +0100 Subject: [PATCH 1100/1428] Fix new wallet being marked as import for a little This moves the decision if it is importing out of the wallet and we stop using a broken heuristic. Also cleanup the API usage of the hd masterkey format in the wallet header. --- src/FloweePay.cpp | 9 +++++---- src/Wallet.cpp | 15 +++++++++------ src/Wallet.h | 6 ++++-- testing/wallet/TestWallet.cpp | 3 ++- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 65d9827..2cfb86a 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1162,7 +1162,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons auto wallet = createWallet(walletName); wallet->setSingleAddressWallet(true); if (startHeight <= 1) - startHeight = m_chain == P2PNet::MainChain ? 750000 : 1000; + startHeight = m_chain == P2PNet::MainChain ? 850000 : 1000; wallet->addPrivateKey(words.first().toString(), startHeight); emit walletsChanged(); if (!m_offline) @@ -1258,10 +1258,11 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons try { std::vector derivationPath = HDMasterKey::deriveFromString(derivationPathStr.toStdString()); if (startHeight <= 1) - startHeight = m_chain == P2PNet::MainChain ? 550000 : 1000; + startHeight = m_chain == P2PNet::MainChain ? 850000 : 1000; auto seedWords = splitString(mnemonic); // this is great to remove any type of whitespace wallet->createHDMasterKey(joinWords(seedWords, true), password.trimmed().remove('\n'), - derivationPath, startHeight, electrumFormat); + derivationPath, startHeight, /* isImport = */ true, + electrumFormat ? HDMasterKey::ElectrumMnemonic : HDMasterKey::BIP39Mnemonic); emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. @@ -1424,7 +1425,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const RandAddSeedPerfmon(); GetRandBytes(seed.data(), seed.size()); auto mnemonic = m_hdSeedValidator.generateMnemonic(seed, "en"); - wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint()); + wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint(), /* isImport = */ false); emit walletsChanged(); if (!m_offline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 948e209..d5eba55 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -856,7 +856,7 @@ QString Wallet::xpub() const return QString(); } -void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight, bool electrumFormat) +void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight, bool isImport, HDMasterKey::MnemonicType format) { assert(m_hdData.get() == nullptr); if (m_hdData.get()) { @@ -876,9 +876,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons pool->write(pwdBytes.constData(), pwdBytes.size()); auto pwdBuf = pool->commit(); - m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, - electrumFormat ? HDMasterKey::ElectrumMnemonic - : HDMasterKey::BIP39Mnemonic)); + m_hdData.reset(new HierarchicallyDeterministicWalletData(mnemonicBuf, derivationPath, pwdBuf, format)); // append two random numbers, to make clear the full length m_hdData->derivationPath.push_back(0); m_hdData->derivationPath.push_back(0); @@ -889,7 +887,7 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons m_hdData->lastMainKey = -1; m_hdData->lastChangeKey = -1; QMutexLocker locker(&m_lock); - if (startHeight < 1000000) { // when its a blockheight and not a timestamp. + if (isImport && startHeight < 10000000) { // when its a blockheight and not a timestamp. m_segment->blockSynched(startHeight - 1); m_segment->blockSynched(startHeight - 1); // yes, twice m_walletIsImporting = true; @@ -1487,17 +1485,22 @@ void Wallet::headerSyncComplete() if (iter->second.initialHeight > 10000000) { // this is a time based height, lets resolve it to a real height. iter->second.initialHeight = blockchain.blockHeightAtTime(iter->second.initialHeight) - 1; - m_secretsChanged = true; changedOne = true; } } if (changedOne) { + m_secretsChanged = true; + // broadcast to peers our bloom filter, which would have skipped all // time-based blocks before. rebuildBloom(ForceBuild); // make the wallet initial sync also show something sane. if (m_segment) m_segment->blockSynched(blockchain.height()); + if (m_walletIsImporting) { + m_walletIsImporting = false; + m_walletChanged = true; + } } } diff --git a/src/Wallet.h b/src/Wallet.h index 2d799db..d466d19 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -363,9 +363,11 @@ public: * @param pwd the password that was created with the seed-phrase. Can be empty. * @param derivationPath the derivation steps. We will add 2 ints to this internally, so typically this vector has 3 fields. * @param initialBlockHeight either a timestamp or blockheight, or zero if unsure. - * @param electrumFormat Set to true to interpret this mnemonic as an Electrum-style phrase. + * @param isImport would be false if this is a new wallet that is not expected to have any transactions yet. + * @param format Allow override to be in Electrum Format */ - void createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight = 0, bool electrumFormat = false); + void createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, + uint32_t startHeight, bool isImport, HDMasterKey::MnemonicType format = HDMasterKey::BIP39Mnemonic); /// return the height of the last seen transaction that is mined int lastTransactionTimestamp() const; diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index 6a085f2..87d7124 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -664,9 +664,10 @@ void TestWallet::hierarchicallyDeterministic() QCOMPARE(wallet->derivationPath(), QString()); std::vector derivationPath = { HDMasterKey::Hardened + 44, HDMasterKey::Hardened + 145, HDMasterKey::Hardened }; - wallet->createHDMasterKey(southMonkey, QString(), derivationPath); + wallet->createHDMasterKey(southMonkey, QString(), derivationPath, 1, true); QVERIFY(wallet->isHDWallet()); + QVERIFY(wallet->walletIsImporting()); // thats the true in the call above QCOMPARE(wallet->hdWalletMnemonic(), southMonkey); QCOMPARE(wallet->hdWalletMnemonicPwd(), QString()); QCOMPARE(wallet->derivationPath(), QString("m/44'/145'/0'")); -- 2.54.0 From 837bad6a13c68f6af616f5f950860fc0673cb25a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 15:59:01 +0100 Subject: [PATCH 1101/1428] Add Module section type; other-default This section type means that the module will have it's icon shown in the 'Explore' tab always, it can not be disabled by users and as such it just becomes an overflow of the main app avoiding worry about a module not being found. --- guis/mobile/ExploreModules.qml | 1 + guis/mobile/MainView.qml | 15 --------------- modules/send-sweep/SendSweepModuleInfo.cpp | 2 +- src/ModuleInfo.cpp | 11 +++++++++++ src/ModuleInfo.h | 6 +++++- src/ModuleManager.cpp | 3 ++- src/ModuleSection.h | 3 ++- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index fad48f5..68e3264 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -142,6 +142,7 @@ Page { anchors.verticalCenter: parent.verticalCenter checked: modelData.enabled onClicked: modelData.enabled = checked; + visible: modelData.canBeEnabled } // one icon per section-type diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 793f449..4bb5461 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -49,21 +49,6 @@ MainViewBase { width: parent.width spacing: 10 - PageTitledBox { - width: parent.width - title: qsTr("Often Used") - visible: content.length > 1 - Repeater { - model: ModuleManager.oftenUsedItems - TextButton { - text: modelData.text - subtext: modelData.subtext - pageButton: true - onClicked: thePile.push(modelData.qml) - } - } - } - Repeater { model: ModuleManager.exploreTabItems TextButton { diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index e93e202..60ad4e7 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -27,7 +27,7 @@ ModuleInfo * SendSweepModuleInfo::build() } SendSweepModuleInfo::SendSweepModuleInfo() - : m_actionTabItem(new ModuleSection(ModuleSection::OtherSectionType, this)) + : m_actionTabItem(new ModuleSection(ModuleSection::OtherSectionTypeDefault, this)) { setId("sendSweepModule"); setTitle(tr("Sweep & Send")); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index a8aeafa..35e9821 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -98,6 +98,17 @@ void ModuleInfo::setEnabled(bool on) } } +bool ModuleInfo::canBeEnabled() const +{ + for (auto section : m_sections) { + if (section->isMainMenuMethod() || section->isSendMethod()) + return true; + if (section->type() == ModuleSection::OtherSectionType) + return true; + } + return false; +} + QString ModuleInfo::iconSource() const { return m_iconSource; diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index a6a0e35..49f9e87 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -36,8 +36,10 @@ class ModuleInfo : public QObject { Q_OBJECT Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) - /// returns true if there is at least one section that is user-visible + /// returns true if there is at least one section that can be enabled Q_PROPERTY(bool hasUISections READ hasUISections CONSTANT FINAL) + /// returns true is enabling this makes it show up somewhere in the UI + Q_PROPERTY(bool canBeEnabled READ canBeEnabled CONSTANT FINAL) Q_PROPERTY(QString title READ title CONSTANT FINAL) Q_PROPERTY(QString description READ description CONSTANT FINAL) Q_PROPERTY(QList sections READ sections CONSTANT FINAL) @@ -83,6 +85,8 @@ public: virtual void setEnabled(bool on); bool enabled() const; + bool canBeEnabled() const; + /** * Sets the path to the icon for this module. * This should be part of the QRC system and inside the module dir. diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index ede59bd..adcbe6f 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -268,7 +268,8 @@ QList ModuleManager::exploreTabItems() const QList answer; for (const auto *m : m_modules) { for (auto *s : m->sections()) { - if (s->enabled() && s->type() == ModuleSection::OtherSectionType) + if (s->type() == ModuleSection::OtherSectionTypeDefault + || (s->enabled() && s->type() == ModuleSection::OtherSectionType)) answer.append(s); } } diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 5074398..259541d 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -46,6 +46,7 @@ public: StartScreenType, ///< A screen displayed on top of the main app at startup. CustomSectionType, ///< Not normally shown type, but fetchable by id. OtherSectionType, ///< Show this on the 'Explore' tab. + OtherSectionTypeDefault, ///< Show this on the 'Explore' tab, even if the user didn't enable it. // BuildTxComponent, ///< Inside the 'build-transactions' this adds a new buildingblock. }; @@ -88,7 +89,7 @@ public: } bool isForExploreTab() const { - return m_type == OtherSectionType; + return m_type == OtherSectionType || m_type == OtherSectionTypeDefault; } bool enabled() const; -- 2.54.0 From cb46be382e426388015e6dfb6f9e11ffe2ddb971 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 16:03:24 +0100 Subject: [PATCH 1102/1428] Make the module icon show up on the Explore tab --- guis/mobile/ExploreModules.qml | 3 ++- guis/mobile/MainView.qml | 1 + src/ModuleSection.cpp | 10 ++++++++++ src/ModuleSection.h | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 68e3264..68288fc 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -185,7 +185,8 @@ Page { height: width anchors.verticalCenter: parent.verticalCenter visible: section != null - opacity: (visible && section.enabled) ? 0.8 : 0.3 + opacity: (visible && section.enabled + || !modelData.canBeEnabled) ? 0.8 : 0.3 property QtObject section: { // find the section our icon represents. for (let s of modelData.sections) { diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 4bb5461..84236fc 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -54,6 +54,7 @@ MainViewBase { TextButton { text: modelData.text subtext: modelData.subtext + iconSource: modelData.icon pageButton: true onClicked: thePile.push(modelData.qml) } diff --git a/src/ModuleSection.cpp b/src/ModuleSection.cpp index aefcc35..f010a2f 100644 --- a/src/ModuleSection.cpp +++ b/src/ModuleSection.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "ModuleSection.h" +#include "ModuleInfo.h" ModuleSection::ModuleSection(SectionType type, QObject *parent) : QObject(parent), @@ -33,6 +34,14 @@ QString ModuleSection::subtext() const return m_subtext; } +QString ModuleSection::iconSource() const +{ + auto *info = qobject_cast(parent()); + if (info) + return info->iconSource(); + return QString(); +} + QStringList ModuleSection::requiredModules() const { return m_requiredModules; @@ -90,3 +99,4 @@ void ModuleSection::setText(const QString &newText) { m_text = newText; } + diff --git a/src/ModuleSection.h b/src/ModuleSection.h index 259541d..ccf29ef 100644 --- a/src/ModuleSection.h +++ b/src/ModuleSection.h @@ -33,6 +33,7 @@ class ModuleSection : public QObject Q_PROPERTY(QString text READ text CONSTANT) Q_PROPERTY(QString subtext READ subtext CONSTANT) Q_PROPERTY(QString qml READ startQMLFile CONSTANT) + Q_PROPERTY(QString icon READ iconSource CONSTANT) Q_PROPERTY(bool isSendMethod READ isSendMethod CONSTANT) Q_PROPERTY(bool isMainMenuMethod READ isMainMenuMethod CONSTANT) Q_PROPERTY(bool isStartScreenType READ isStartScreenType CONSTANT) @@ -63,6 +64,9 @@ public: void setSubtext(const QString &newSubtext); QString subtext() const; + /// convenience method to return ModuleInfo::iconSource() + QString iconSource() const; + /** * This section may only show if another module is enabled, * name that module (by id) here. -- 2.54.0 From 61b98fd4da445dd285b94cbcc0edb5a0d7e5fe92 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 16:17:39 +0100 Subject: [PATCH 1103/1428] Adjust color of icon make it slightly less bright --- guis/mobile/images/more-light.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/mobile/images/more-light.svg b/guis/mobile/images/more-light.svg index 079297b..d88b7e4 100644 --- a/guis/mobile/images/more-light.svg +++ b/guis/mobile/images/more-light.svg @@ -1,7 +1,7 @@ - - - - + + + + -- 2.54.0 From a27e6df0e0ed97e317b7e62a23a3c81c62b00528 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 16:17:51 +0100 Subject: [PATCH 1104/1428] Make checkbox bigger --- guis/Flowee/CheckBox.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 92b13a6..87f8a5f 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -31,9 +31,8 @@ T.CheckBox { spacing: 6 indicator: Item { - implicitHeight: titleLabel.font.pixelSize + implicitHeight: titleLabel.font.pixelSize * 1.3 implicitWidth: implicitHeight * 2.1 - y: 4.5 // make the inner circle visually baseline aligned Rectangle { anchors.fill: parent @@ -89,8 +88,8 @@ T.CheckBox { Rectangle { id: questionMarkIcon visible: control.toolTipText !== "" && control.enabled - width: q.implicitWidth + 14 - height: q.implicitHeight + width: q.height + 14 + height: q.height x: titleLabel.x + titleLabel.implicitWidth + 10 anchors.verticalCenter: contentItem.verticalCenter radius: width @@ -100,6 +99,7 @@ T.CheckBox { text: "?" color: palette.base anchors.centerIn: parent + height: implicitHeight * 1.3 MouseArea { anchors.fill: parent anchors.margins: -7 -- 2.54.0 From 051931744376ff11781c75e991d5ef55ada45f86 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 16:34:29 +0100 Subject: [PATCH 1105/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 2037ad5..d1c2e5e 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="33" android:versionName="2025.00.0"> diff --git a/src/main.cpp b/src/main.cpp index 427b408..ef43119 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2024.12.1"); + qapp.setApplicationVersion("2025.00.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 5411d21fb06984d8dcb823a7dff19451e044f3de Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Dec 2024 23:14:47 +0100 Subject: [PATCH 1106/1428] Use ints instead of fancy JS enums. I mean, yes, the fancy enums help readability, but the fancy enums actually pull in a rather large dependency that adds hundreds of kilobytes to the deployment. Then just ints don't look so bad anymore, do they? --- guis/mobile/ImportWalletPage.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 0176ba5..e22d9f6 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -666,7 +666,7 @@ Page { model: { let locale = Qt.locale(); var list = []; - for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { + for (let i = 0; i <= 11; ++i) { list.push(locale.monthName(i)); } return list; -- 2.54.0 From c2c39683dba706a985a001a72bb27c632e977e5d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Dec 2024 15:55:36 +0100 Subject: [PATCH 1107/1428] Add more checks and avoid surprises --- modules/big-transfer/QMLTransferManager.cpp | 40 +++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index d49874a..9b5dbb0 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -101,10 +101,14 @@ void QMLTransferManager::send(QObject *previewTx) auto tx = createTx(data); data->m_sent = true; - auto wallet = m_toAccount->wallet(); - // call to wallet to mark outputs locked and save tx. - wallet->newTransaction(tx); - wallet->setTransactionComment(tx, tr("Migrated Coin")); + // call to fromWallet to mark outputs locked and save tx. + auto fromWallet = m_fromAccount->wallet(); + fromWallet->newTransaction(tx); + fromWallet->setTransactionComment(tx, tr("Migrated Coin")); + // call to toWallet to have it too! + auto toWallet = m_toAccount->wallet(); + toWallet->newTransaction(tx); + toWallet->setTransactionComment(tx, tr("Migrated Coin")); // and broadcast it. auto txInfo = std::make_shared(m_toAccount->wallet(), tx); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(txInfo); @@ -141,12 +145,20 @@ int QMLTransferManager::unsentTxCount() const bool QMLTransferManager::inputsOk() const { - if (m_addressCount == 0) - return false; if (m_fromAccount == nullptr) return false; + if (m_addressCount == 0) + return false; + if (!m_fromAccount->isDecrypted()) + return false; + if (m_fromAccount->isSingleAddressAccount()) + return false; if (m_toAccount == nullptr) return false; + if (m_toAccount->needsPinToOpen() && !m_toAccount->isDecrypted()) + return false; + if (m_toAccount->isSingleAddressAccount()) + return false; return m_toAccount != m_fromAccount; } @@ -256,12 +268,14 @@ void QMLTransferManager::setFromAccount(AccountInfo *account) int totalCoins = 0; if (account->wallet()) { const Wallet *wallet = account->wallet(); - const auto walletSecrets = wallet->walletSecrets(); - for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) { - auto details = wallet->fetchKeyDetails(i->first); - if (details.coins > 0) { - ++addresses; - totalCoins += details.coins; + if (!account->needsPinToOpen() || account->isDecrypted()) { + const auto walletSecrets = wallet->walletSecrets(); + for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) { + auto details = wallet->fetchKeyDetails(i->first); + if (details.coins > 0) { + ++addresses; + totalCoins += details.coins; + } } } } @@ -322,5 +336,3 @@ bool PreviewTx::sent() const { return m_sent; } - - -- 2.54.0 From ef5982c312742c3734684e845d8068f1f592d756 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 31 Dec 2024 16:44:11 +0100 Subject: [PATCH 1108/1428] Update the UX on ExploreModules page Following the moving of this to the 'explore' tab as "find more" this is more a detailed listing of all modules, and as a result it makes sense to add an 'open' button. This avoids people being forced to enable a feature they only want to use once. Following this new UX the 'ON' ribbon loses its meaning, you can use a module that is not on. --- guis/mobile.qrc | 1 - guis/mobile/ExploreModules.qml | 61 ++++++++++++++++----------------- guis/mobile/images/ribbon.svg | 62 ---------------------------------- 3 files changed, 30 insertions(+), 94 deletions(-) delete mode 100644 guis/mobile/images/ribbon.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 3128ebb..bb350d6 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -33,7 +33,6 @@ mobile/images/tx-coinbase-light.svg mobile/images/tx-move.svg mobile/images/tx-move-light.svg - mobile/images/ribbon.svg mobile/images/keyboard.svg mobile/images/keyboard-light.svg mobile/images/num-keyboard-light.svg diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 68288fc..93e35e8 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -95,37 +95,6 @@ Page { } } - Image { - id: enabledIndicator - source: "qrc:/ribbon.svg" - smooth: true - anchors.right: parent.right - anchors.rightMargin: -8 - y: -9 - width: 90 - height: 90 - opacity: modelData.enabled ? 1 : 0 - visible: opacity > 0 - Behavior on opacity { NumberAnimation { } } - z: 11 - - Item { - rotation: 45 - width: 80 - height: 22 - x: 16 - y: 22 - clip: true - - Text { - color: "white" - text: qsTr("ON", "Enabled. SHORT TEXT!"); - font.bold: true - anchors.centerIn: parent - } - } - } - Item { id: statusField width: parent.width @@ -133,6 +102,36 @@ Page { anchors.bottom: parent.bottom z: 1 + Image { + id: openIcon + rotation: 270; width: 12; height: 8 + anchors.right: parent.right + anchors.rightMargin: 10 + source: Pay.useDarkSkin ? "qrc:/smallArrow-light.svg" : "qrc:/smallArrow.svg"; + anchors.verticalCenter: openLabel.verticalCenter + } + Flowee.Label { + id: openLabel + text: qsTr("Open") + anchors.right: openIcon.left + anchors.rightMargin: 6 + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + } + MouseArea { + anchors.left: openLabel.left + anchors.right: parent.right + height: parent.height + onClicked: { + for (let s of modelData.sections) { + if (s.isMainMenuMethod || s.isSendMethod || s.isForExploreTab) { + thePile.replace(s.qml); + return; + } + } + } + } + Row { spacing: 10 anchors.fill: parent diff --git a/guis/mobile/images/ribbon.svg b/guis/mobile/images/ribbon.svg deleted file mode 100644 index e066e02..0000000 --- a/guis/mobile/images/ribbon.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- 2.54.0 From 44d3a267eb4e1e3f2db382d3f3ff03b2a4a75d1c Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 1 Jan 2025 17:35:36 +0100 Subject: [PATCH 1109/1428] Improve API, make private method private --- src/TxInfoObject.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h index 6c29b83..e87b13c 100644 --- a/src/TxInfoObject.h +++ b/src/TxInfoObject.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -68,8 +68,6 @@ public: /// the wallet internal index of the transaction we're broadcasting. int txIndex() const; - // set internally. - void setBroadcastStatus(const FloweePay::BroadcastStatus &status); FloweePay::BroadcastStatus broadcastStatus() const; signals: @@ -79,6 +77,10 @@ signals: void broadcastStatusChanged(); private: + // set internally. + void setBroadcastStatus(const FloweePay::BroadcastStatus &status); + + friend class TxInfoPrivate; TxInfoPrivate *d; FloweePay::BroadcastStatus m_status; }; -- 2.54.0 From 7d5686b427b6529737b90c32ba8d680cf8e655cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 1 Jan 2025 17:36:58 +0100 Subject: [PATCH 1110/1428] Forward the broadcast status to the UI When a transaction has been created by the plugin, this stores it also on our UI classes and forwards the broadcast status for the front end to use in some animation. --- modules/big-transfer/QMLTransferManager.cpp | 22 ++++++++++++++++++++- modules/big-transfer/QMLTransferManager.h | 13 +++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index 9b5dbb0..2078532 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -112,6 +112,9 @@ void QMLTransferManager::send(QObject *previewTx) // and broadcast it. auto txInfo = std::make_shared(m_toAccount->wallet(), tx); FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(txInfo); + // the txInfo is a sharedPtr, we need to store it somewhere to not get deleted + // when it goes out of scope at the end of this method. + data->setFinalTx(txInfo); emit data->sentChanged(); emit unsentTxCountChanged(); } @@ -332,6 +335,23 @@ int PreviewTx::inputCount() const return m_utxos.size(); } +void PreviewTx::setFinalTx(const std::shared_ptr &finalTx) +{ + assert(m_finalTx.get() == nullptr); // avoid bugs with connects and only allow this once + m_finalTx = finalTx; + if (m_finalTx) { + connect (m_finalTx.get(), SIGNAL(broadcastStatusChanged()), + this, SIGNAL(broadcastStatusChanged())); + } +} + +FloweePay::BroadcastStatus PreviewTx::broadcastStatus() const +{ + if (m_finalTx) + return m_finalTx->broadcastStatus(); + return FloweePay::NotStarted; +} + bool PreviewTx::sent() const { return m_sent; diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h index 28bcde7..d6d3486 100644 --- a/modules/big-transfer/QMLTransferManager.h +++ b/modules/big-transfer/QMLTransferManager.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,9 @@ #ifndef TRANSFERMANAGER_H #define TRANSFERMANAGER_H +#include #include + #include class PreviewTx; @@ -104,6 +106,7 @@ class PreviewTx : public QObject { Q_PROPERTY(QObject* from READ from CONSTANT FINAL) Q_PROPERTY(QObject* to READ to CONSTANT FINAL) Q_PROPERTY(bool sent READ sent NOTIFY sentChanged FINAL) + Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged FINAL) public: PreviewTx(QObject *parent = nullptr); @@ -122,8 +125,16 @@ public: std::vector m_utxos; KeyId m_toP2PKH; + void setFinalTx(const std::shared_ptr &finalTx); + + FloweePay::BroadcastStatus broadcastStatus() const; signals: void sentChanged(); + void broadcastStatusChanged(); + +private: + // the one we created + std::shared_ptr m_finalTx; }; #endif -- 2.54.0 From 91d7abcd4403d3abfe90257ce70e648ed162a697 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 13:38:39 +0100 Subject: [PATCH 1111/1428] Fix fresh new wallet showing it is in need of sync. --- src/Wallet.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d5eba55..d9e33e4 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -887,10 +887,15 @@ void Wallet::createHDMasterKey(const QString &mnemonic, const QString &pwd, cons m_hdData->lastMainKey = -1; m_hdData->lastChangeKey = -1; QMutexLocker locker(&m_lock); - if (isImport && startHeight < 10000000) { // when its a blockheight and not a timestamp. - m_segment->blockSynched(startHeight - 1); - m_segment->blockSynched(startHeight - 1); // yes, twice - m_walletIsImporting = true; + m_walletIsImporting = isImport; + if (startHeight < 10000000) { // when its a blockheight and not a timestamp. + int syncStart = startHeight; + // off by one because we expect to start synching at start + // and this registers the last one we checked. + if (isImport) // not imports will just list current height + syncStart -= 1; + m_segment->blockSynched(syncStart); + m_segment->blockSynched(syncStart); // yes, twice } deriveHDKeys(200, 200, startHeight); rebuildBloom(MaybeBuild); -- 2.54.0 From 5aa17b96fb35a57e7f3331ef2be1959091852373 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 15:51:13 +0100 Subject: [PATCH 1112/1428] Follow scanner API change --- guis/desktop/NewAccountImportAccount.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 303f57d..21715b4 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -104,11 +104,10 @@ Item { } QRScanner { id: scanner - scanType: QRScanner.SeedOrPrivKey onFinished: { - if (scanResult !== "") + if ((scanType === QRScanner.Seed || QRScanner.PrivateKeyWIF) + && scanResult !== "") secretText.text = scanResult; - // make sure to give focus back to this page after the camera took it. scanButton.forceActiveFocus(); } } -- 2.54.0 From 3972d44f69a5b519bcff4d4bf1ee321f9917f723 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 18:35:46 +0100 Subject: [PATCH 1113/1428] Implement more basic component features. This makes the widgets more re-usable by following the general design of Qt components with porperties like implicitWidth being implemented as expected. --- guis/Flowee/GroupBox.qml | 8 ++++++-- guis/mobile/TextButton.qml | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index f179a7c..c9d001b 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,10 +30,13 @@ QQC2.Control { * collapsed state taking both changes from the `collapsed` property and from user input. */ property bool effectiveCollapsed: collapsed + property bool drawBorder: collapsable property alias title: titleLabel.text property alias summary: summaryLabel.text default property alias content: child.children property alias columns: child.columns + property alias columnSpacing: child.columnSpacing + property alias rowSpacing: child.rowSpacing activeFocusOnTab: collapsable clip: collapsable @@ -41,6 +44,7 @@ QQC2.Control { width: 200 // should be changed by user implicitHeight: Math.max(titleArea.height, arrowPoint.height) + (effectiveCollapsed ? 0 : child.implicitHeight + 25) + implicitWidth: child.implicitWidth + 24 Rectangle { // the outline width: parent.width @@ -48,7 +52,7 @@ QQC2.Control { height: root.effectiveCollapsed ? 1 : parent.height - y; color: palette.alternateBase border.color: palette.mid - border.width: root.collapsable ? 1.3 : 0 + border.width: root.drawBorder ? 1.3 : 0 radius: 3 } MouseArea { diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 179652e..8c74285 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,13 +16,17 @@ * along with this program. If not, see . */ import QtQuick +import QtQuick.Layouts import "../Flowee" as Flowee Item { id: root width: parent.width + implicitWidth: label.x + Math.max(label.contentWidth, smallLabel.contentWidth) + + ((rightIcon.implicitWidth === 0) ? 0 : (10 + + rightIcon.implicitWidth)) height: label.height + (smallLabel.text === "" ? 0 : smallLabel.height + 6) + 20 baselineOffset: label.baselineOffset + 10 + Layout.fillWidth: true signal clicked property alias text: label.text @@ -72,6 +76,7 @@ Item { } Loader { + id: rightIcon sourceComponent: { if (root.pageButton) return pageIcon; -- 2.54.0 From 7f9b48652d310acf7871703b7143621919a64339 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 18:41:06 +0100 Subject: [PATCH 1114/1428] Make output more terse --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 498b600..3c886ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -399,9 +399,8 @@ if (${build_mobile_pay}) if (EXISTS ${subModule}) file (RELATIVE_PATH module ${CMAKE_CURRENT_SOURCE_DIR} ${child}) if (NOT (${skip_example} AND ${module} STREQUAL "modules/example")) - message("-> Including module '${module}'") get_filename_component(className ${subModule} NAME_WE) - message(" ${className}") + message("-> Including module '${module}' (${className})") endif() endif() endif() -- 2.54.0 From 27e696bd9ca0ff7918b7108d108c2f12d6cc7aff Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 18:37:27 +0100 Subject: [PATCH 1115/1428] Rework the startup screen a little --- guis/mobile/StartupScreen.qml | 147 +++++++++++++++------------------- 1 file changed, 66 insertions(+), 81 deletions(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 6e34213..5cf4014 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +36,7 @@ Page { } - Item { // data store for this page. + Item { // data Connections { // connect to wallet and when its no longe user owned, close this window. target: portfolio.current @@ -53,47 +53,30 @@ Page { onActiveFocusChanged: request.start(); Flickable { - anchors.fill: parent - contentWidth: parent.width + width: parent.width + 20 + x: -10 + height: parent.height + 20 + contentWidth: width contentHeight: column.height + 20 clip: true Column { id: column width: parent.width spacing: 10 - y: 10 - Row { - spacing: 20 - x: (column.width - width) / 2 + Rectangle { + width: parent.width + height: 80 + color: mainWindow.floweeBlue - Rectangle { - id: logo - width: 100 - height: 100 - radius: 50 - color: mainWindow.floweeBlue - Item { - // clip the logo only, ignore the text part - width: 71 - height: 71 - clip: true - x: 18 - y: 22 - Image { - source: "qrc:/FloweePay-light.svg" - // ratio: 449 / 77 - width: height / 77 * 449 - height: 71 - } - } - } Image { - source: "qrc:/bch.svg" - y: 10 - width: 87 - height: 87 - smooth: true + id: img + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: parent.width - 20 + height: width / 449 * 77 + x: 10 + y: 8 } } Flowee.Label { @@ -101,14 +84,60 @@ Page { wrapMode: Text.WordWrap font.italic: true horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter width: column.width * 0.8 - x: column.width / 10 } - Item { width: 1; height: 20 } // spacer + + Item { width: 1; height: 10 } // spacer + + Rectangle { + radius: 20 + height:button2Text.height + 20 + width: button2Text.width + 40 + color: "#178b3a" + anchors.horizontalCenter: parent.horizontalCenter + property QtObject infoObject: + ModuleManager.sectionOnPlugin("sendSweepModule", "main"); + + visible: infoObject !== null + + Flowee.Label { + id: button2Text + color: "white" + text: qsTr("Claim a Cash Stamp") + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: thePile.replace(parent.infoObject.qml); + } + } + + Item { width: 1; height: 5 } // spacer + + Row { + spacing: 15 + x: (column.width - width) / 2 + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } + Flowee.Label { + text: qsTr("OR") + } + Rectangle { + width: 50 + height: 1 + color: palette.button + anchors.verticalCenter: parent.verticalCenter + } + } Flowee.Label { id: instructions - text: qsTr("Scan me to send funds to your HD wallet") + ":" + text: qsTr("Scan to send to your wallet") + ":" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter width: column.width * 0.8 @@ -119,26 +148,6 @@ Page { qrText: request.qr } - /* - Row { - spacing: 20 - height: 20 - anchors.horizontalCenter: parent.horizontalCenter - Rectangle { - // copy to clipboard icon - width: 25 - height: 25 - } - Rectangle { - // 'share' icon - width: 25 - height: 25 - } - } */ - - - Item { width: 1; height: 5 } // spacer - Row { spacing: 15 x: (column.width - width) / 2 @@ -180,30 +189,6 @@ Page { } } - Rectangle { - radius: 20 - height:button2Text.height + 20 - width: button2Text.width + 40 - color: "#178b3a" - anchors.horizontalCenter: parent.horizontalCenter - property QtObject infoObject: - ModuleManager.sectionOnPlugin("sendSweepModule", "main"); - - visible: infoObject !== null - - Flowee.Label { - id: button2Text - color: "white" - text: qsTr("Claim a Cash Stamp") - anchors.centerIn: parent - } - MouseArea { - anchors.fill: parent - onClicked: thePile.replace(parent.infoObject.qml); - } - } - - Item { width: 1; height: 40 } // spacer } } -- 2.54.0 From 9e3ae43e112350236d736d1cb8bba15645f07b41 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 22:38:46 +0100 Subject: [PATCH 1116/1428] Stop shipping Portugese translations. Portugese translations dropped below 10%, this is just confusing to users to have most of the app in English and some localized. --- CMakeLists.txt | 5 ----- translations/desktop-i18n.qrc | 2 -- translations/mobile-i18n.qrc | 4 ---- 3 files changed, 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c886ca..3b92903 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,7 +105,6 @@ if(NOT ANDROID) translations/floweepay-desktop_pl.ts translations/floweepay-desktop_de.ts translations/floweepay-desktop_es.ts - translations/floweepay-desktop_pt.ts translations/floweepay-desktop_ha.ts translations/floweepay-common_en.ts @@ -113,7 +112,6 @@ if(NOT ANDROID) translations/floweepay-common_pl.ts translations/floweepay-common_de.ts translations/floweepay-common_es.ts - translations/floweepay-common_pt.ts translations/floweepay-common_ha.ts translations/floweepay-mobile_en.ts @@ -121,7 +119,6 @@ if(NOT ANDROID) translations/floweepay-mobile_pl.ts translations/floweepay-mobile_de.ts translations/floweepay-mobile_es.ts - translations/floweepay-mobile_pt.ts translations/floweepay-mobile_ha.ts translations/module-build-transaction_en.ts @@ -129,7 +126,6 @@ if(NOT ANDROID) translations/module-build-transaction_pl.ts translations/module-build-transaction_de.ts translations/module-build-transaction_es.ts - translations/module-build-transaction_pt.ts translations/module-build-transaction_ha.ts translations/module-peers-view_en.ts @@ -137,7 +133,6 @@ if(NOT ANDROID) translations/module-peers-view_pl.ts translations/module-peers-view_de.ts translations/module-peers-view_es.ts - translations/module-peers-view_pt.ts translations/module-peers-view_ha.ts translations/module-send-sweep_en.ts diff --git a/translations/desktop-i18n.qrc b/translations/desktop-i18n.qrc index 34b3c27..ab15d6f 100644 --- a/translations/desktop-i18n.qrc +++ b/translations/desktop-i18n.qrc @@ -5,13 +5,11 @@ floweepay-desktop_en.qm floweepay-desktop_de.qm floweepay-desktop_es.qm - floweepay-desktop_pt.qm floweepay-desktop_ha.qm floweepay-common_nl.qm floweepay-common_pl.qm floweepay-common_en.qm floweepay-common_de.qm - floweepay-common_pt.qm floweepay-common_es.qm floweepay-common_ha.qm diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index e7716a9..3c225bc 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -5,28 +5,24 @@ floweepay-common_pl.qm floweepay-common_de.qm floweepay-common_es.qm - floweepay-common_pt.qm floweepay-common_ha.qm floweepay-mobile_en.qm floweepay-mobile_nl.qm floweepay-mobile_pl.qm floweepay-mobile_de.qm floweepay-mobile_es.qm - floweepay-mobile_pt.qm floweepay-mobile_ha.qm module-build-transaction_en.qm module-build-transaction_nl.qm module-build-transaction_pl.qm module-build-transaction_de.qm module-build-transaction_es.qm - module-build-transaction_pt.qm module-build-transaction_ha.qm module-peers-view_en.qm module-peers-view_nl.qm module-peers-view_pl.qm module-peers-view_de.qm module-peers-view_es.qm - module-peers-view_pt.qm module-peers-view_ha.qm module-send-sweep_nl.qm module-send-sweep_es.qm -- 2.54.0 From 9da5715c182391586b59c2f40382cc4560b2d3f6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 2 Jan 2025 22:41:04 +0100 Subject: [PATCH 1117/1428] Updates from Crowdin --- translations/floweepay-common_de.ts | 119 ++++----- translations/floweepay-common_es.ts | 119 ++++----- translations/floweepay-common_ha.ts | 119 ++++----- translations/floweepay-common_nl.ts | 115 ++++---- translations/floweepay-common_pl.ts | 117 +++++---- translations/floweepay-common_pt.ts | 204 ++++++++++----- translations/floweepay-desktop_de.ts | 209 +++++++-------- translations/floweepay-desktop_es.ts | 209 +++++++-------- translations/floweepay-desktop_ha.ts | 209 +++++++-------- translations/floweepay-desktop_nl.ts | 209 +++++++-------- translations/floweepay-desktop_pl.ts | 209 +++++++-------- translations/floweepay-mobile_de.ts | 274 ++++++++++++-------- translations/floweepay-mobile_es.ts | 274 ++++++++++++-------- translations/floweepay-mobile_ha.ts | 274 ++++++++++++-------- translations/floweepay-mobile_nl.ts | 274 ++++++++++++-------- translations/floweepay-mobile_pl.ts | 274 ++++++++++++-------- translations/module-build-transaction_de.ts | 19 +- translations/module-build-transaction_es.ts | 19 +- translations/module-build-transaction_ha.ts | 19 +- translations/module-build-transaction_nl.ts | 19 +- translations/module-build-transaction_pl.ts | 19 +- translations/module-send-sweep_de.ts | 45 ++-- translations/module-send-sweep_es.ts | 45 ++-- translations/module-send-sweep_ha.ts | 45 ++-- translations/module-send-sweep_nl.ts | 45 ++-- translations/module-send-sweep_pl.ts | 45 ++-- 26 files changed, 1954 insertions(+), 1574 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 7ac8db7..34d0610 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Geldbörse: Aktuell - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Hinterher: %1 Tag @@ -31,17 +31,17 @@ - + Up to date Aktuell - + Updating Aktualisierung - + Still %1 hours behind Noch %1 Stunde hinterher @@ -79,47 +79,47 @@ BroadcastFeedback - + Sending Payment Sende Zahlung - + Payment Sent Zahlung gesendet - + Failed Fehlgeschlagen - + Transaction rejected by network Transaktion wurde vom Netzwerk abgelehnt - + Payment has been sent to: Zahlung wurde gesendet an: - + Copied address to clipboard Adresse in Zwischenablage kopiert - + Opening Website Öffne Website - + Add a personal note Eine persönliche Notiz hinzufügen - + Close Schließen @@ -135,12 +135,12 @@ FiatTxInfo - + Value now Wert jetzt - + Value then Wert zuvor @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -234,22 +234,35 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Bitcoin Cash Block abgebaut. Höhe: %1 - + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) Block abgebaut: %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash Block abgebaut. Höhe: %1 + + + tBCH (testnet4) block mined: %1 tBCH (testnet4) Block abgebaut: %1 - + Mute Stummschalten - + New Transactions dialog-title @@ -258,21 +271,21 @@ - + %1 new transactions across %2 wallets found (%3) %1 neue Transaktionen über %2 Wallets gefunden (%3) - + A payment of %1 has been sent Eine Zahlung von %1 wurde gesendet - + %1 new transactions found (%2) - - %1 neue Transaktion gefunden (%2) + %1 neue Transaktionen gefunden (%2) + %1 new transactions found (%2) @@ -331,22 +344,12 @@ QRScanner - - Paste - Einfügen - - - - Failed - Fehlgeschlagen - - - + Instant Pay limit is %1 Sofortzahlungslimit ist %1 - + Selected wallet: '%1' Ausgewählte Geldbörse: '%1' @@ -370,7 +373,7 @@ TextPasteDecorator - + Paste Einfügen @@ -378,14 +381,14 @@ Wallet - - + + Change #%1 Wechselgeld #%1 - - + + Main #%1 Haupt #%1 @@ -442,27 +445,27 @@ WalletHistoryModel - + Today Heute - + Yesterday Gestern - + Earlier this week Früher in dieser Woche - + This week Diese Woche - + Earlier this month Früher in diesem Monat diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 115c469..017412a 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Sin conexión - + Wallet: Up to date Monedero: Actualizado - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Retraso: %1 día @@ -31,17 +31,17 @@ - + Up to date Actualizado - + Updating Actualizando - + Still %1 hours behind Todavía %1 hora de retraso @@ -79,47 +79,47 @@ BroadcastFeedback - + Sending Payment Enviando Pago - + Payment Sent Pago Enviado - + Failed Fallido - + Transaction rejected by network Transacción rechazada por la red - + Payment has been sent to: El pago ha sido enviado a: - + Copied address to clipboard Dirección copiada al portapapeles - + Opening Website Abriendo Sitio Web - + Add a personal note Añadir una nota personal - + Close Cerrar @@ -135,12 +135,12 @@ FiatTxInfo - + Value now Valor ahora - + Value then Valor entonces @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Monedero inicial - - + + Today Hoy - - + + Yesterday Ayer - + Now timestamp Ahora - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp Hace ½ hora - + %1 hours ago timestamp @@ -234,22 +234,35 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Bloque de Bitcoin Cash minado. Altura: %1 - + + tBCH (testnet4) block mined: %1 + Bloque de tBCH (testnet4) minado: %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Bloque de Bitcoin Cash minado. Altura: %1 + + + tBCH (testnet4) block mined: %1 Bloque de tBCH (testnet4) minado: %1 - + Mute Silenciar - + New Transactions dialog-title @@ -258,21 +271,21 @@ - + %1 new transactions across %2 wallets found (%3) %1 nuevas transacciones entre %2 monederos encontradas (%3) - + A payment of %1 has been sent Un pago de %1 ha sido enviado - + %1 new transactions found (%2) - - %1 nuevas transacciones encontradas (%2) + %1 nuevas transacciones encontradas (%2) + %1 new transactions found (%2) @@ -331,22 +344,12 @@ QRScanner - - Paste - Pegar - - - - Failed - Fallido - - - + Instant Pay limit is %1 El límite de pago instantáneo es %1 - + Selected wallet: '%1' Monedero seleccionado: '%1' @@ -370,7 +373,7 @@ TextPasteDecorator - + Paste Pegar @@ -378,14 +381,14 @@ Wallet - - + + Change #%1 Cambio #%1 - - + + Main #%1 Principal #%1 @@ -442,27 +445,27 @@ WalletHistoryModel - + Today Hoy - + Yesterday Ayer - + Earlier this week Anteriormente en esta semana - + This week Esta semana - + Earlier this month Anteriormente este mes diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts index 02d76e4..da23cb7 100644 --- a/translations/floweepay-common_ha.ts +++ b/translations/floweepay-common_ha.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Baya kan na'ura - + Wallet: Up to date Wallet: Na zamani - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Bayan: %1 kwanaki @@ -31,17 +31,17 @@ - + Up to date Na zamani - + Updating Sabontawa - + Still %1 hours behind Har yanzu %1 a baya @@ -79,47 +79,47 @@ BroadcastFeedback - + Sending Payment Aika Biyan Kuɗi - + Payment Sent An aika Biya - + Failed Ba a yi nasara ba - + Transaction rejected by network hanyar sadarwa ta ƙi ciniki - + Payment has been sent to: An aika biyan kuɗi zuwa: - + Copied address to clipboard Adireshin da aka kwafi zuwa allo - + Opening Website Buɗe Yanar Gizon - + Add a personal note Ƙara bayanin kula na sirri - + Close Kulle @@ -135,12 +135,12 @@ FiatTxInfo - + Value now Daraja yanzu - + Value then Daraja zuwa ga @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Asusu na farko - - + + Today Yau - - + + Yesterday Jiya - + Now timestamp Yanzu - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp ½ awa da ya wuce - + %1 hours ago timestamp @@ -234,41 +234,54 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 - + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) toshe ma'adinai: %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 + + + tBCH (testnet4) block mined: %1 tBCH (testnet4) toshe ma'adinai: %1 - + Mute Shiru - + New Transactions dialog-title - - Sabbin Ma'amaloli + Sabbin Ma'amaloli + New Transactions - + %1 new transactions across %2 wallets found (%3) %1 sabbin ma'amaloli a cikin %2 asusun da aka samu (%3) - + A payment of %1 has been sent An aika biyan kuɗi na %1 - + %1 new transactions found (%2) %1 sababbin ma'amaloli akasamu (%2) @@ -331,22 +344,12 @@ QRScanner - - Paste - Wallafa - - - - Failed - Ba a yi nasara ba - - - + Instant Pay limit is %1 Iyakar Biyan Nan take shine %1 - + Selected wallet: '%1' Asusun da aka zaɓa: '%1' @@ -370,7 +373,7 @@ TextPasteDecorator - + Paste Wallafa @@ -378,14 +381,14 @@ Wallet - - + + Change #%1 Chanji #%1 - - + + Main #%1 Mafarin #%1 @@ -442,27 +445,27 @@ WalletHistoryModel - + Today Yau - + Yesterday Jiya - + Earlier this week A farkon wannan makon - + This week Wannan Makon - + Earlier this month A farkon wannan watan diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index eb6d317..ee0a5f9 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Portemonnee: gesynchroniseerd - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days %1 dag oude data @@ -31,17 +31,17 @@ - + Up to date Is volledig bijgewerkt - + Updating Aan het bijwerken - + Still %1 hours behind Nog één uur @@ -79,47 +79,47 @@ BroadcastFeedback - + Sending Payment Betaling wordt verzonden - + Payment Sent Betaling Verzonden - + Failed Mislukt - + Transaction rejected by network Transactie afgewezen door het netwerk - + Payment has been sent to: Betaling is verzonden naar: - + Copied address to clipboard Adres gekopieerd naar klembord - + Opening Website Open Website - + Add a personal note Voeg een persoonlijke notitie toe - + Close Sluiten @@ -135,12 +135,12 @@ FiatTxInfo - + Value now Waarde nu - + Value then Waarde toen @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -180,13 +180,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -234,22 +234,35 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Bitcoin Cash-blok gemijnd. Hoogte: %1 - + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) blok gemijnd: %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Bitcoin Cash-blok gemijnd. Hoogte: %1 + + + tBCH (testnet4) block mined: %1 tBCH (testnet4) blok gemijnd: %1 - + Mute Negeren - + New Transactions dialog-title @@ -258,17 +271,17 @@ - + %1 new transactions across %2 wallets found (%3) %1 nieuwe transacties in %2 portemonnees gevonden (%3) - + A payment of %1 has been sent Een betaling van %1 is verzonden - + %1 new transactions found (%2) %1 nieuwe transactie gevonden (%2) @@ -331,22 +344,12 @@ QRScanner - - Paste - Plak - - - - Failed - Mislukt - - - + Instant Pay limit is %1 Directbetalen limiet is %1 - + Selected wallet: '%1' Geselecteerde portemonnee: '%1' @@ -370,7 +373,7 @@ TextPasteDecorator - + Paste Plak @@ -378,14 +381,14 @@ Wallet - - + + Change #%1 Wisselmunt #%1 - - + + Main #%1 Standaard#%1 @@ -442,27 +445,27 @@ WalletHistoryModel - + Today Vandaag - + Yesterday Gisteren - + Earlier this week Eerder deze week - + This week Deze week - + Earlier this month Eerder deze maand diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 6c091f3..625202c 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Rozłączony - + Wallet: Up to date Portfel: Aktualny - + Behind: %1 weeks, %2 days counter on weeks @@ -25,7 +25,7 @@ - + Behind: %1 days W tyle: %1 dzień @@ -35,17 +35,17 @@ - + Up to date Na bieżąco - + Updating Aktualizowanie - + Still %1 hours behind Nadal %1 godzinę w tyle @@ -85,47 +85,47 @@ BroadcastFeedback - + Sending Payment Wysyłanie Płatności - + Payment Sent Płatność Wysłana - + Failed Niepowodzenie - + Transaction rejected by network Transakcja odrzucona przez sieć - + Payment has been sent to: Płatność została wysłana do: - + Copied address to clipboard Adres skopiowany do schowka - + Opening Website Otwieranie Strony - + Add a personal note Dodaj osobistą notatkę - + Close Zamknij @@ -141,12 +141,12 @@ FiatTxInfo - + Value now Wartość teraz - + Value then Wartość wtedy @@ -154,30 +154,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -188,13 +188,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -244,43 +244,56 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Blok Bitcoin Cash został wykopany. Wysokość: %1 - + + tBCH (testnet4) block mined: %1 + Wykopano blok tBCH (testnet4): %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Blok Bitcoin Cash został wykopany. Wysokość: %1 + + + tBCH (testnet4) block mined: %1 Wykopano blok tBCH (testnet4): %1 - + Mute Wycisz - + New Transactions dialog-title - nowa transakcja + Nowe transakcje Nowe transakcje Nowe transakcje Nowe transakcje - + %1 new transactions across %2 wallets found (%3) %1 nowe transakcje w %2 portfelach (%3) - + A payment of %1 has been sent Wysłano płatność w wysokości %1 - + %1 new transactions found (%2) Znaleziono %1 nową transakcję (%2) @@ -345,22 +358,12 @@ QRScanner - - Paste - Wklej - - - - Failed - Niepowodzenie - - - + Instant Pay limit is %1 Limit szybkiej płatności wynosi %1 - + Selected wallet: '%1' Wybrany portfel: '%1' @@ -384,7 +387,7 @@ TextPasteDecorator - + Paste Wklej @@ -392,14 +395,14 @@ Wallet - - + + Change #%1 Reszta #%1 - - + + Main #%1 Główny #%1 @@ -464,27 +467,27 @@ WalletHistoryModel - + Today Dzisiaj - + Yesterday Wczoraj - + Earlier this week Wcześniej w tym tygodniu - + This week W tym tygodniu - + Earlier this month Wcześniej w tym miesiącu diff --git a/translations/floweepay-common_pt.ts b/translations/floweepay-common_pt.ts index 536800b..ef73194 100644 --- a/translations/floweepay-common_pt.ts +++ b/translations/floweepay-common_pt.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Desconectado - + Wallet: Up to date Carteira: atualizada - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Atrasado: %1 dias @@ -31,17 +31,17 @@ - + Up to date Atualizado - + Updating Atualizando - + Still %1 hours behind Ainda %1 horas atrasado @@ -79,47 +79,47 @@ BroadcastFeedback - + Sending Payment Enviando pagamento - + Payment Sent Pagamento enviado - + Failed Failed - + Transaction rejected by network Transação rejeitada pela rede - + Payment has been sent to: Payment has been sent to: - + Copied address to clipboard Copied address to clipboard - + Opening Website Abrindo página - + Add a personal note Adicionar nota pessoal - + Close Fechar @@ -127,38 +127,51 @@ CFIcon - + Coin has been fused for increased anonymity Moeda fundida para maior anonimato + + FiatTxInfo + + + Value now + Value now + + + + Value then + Value then + + FloweePay - + Initial Wallet Carteira inicial - - + + Today Hoje - - + + Yesterday Ontem - + Now timestamp Agora - + %1 minutes ago relative time stamp @@ -167,13 +180,13 @@ - + ½ hour ago timestamp Meia hora atrás - + %1 hours ago timestamp @@ -221,77 +234,95 @@ NotificationManager - + Bitcoin Cash block mined. Height: %1 Bloco Bitcoin Cash minerado. Último bloco: %1 - + + tBCH (testnet4) block mined: %1 + tBCH (testnet4) bloco minerado: %1 + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 + Bloco Bitcoin Cash minerado. Último bloco: %1 + + + tBCH (testnet4) block mined: %1 tBCH (testnet4) bloco minerado: %1 - + Mute Silenciar - + New Transactions dialog-title - Novas transações + New Transactions New Transactions - + %1 new transactions across %2 wallets found (%3) %1Novas transações de %2 carteiras encontradas (%3) - + A payment of %1 has been sent Um pagamento de %1 foi enviado - + %1 new transactions found (%2) - + + %1 novas transações encontradas (%2) %1 novas transações encontradas (%2) - %1 new transactions found (%2) Payment - + Invalid PIN PIN inválido - + + Wallet is locked + Wallet is locked + + + Not enough funds selected for fees Saldo insuficiente para taxas - + Not enough funds in wallet to make payment! Saldo insuficiente para realizar pagamento! - + Transaction too large. Amount selected needs too many coins. Valor muito alto. A quantia selecionada requer moedas demais. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. @@ -299,17 +330,30 @@ PaymentProtocolBip70 - + Payment request unreadable Payment request unreadable - - + + Payment request expired Payment request expired + + QRScanner + + + Instant Pay limit is %1 + Instant Pay limit is %1 + + + + Selected wallet: '%1' + Selected wallet: '%1' + + QRWidget @@ -318,17 +362,33 @@ Copiado para área de transferência + + TextField + + + Paste + Paste + + + + TextPasteDecorator + + + Paste + Paste + + Wallet - - + + Change #%1 Alterar %1 - - + + Main #%1 Main #%1 @@ -336,12 +396,12 @@ WalletCoinsModel - + Unconfirmed Unconfirmed - + %1 hours age, like: hours old @@ -350,7 +410,7 @@ - + %1 days age, like: days old @@ -359,7 +419,7 @@ - + %1 weeks age, like: weeks old @@ -368,7 +428,7 @@ - + %1 months age, like: months old @@ -377,7 +437,7 @@ - + Change #%1 Alterar %1 @@ -385,27 +445,27 @@ WalletHistoryModel - + Today Hoje - + Yesterday Ontem - + Earlier this week Earlier this week - + This week This week - + Earlier this month Earlier this month @@ -413,12 +473,12 @@ WalletSecretsView - + Explanation Explanation - + Coins a / b a) active coin-count. b) historical coin-count. @@ -427,23 +487,33 @@ b) historical coin-count. - - + + Copy Address Copy Address - + + QR of Address + QR of Address + + + Copy Private Key Copy Private Key - + + QR of Private Key + QR of Private Key + + + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signed with Schnorr signatures in the past diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 41e603e..3992d93 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -70,6 +70,7 @@ + Password Passwort @@ -129,27 +130,27 @@ Seed-Phrase - + Seed format Seed-Format - + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -157,7 +158,7 @@ AccountListItem - + Last Transaction Letzte Transaktion @@ -318,102 +319,102 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss NewAccountImportAccount - + Select import method Importmethode auswählen - + Camera Kamera - + OR OR - + Secret as text The seed-phrase or private key Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Address to import Zu importierende Adresse - + + + New Wallet Name + Neuer Geldbörsen Name + + + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - - New Wallet Name - Neuer Geldbörsen Name - - - - + + Start Start - - Password - Passwort - - - - imported wallet password - importiertes Geldbörsen-Passwort - - - + Discover Details online check for wallet details Entdecke Details - + Derivation Ableitung - - Nothing found for seed - Nichts gefunden für Seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Passwort + + + + imported wallet password + importiertes Geldbörsen-Passwort @@ -605,7 +606,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Warning Warnung @@ -661,65 +662,65 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Senden - + Destination Ziel - + Max available The maximum balance available Max. verfügbar - + %1 to %2 summary text to pay X-euro to address M %1 zu %2 - + Enter Bitcoin Cash Address Geben Sie eine Bitcoin Cash Adresse ein - - + + Copy Address Adresse kopieren - + Amount Betrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + Continue Fortfahren - + Cancel Abbrechen - + Coin Selector Coin Selektor - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -728,78 +729,78 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Total Number of coins Gesamt - + Needed Benötigt - + Selected Ausgewählt - + Value Wert - + Locked coins will never be used for payments. Right-click for menu. Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin Coin entsperren - + Lock coin Coin sperren - + Public-comment Öffentlicher Kommentar - + Add a comment you want to include in the transaction, visible for everyone. Fügen Sie einen Kommentar hinzu, den Sie in die Transaktion einfügen möchten, sichtbar für alle. - + Custom message, to be included in the transaction. Benutzerdefinierte Nachricht, die in die Transaktion aufgenommen werden soll. - + Text Text - + Size Größe @@ -880,32 +881,32 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Transaction - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + rejected abgelehnt @@ -1014,22 +1015,22 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. TransactionInfoSmall - + Transaction is rejected Transaktion wurde abgelehnt - + Processing Verarbeite - + Mined Gemined - + %1 blocks ago Confirmations @@ -1038,22 +1039,22 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Fees Gebühren - + Copy transaction-ID Transaktions-ID kopieren - + Holds a token Hält ein Token - + Opening Website Öffne Website @@ -1205,95 +1206,95 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. main - + Activity Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1302,12 +1303,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index 7ed9197..17439fc 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -70,6 +70,7 @@ + Password Contraseña @@ -129,27 +130,27 @@ Frase semilla - + Seed format Formato de semilla - + Derivation Derivación - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. @@ -157,7 +158,7 @@ AccountListItem - + Last Transaction Última transacción @@ -318,102 +319,102 @@ Esto asegura que solo una clave privada tendrá que ser respaldada NewAccountImportAccount - + Select import method Seleccionar método de importación - + Camera Cámara - + OR O - + Secret as text The seed-phrase or private key Secreto como texto - + Unknown word(s) found Palabra(s) desconocidas encontradas - + Address to import Dirección a importar - + + + New Wallet Name + Nuevo nombre de billetera + + + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - - New Wallet Name - Nuevo nombre de billetera - - - - + + Start Comenzar - - Password - Contraseña - - - - imported wallet password - Contraseña del monedero importada - - - + Discover Details online check for wallet details Detalles de Descubre - + Derivation Derivación - - Nothing found for seed - No se encontró nada para la semilla + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Contraseña + + + + imported wallet password + Contraseña del monedero importada @@ -605,7 +606,7 @@ El cambio volverá a la clave importada. - + Warning Advertencia @@ -661,65 +662,65 @@ El cambio volverá a la clave importada. Enviar - + Destination Destino - + Max available The maximum balance available Máximo disponible - + %1 to %2 summary text to pay X-euro to address M %1 a %2 - + Enter Bitcoin Cash Address Introduzca la dirección de Bitcoin Cash - - + + Copy Address Copiar dirección - + Amount Monto - + Max Máx - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + Continue Continuar - + Cancel Cancelar - + Coin Selector Selector de monedas - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -728,78 +729,78 @@ El cambio volverá a la clave importada. - + Total Number of coins Total - + Needed Necesario - + Selected Seleccionado - + Value Valor - + Locked coins will never be used for payments. Right-click for menu. Las monedas bloqueadas nunca se utilizarán para los pagos. Clic derecho para desplegar el menú. - + Age Edad - + Unselect All Deseleccionar Todo - + Select All Seleccionar Todo - + Unlock coin Desbloquear moneda - + Lock coin Bloquear moneda - + Public-comment Comentario-público - + Add a comment you want to include in the transaction, visible for everyone. Agrega un comentario que quieras incluir en la transacción, visible para todos. - + Custom message, to be included in the transaction. Mensaje personalizado, a ser incluido en la transacción. - + Text Texto - + Size Tamaño @@ -880,32 +881,32 @@ El cambio volverá a la clave importada. Transaction - + Miner Reward Recompensa del minero - + Fused Fusionado - + Received Recibido - + Moved Movido - + Sent Enviado - + rejected rechazada @@ -1014,22 +1015,22 @@ El cambio volverá a la clave importada. TransactionInfoSmall - + Transaction is rejected Transacción rechazada - + Processing Procesando - + Mined Minado - + %1 blocks ago Confirmations @@ -1038,22 +1039,22 @@ El cambio volverá a la clave importada. - + Fees Comisiones - + Copy transaction-ID Copiar ID de la transacción - + Holds a token Contiene un token - + Opening Website Abriendo Sitio Web @@ -1205,95 +1206,95 @@ El cambio volverá a la clave importada. main - + Activity Actividad - + Archived wallets do not check for activities. Balance may be out of date. Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado. - + Unarchive Desarchivar - + This wallet needs a password to open. Esta cartera necesita una contraseña para abrirse. - + Password: Contraseña: - + Invalid password Contraseña invalida - + Open Abrir - + Send Enviar - + Receive Recibir - + Balance Balance - + Main balance (money), non specified Principal - + Unconfirmed balance (money) Sin confirmar - + Immature balance (money) Sin madurar - + 1 BCH is: %1 1 BCH es: %1 - + Network status Estado de la red - + Offline Sin conexión - + Add Bitcoin Cash wallet Añadir monedero de Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1302,12 +1303,12 @@ El cambio volverá a la clave importada. - + Preparing... Preparando... - + QR-Scan Escaneo de QR diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts index bee17f7..a88a07d 100644 --- a/translations/floweepay-desktop_ha.ts +++ b/translations/floweepay-desktop_ha.ts @@ -70,6 +70,7 @@ + Password Kwadon shiga @@ -129,27 +130,27 @@ Jimlar iri - + Seed format Tsarin iri - + Derivation Samo asali - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Wannan asusun na tsare da almar sirri (pin-to-pay). Don ganin bayanan ciki akwai buƙatar samar da kalmar wucewa. @@ -157,7 +158,7 @@ AccountListItem - + Last Transaction Ciniki na ƙarshe @@ -317,101 +318,101 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - + Select import method Select import method - + Camera Camera - + OR Ko - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Address to import Address to import - + + + New Wallet Name + New Wallet Name + + + Force Single Address Tilasta Adireshi Guda - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - - New Wallet Name - New Wallet Name - - - - + + Start Start - - Password - Kwadon shiga - - - - imported wallet password - imported wallet password - - - + Discover Details online check for wallet details Discover Details - + Derivation Samo asali - - Nothing found for seed - Nothing found for seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Kwadon shiga + + + + imported wallet password + imported wallet password @@ -603,7 +604,7 @@ Change will come back to the imported key. - + Warning Gargaɗi @@ -659,65 +660,65 @@ Change will come back to the imported key. Aika - + Destination Madakata - + Max available The maximum balance available Mafi girman samuwa - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Shigar da Adireshin Kuɗi na Bitcoin - - + + Copy Address Kwafi Adireshi - + Amount Adadi - + Max Matsakaici - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + Continue Ci gaba - + Cancel Soke - + Coin Selector Zaɓin Tsabar kudi - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -726,78 +727,78 @@ Change will come back to the imported key. - + Total Number of coins Jimilla - + Needed Ake Bukata - + Selected An zaɓa - + Value Daraja - + Locked coins will never be used for payments. Right-click for menu. Kullallun tsabar kudi ba za a taɓa amfani da su don biyan kuɗi ba. Danna-dama don menu. - + Age Shekaru - + Unselect All Cire Zaɓi Duk - + Select All Zaɓi Duk - + Unlock coin Buɗe tsabar kudi - + Lock coin Kulle tsabar kudi - + Public-comment Public-comment - + Add a comment you want to include in the transaction, visible for everyone. Add a comment you want to include in the transaction, visible for everyone. - + Custom message, to be included in the transaction. Custom message, to be included in the transaction. - + Text Text - + Size Size @@ -878,32 +879,32 @@ Change will come back to the imported key. Transaction - + Miner Reward Miner Reward - + Fused Fused - + Received Received - + Moved Moved - + Sent Sent - + rejected rejected @@ -1012,22 +1013,22 @@ Change will come back to the imported key. TransactionInfoSmall - + Transaction is rejected Transaction is rejected - + Processing Processing - + Mined Mined - + %1 blocks ago Confirmations @@ -1036,22 +1037,22 @@ Change will come back to the imported key. - + Fees Fees - + Copy transaction-ID Kwafi shaidar ma'amala-ID - + Holds a token Holds a token - + Opening Website Buɗe Yanar Gizon @@ -1203,95 +1204,95 @@ Change will come back to the imported key. main - + Activity Ayyuka - + Archived wallets do not check for activities. Balance may be out of date. Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya tsufa. - + Unarchive Cire Takardu - + This wallet needs a password to open. Wannan asusun tana buƙatar kalmar sirri don buɗewa. - + Password: Kalmar wucewa: - + Invalid password Kalmar shiga mara inganci - + Open Bude - + Send Aika - + Receive Karɓa - + Balance Sauran kudi - + Main balance (money), non specified Babban - + Unconfirmed balance (money) Ba'a tabbatar ba - + Immature balance (money) Rashin cika - + 1 BCH is: %1 1 BCH shi ne: %1 - + Network status Matsayin hanyar sadarwa - + Offline Baya kan na'ura - + Add Bitcoin Cash wallet Sanya asusun Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1300,12 +1301,12 @@ Change will come back to the imported key. - + Preparing... Shiryawa... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index db63d61..6e5fc32 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -70,6 +70,7 @@ + Password Wachtwoord @@ -129,27 +130,27 @@ Herstelzin - + Seed format Herstelzin formaat - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -157,7 +158,7 @@ AccountListItem - + Last Transaction Laatste Transactie @@ -318,102 +319,102 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt NewAccountImportAccount - + Select import method Kies import-methode - + Camera Kamera - + OR OF - + Secret as text The seed-phrase or private key Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Address to import Te importeren adres - + + + New Wallet Name + Nieuwe naam Portemonnee + + + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - - New Wallet Name - Nieuwe naam Portemonnee - - - - + + Start Start - - Password - Wachtwoord - - - - imported wallet password - Wachtwoord geïmporteerde portemonnee - - - + Discover Details online check for wallet details Vind de details - + Derivation Derivatie - - Nothing found for seed - Niets gevonden voor herstelzin + + Nothing found for seed. Does it have a password? + Niets gevonden voor herstelzin. Behoeft het een wachtwoord? + + + + Password + Wachtwoord + + + + imported wallet password + Wachtwoord geïmporteerde portemonnee @@ -605,7 +606,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Warning Waarschuwing @@ -661,65 +662,65 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Verstuur - + Destination Bestemming - + Max available The maximum balance available Max. beschikbaar - + %1 to %2 summary text to pay X-euro to address M %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - - + + Copy Address Kopieer adres - + Amount Bedrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Cancel Afbreken - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -728,78 +729,78 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt - + Public-comment Publiek commentaar - + Add a comment you want to include in the transaction, visible for everyone. Voeg een opmerking toe die u wilt opnemen in de transactie, zichtbaar voor iedereen. - + Custom message, to be included in the transaction. Speciaal bericht dat wordt opgenomen in de transactie. - + Text Tekst - + Size Grootte @@ -880,32 +881,32 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Transaction - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + rejected afgewezen @@ -1014,22 +1015,22 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. TransactionInfoSmall - + Transaction is rejected Transactie geweigerd - + Processing In behandeling - + Mined Gedolven - + %1 blocks ago Confirmations @@ -1038,22 +1039,22 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Fees Kosten - + Copy transaction-ID Kopieer transactie-ID - + Holds a token Heeft een token - + Opening Website Open Website @@ -1205,95 +1206,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1302,12 +1303,12 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... - + QR-Scan QR-Scannen diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index b2976cb..a14be8c 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -70,6 +70,7 @@ + Password Hasło @@ -129,27 +130,27 @@ Seed - + Seed format Format seeda - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -157,7 +158,7 @@ AccountListItem - + Last Transaction Ostatnia transakcja @@ -320,101 +321,101 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego NewAccountImportAccount - + Select import method Wybierz metodę importu - + Camera Aparat - + OR LUB - + Secret as text The seed-phrase or private key Sekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Address to import Adres do zaimportowania - + + + New Wallet Name + Nazwa nowego portfela + + + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - - New Wallet Name - Nazwa nowego portfela - - - - + + Start Rozpocznij - - Password - Hasło - - - - imported wallet password - hasło do importowanego portfela - - - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Derywacja - - Nothing found for seed - Nic nie znaleziono dla klucza seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Hasło + + + + imported wallet password + hasło do importowanego portfela @@ -606,7 +607,7 @@ Change will come back to the imported key. - + Warning Uwaga! @@ -662,65 +663,65 @@ Change will come back to the imported key. Wyślij - + Destination Odbiorca - + Max available The maximum balance available Maksymalnie dostępne - + %1 to %2 summary text to pay X-euro to address M %1 do %2 - + Enter Bitcoin Cash Address Wprowadź adres Bitcoin Cash - - + + Copy Address Skopiuj Adres - + Amount Kwota - + Max Maks. - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + Continue Kontynuuj - + Cancel Anuluj - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -731,78 +732,78 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę - + Public-comment Publiczny komentarz - + Add a comment you want to include in the transaction, visible for everyone. Dodaj komentarz, który chcesz zawrzeć w transakcji, widoczny dla wszystkich. - + Custom message, to be included in the transaction. Komunikat użytkownika, który ma być zawarty w transakcji. - + Text Tekst - + Size Rozmiar @@ -883,32 +884,32 @@ Change will come back to the imported key. Transaction - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymano - + Moved Przeniesiono - + Sent Wysłano - + rejected odrzucona @@ -1019,22 +1020,22 @@ Change will come back to the imported key. TransactionInfoSmall - + Transaction is rejected Transakcja została odrzucona - + Processing Przetwarzanie - + Mined Wykopano - + %1 blocks ago Confirmations @@ -1045,22 +1046,22 @@ Change will come back to the imported key. - + Fees Opłaty - + Copy transaction-ID Kopiuj ID transakcji - + Holds a token Przechowuje token - + Opening Website Otwieranie Strony @@ -1212,95 +1213,95 @@ Change will come back to the imported key. main - + Activity Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1311,12 +1312,12 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… - + QR-Scan Skanowanie QR diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index ba77f67..c4bfaee 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -30,17 +30,17 @@ ©️ 2020-2024 Tom Zander und Mitwirkende - + Project Home Projekt-Startseite - + With git repository and issues tracker Mit git Repository und Issue Tracker - + Telegram Telegram @@ -91,105 +91,110 @@ Geldbörsen-Seed-Phrase - + + Password + Passwort + + + Seed format Seed-Format - - + + Starting Height height refers to block-height Starthöhe - + Derivation Path Ableitungspfad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + Wallet keys Geldbörsen-Schlüssel - + Show Index toggle to show numbers Index anzeigen - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen - + Addresses and keys Adressen und Schlüssel - + Sync Status Synchronisierungsstatus - + Hide balance in overviews Guthaben in Übersichten ausblenden - + Hide in private mode Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren - + Re-scan Chain Chain neu scannen - + Remove Wallet Geldbörse entfernen @@ -197,22 +202,22 @@ AccountSelectorPopup - + Your Wallets Ihre Geldbörsen - + last active zuletzt aktiv - + Needs PIN to open Benötigt PIN zum Öffnen - + Balance Total Gesamtguthaben @@ -220,7 +225,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -244,31 +249,21 @@ - Default Wallet - Standard-Geldbörse - - - - %1 is used on startup - %1 wird beim Starten verwendet - - - Exit Private Mode Privaten Modus beenden - + Enter Private Mode Privaten Modus aktivieren - + Reveals wallets marked private Zeigt als privat markierte Geldbörsen - + Hides wallets marked private Versteckt als privat markierte Geldbörsen @@ -289,10 +284,23 @@ Erkunden - - ON - Enabled. SHORT TEXT! - ON + + Open + Öffnen + + + + FilterPopup + + + Transactions Filter + Transactions Filter + + + + Only with a comment + This is a statement about a transaction + Only with a comment @@ -341,107 +349,107 @@ Geldbörse importieren - + Select import method Importmethode auswählen - + Camera Kamera - + OR OR - + Secret as text The seed-phrase or private key Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Next Weiter - + Address to import Zu importierende Adresse + + New Wallet Name + Neuer Geldbörsen Name + + + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - - New Wallet Name - Neuer Geldbörsen Name - - - - + + Start Start - - Password - Passwort - - - - imported wallet password - importiertes Geldbörsen-Passwort - - - + Discover Details online check for wallet details Entdecke Details - + Derivation Path Ableitungspfad - - Nothing found for seed - Nichts gefunden für Seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Passwort + + + + imported wallet password + importiertes Geldbörsen-Passwort @@ -548,15 +556,28 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. PIN wurde gesetzt - + Ok Ok + + MainView + + + Explore + Erkunden + + + + Find More + Find More + + MenuOverlay - + Add Wallet Geldbörse hinzufügen @@ -705,37 +726,37 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBetrag bearbeiten - + Invalid QR code Ungültiger QR Code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. - + details details - + Scanned text: <pre>%1</pre> Gescannter Text: <pre>%1</pre> - + Payment description Zahlungsbeschreibung - + Destination Address Zieladresse - + Unlock Wallet Geldbörse entsperren @@ -890,18 +911,46 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussSofortzahlung ist deaktiviert + + SelectDefaultConfigButton + + + Default Wallet + Standard-Geldbörse + + + + %1 is used on startup + %1 wird beim Starten verwendet + + SendTransactionsTab - + Send Senden - + Start Payment Starte Zahlung + + + Scan QR + Scan QR + + + + Paste + Einfügen + + + + Options + Options + SlideToApprove @@ -924,29 +973,30 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussFortfahren - + Moving the world towards a Bitcoin Cash economy Bewege die Welt in Richtung einer Bitcoin Cash Wirtschaft - - Scan me to send funds to your HD wallet - Scanne mich, um Geld an deine HD-Geldbörse zu senden + + Claim a Cash Stamp + Cash Stamp beanspruchen - + + OR OR - - Add a different wallet - Eine andere Geldbörse hinzufügen + + Scan to send to your wallet + Scan to send to your wallet - - Claim a Cash Stamp - Cash Stamp beanspruchen + + Add a different wallet + Eine andere Geldbörse hinzufügen @@ -1112,7 +1162,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussGesendet an - + Transaction Details Transaktionsdetails @@ -1120,32 +1170,32 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss TransactionListItem - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + Rejected Abgelehnt @@ -1153,12 +1203,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss UnlockWidget - + Enter your wallet passcode Geben Sie Ihren Geldbörsen-Zugangscode ein - + Open open wallet with PIN Öffnen diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index 540f88b..b148fb8 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -30,17 +30,17 @@ ©️ 2020-2024 Tom Zander y colaboradores - + Project Home Página del proyecto - + With git repository and issues tracker Con repositorio git y seguimiento de incidencias - + Telegram Telegram @@ -91,105 +91,110 @@ Frase-semilla del monedero - + + Password + Contraseña + + + Seed format Formato de semilla - - + + Starting Height height refers to block-height Altura inicial - + Derivation Path Ruta de Derivación - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que pierda su móvil. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + Wallet keys Llaves del monedero - + Show Index toggle to show numbers Mostrar índice - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Intercambia entre direcciones que otros pueden pagarle y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between unused and used Bitcoin addresses Alterna entre direcciones no usadas y usadas de Bitcoin - + Addresses and keys Direcciones y claves - + Sync Status Estado de la sincronización - + Hide balance in overviews Ocultar balance en vistas generales - + Hide in private mode Ocultar en modo privado - + Unarchive Wallet Desarchivar monedero - + Archive Wallet Archivar monedero - + Re-scan Chain Re-escanear la cadena - + Remove Wallet Eliminar Monedero @@ -197,22 +202,22 @@ AccountSelectorPopup - + Your Wallets Tus monederos - + last active Última vez activo - + Needs PIN to open Necesita PIN para abrir - + Balance Total Balance total @@ -220,7 +225,7 @@ AccountSyncState - + Status: Offline Estado: Desconectado @@ -244,31 +249,21 @@ - Default Wallet - Monedero predeterminado - - - - %1 is used on startup - %1 se utiliza al iniciar - - - Exit Private Mode Salir del modo privado - + Enter Private Mode Entrar en modo privado - + Reveals wallets marked private Revela monederos marcados como privados - + Hides wallets marked private Oculta monederos marcados como privados @@ -289,10 +284,23 @@ Explorar - - ON - Enabled. SHORT TEXT! - ENCENDIDO + + Open + Abrir + + + + FilterPopup + + + Transactions Filter + Transactions Filter + + + + Only with a comment + This is a statement about a transaction + Only with a comment @@ -341,107 +349,107 @@ Importar monedero - + Select import method Seleccionar método de importación - + Camera Cámara - + OR O - + Secret as text The seed-phrase or private key Secreto como texto - + Unknown word(s) found Palabra(s) desconocidas encontradas - + Next Siguiente - + Address to import Dirección a importar + + New Wallet Name + Nuevo nombre de billetera + + + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - - New Wallet Name - Nuevo nombre de billetera - - - - + + Start Comenzar - - Password - Contraseña - - - - imported wallet password - Contraseña del monedero importada - - - + Discover Details online check for wallet details Detalles de Descubre - + Derivation Path Ruta de Derivación - - Nothing found for seed - No se encontró nada para la semilla + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Contraseña + + + + imported wallet password + Contraseña del monedero importada @@ -548,15 +556,28 @@ El cambio volverá a la clave importada. El PIN ha sido establecido - + Ok Aceptar + + MainView + + + Explore + Explorar + + + + Find More + Find More + + MenuOverlay - + Add Wallet Añadir Monedero @@ -705,37 +726,37 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Editar cantidad - + Invalid QR code Código QR inválido - + I don't understand the scanned code. I'm sorry, I can't start a payment. No entiendo el código escaneado. Lo siento, no puedo iniciar un pago. - + details detalles - + Scanned text: <pre>%1</pre> Texto escaneado: <pre>%1</pre> - + Payment description Descripción del pago - + Destination Address Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -890,18 +911,46 @@ Esto asegura que solo una clave privada tendrá que ser respaldada InstaPay está desactivado + + SelectDefaultConfigButton + + + Default Wallet + Monedero predeterminado + + + + %1 is used on startup + %1 se utiliza al iniciar + + SendTransactionsTab - + Send Enviar - + Start Payment Iniciar pago + + + Scan QR + Scan QR + + + + Paste + Pegar + + + + Options + Options + SlideToApprove @@ -924,29 +973,30 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Continuar - + Moving the world towards a Bitcoin Cash economy Moviendo el mundo hacia una economía de Bitcoin Cash - - Scan me to send funds to your HD wallet - Escanéame para enviar fondos a tu monedero HD + + Claim a Cash Stamp + Reclamar un Cash Stamp - + + OR O - - Add a different wallet - Añadir un monedero diferente + + Scan to send to your wallet + Scan to send to your wallet - - Claim a Cash Stamp - Reclamar un Cash Stamp + + Add a different wallet + Añadir un monedero diferente @@ -1112,7 +1162,7 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Enviado a - + Transaction Details Detalles de la transacción @@ -1120,32 +1170,32 @@ Esto asegura que solo una clave privada tendrá que ser respaldada TransactionListItem - + Miner Reward Recompensa del minero - + Fused Fusionado - + Received Recibido - + Moved Movido - + Sent Enviado - + Rejected Rechazado @@ -1153,12 +1203,12 @@ Esto asegura que solo una clave privada tendrá que ser respaldada UnlockWidget - + Enter your wallet passcode Introduzca su código de acceso al monedero - + Open open wallet with PIN Abrir diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts index b612673..dcaa37a 100644 --- a/translations/floweepay-mobile_ha.ts +++ b/translations/floweepay-mobile_ha.ts @@ -30,17 +30,17 @@ © 2020-2024 Tom Zander and contributors - + Project Home Gidan Aikin - + With git repository and issues tracker Tare da ma'ajiyar git da mai bin diddigin batutuwa - + Telegram Telegram @@ -91,105 +91,110 @@ Asusun Jumla iri-iri - + + Password + Kwadon shiga + + + Seed format Tsarin iri - - + + Starting Height height refers to block-height Fara tsawo - + Derivation Path Hanyar Fitowa - + xpub Xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + Wallet keys Maɓallan asusu - + Show Index toggle to show numbers Nuna index - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Addresses and keys Adireshi da maɓallai - + Sync Status Daidaita matsayi - + Hide balance in overviews Boye ma'auni a cikin bayyani - + Hide in private mode Boye a yanayin sirri - + Unarchive Wallet Ma'ajiyin taskoki - + Archive Wallet Ma'ajiyin taskoki - + Re-scan Chain Re-scan Chain - + Remove Wallet Remove Wallet @@ -197,22 +202,22 @@ AccountSelectorPopup - + Your Wallets Asusun ku - + last active aiki na ƙarshe - + Needs PIN to open Akwai buƙatar PIN don buɗewa - + Balance Total Jimlar Ma'auni @@ -220,7 +225,7 @@ AccountSyncState - + Status: Offline Matsayi: Akashe @@ -244,31 +249,21 @@ - Default Wallet - Tsohuwar asusu - - - - %1 is used on startup - Ana amfani da %1 akan farawa - - - Exit Private Mode Fita daga Yanayin Sirri - + Enter Private Mode Shiga Keɓaɓɓen Yanayin - + Reveals wallets marked private Bayyana asusun ɗin da ke da alamar sirri - + Hides wallets marked private Ɓoye asusun ɗin da aka yiwa alama na sirri @@ -289,10 +284,23 @@ Bincika - - ON - Enabled. SHORT TEXT! - Kan + + Open + Bude + + + + FilterPopup + + + Transactions Filter + Transactions Filter + + + + Only with a comment + This is a statement about a transaction + Only with a comment @@ -341,106 +349,106 @@ Ɗauko asusu - + Select import method Select import method - + Camera Camera - + OR Ko - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Next Next - + Address to import Address to import + + New Wallet Name + New Wallet Name + + + Force Single Address Tilasta Adireshi Guda Daya - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - - New Wallet Name - New Wallet Name - - - - + + Start Start - - Password - Kwadon shiga - - - - imported wallet password - imported wallet password - - - + Discover Details online check for wallet details Discover Details - + Derivation Path Hanyar Fitowa - - Nothing found for seed - Nothing found for seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Kwadon shiga + + + + imported wallet password + imported wallet password @@ -547,15 +555,28 @@ Change will come back to the imported key. An saita lambar tsaro - + Ok Ya yi + + MainView + + + Explore + Bincika + + + + Find More + Find More + + MenuOverlay - + Add Wallet Ƙara Asusu @@ -703,37 +724,37 @@ This ensures only one private key will need to be backed up Gyara Adadin - + Invalid QR code Lambar QR mara inganci - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ban fahimci lambar da aka bincika ba. Yi hakuri, ba zan iya fara biya ba. - + details bayanai - + Scanned text: <pre>%1</pre> Rubutun da aka duba: <pre>%1</pre> - + Payment description Bayanin biyan kuɗi - + Destination Address Adireshin Zuwa - + Unlock Wallet Buɗe Asusu @@ -888,18 +909,46 @@ This ensures only one private key will need to be backed up An kashe InstaPay + + SelectDefaultConfigButton + + + Default Wallet + Tsohuwar asusu + + + + %1 is used on startup + %1 is used on startup + + SendTransactionsTab - + Send Aika - + Start Payment Fara Biyan Kuɗi + + + Scan QR + Scan QR + + + + Paste + Wallafa + + + + Options + Options + SlideToApprove @@ -922,29 +971,30 @@ This ensures only one private key will need to be backed up Ci gaba - + Moving the world towards a Bitcoin Cash economy Matsar da duniya zuwa tattalin arzikin Bitcoin Cash - - Scan me to send funds to your HD wallet - Duba ni don aika kuɗi zuwa asusun ku na HD + + Claim a Cash Stamp + Claim a Cash Stamp - + + OR Ko - - Add a different wallet - Ƙara wani asusu na daban + + Scan to send to your wallet + Scan to send to your wallet - - Claim a Cash Stamp - Claim a Cash Stamp + + Add a different wallet + Ƙara wani asusu na daban @@ -1110,7 +1160,7 @@ This ensures only one private key will need to be backed up Sent to - + Transaction Details Cikakken Bayanin Kasuwanci @@ -1118,32 +1168,32 @@ This ensures only one private key will need to be backed up TransactionListItem - + Miner Reward Miner Reward - + Fused Fused - + Received Received - + Moved Moved - + Sent Sent - + Rejected Rejected @@ -1151,12 +1201,12 @@ This ensures only one private key will need to be backed up UnlockWidget - + Enter your wallet passcode Shiga da lambar wucewar asusu - + Open open wallet with PIN Bude diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 3470135..9441a5d 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -30,17 +30,17 @@ © 2020-2024 Tom Zander en bijdragers - + Project Home Startpagina project - + With git repository and issues tracker Met git data en takenlijst - + Telegram Telegram @@ -91,105 +91,110 @@ Herstelzin opslaan - + + Password + Wachtwoord + + + Seed format Herstelzin formaat - - + + Starting Height height refers to block-height Beginhoogte - + Derivation Path Derivatie pad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren - + Re-scan Chain Keten herscannen - + Remove Wallet Portemonnee verwijderen @@ -197,22 +202,22 @@ AccountSelectorPopup - + Your Wallets Uw portemonnees - + last active Laatst actief - + Needs PIN to open Benodigd pincode bij openen - + Balance Total Totale saldo @@ -220,7 +225,7 @@ AccountSyncState - + Status: Offline Status: offline @@ -244,31 +249,21 @@ - Default Wallet - Standaard portemonnee - - - - %1 is used on startup - %1 wordt gebruikt bij start - - - Exit Private Mode Verlaat privémodus - + Enter Private Mode Start privémodus - + Reveals wallets marked private Onthult portefeuilles gemarkeerd als privé - + Hides wallets marked private Verbergt portemonnees gemarkeerd als privé @@ -289,10 +284,23 @@ Ontdek - - ON - Enabled. SHORT TEXT! - AAN + + Open + Open + + + + FilterPopup + + + Transactions Filter + Transacties filter + + + + Only with a comment + This is a statement about a transaction + Alleen met omschrijving @@ -341,107 +349,107 @@ Portemonnee importeren - + Select import method Kies import-methode - + Camera Kamera - + OR OF - + Secret as text The seed-phrase or private key Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Next Volgende - + Address to import Te importeren adres + + New Wallet Name + Nieuwe naam Portemonnee + + + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - - New Wallet Name - Nieuwe naam Portemonnee - - - - + + Start Start - - Password - Wachtwoord - - - - imported wallet password - Wachtwoord geïmporteerde portemonnee - - - + Discover Details online check for wallet details Vind de details - + Derivation Path Derivatie pad - - Nothing found for seed - Niets gevonden voor herstelzin + + Nothing found for seed. Does it have a password? + Niets gevonden voor herstelzin. Behoeft het een wachtwoord? + + + + Password + Wachtwoord + + + + imported wallet password + Wachtwoord geïmporteerde portemonnee @@ -548,15 +556,28 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Pincode gezet - + Ok Ok + + MainView + + + Explore + Ontdek + + + + Find More + Vind meer + + MenuOverlay - + Add Wallet Portemonnee toevoegen @@ -705,37 +726,37 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -890,18 +911,46 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Direct Betalen staat uit + + SelectDefaultConfigButton + + + Default Wallet + Standaard portemonnee + + + + %1 is used on startup + %1 wordt gebruikt bij start + + SendTransactionsTab - + Send Versturen - + Start Payment Betaling starten + + + Scan QR + Scan QR-code + + + + Paste + Plak + + + + Options + Opties + SlideToApprove @@ -924,29 +973,30 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Doorgaan - + Moving the world towards a Bitcoin Cash economy We lopen het pad naar een Bitcoin Cash economie - - Scan me to send funds to your HD wallet - Scan mij om geld naar uw HD-portemonnee te sturen + + Claim a Cash Stamp + Claim een Cash Stamp - + + OR OF - - Add a different wallet - Een andere portemonnee toevoegen + + Scan to send to your wallet + Scan om naar uw portemonnee te verzenden - - Claim a Cash Stamp - Claim een Cash Stamp + + Add a different wallet + Een andere portemonnee toevoegen @@ -1112,7 +1162,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Verstuurd naar - + Transaction Details Transactiedetails @@ -1120,32 +1170,32 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionListItem - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + Rejected Afgewezen @@ -1153,12 +1203,12 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt UnlockWidget - + Enter your wallet passcode Voer pincode voor portemonnee in - + Open open wallet with PIN Open diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 51cb0ea..91dab92 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -30,17 +30,17 @@ ©️ 2020-2024 Tom Zander i współtwórcy - + Project Home Strona projektu - + With git repository and issues tracker Z repozytorium git i trackerem problemów - + Telegram Telegram @@ -91,105 +91,110 @@ Fraza seedowa portfela - + + Password + Hasło + + + Seed format Format seeda - - + + Starting Height height refers to block-height Wysokość początkowa - + Derivation Path Ścieżka derywacji - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + Wallet keys Klucze portfela - + Show Index toggle to show numbers Pokaż indeks - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Ukryj saldo w podsumowaniach - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet Przywróć portfel - + Archive Wallet Zarchiwizuj portfel - + Re-scan Chain Skanuj ponownie - + Remove Wallet Usuń portfel @@ -197,22 +202,22 @@ AccountSelectorPopup - + Your Wallets Twoje portfele - + last active Ostatnio aktywny - + Needs PIN to open Do otwarcia wymagany jest PIN - + Balance Total Saldo Całkowite @@ -220,7 +225,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -244,31 +249,21 @@ - Default Wallet - Domyślny Portfel - - - - %1 is used on startup - %1 jest używany przy starcie - - - Exit Private Mode Wyjdź z trybu prywatnego - + Enter Private Mode Włącz tryb prywatny - + Reveals wallets marked private Ujawnia portfele oznaczone jako prywatne - + Hides wallets marked private Ukrywa portfele oznaczone jako prywatne @@ -289,10 +284,23 @@ Eksploruj - - ON - Enabled. SHORT TEXT! - + + Open + Otwórz + + + + FilterPopup + + + Transactions Filter + Transactions Filter + + + + Only with a comment + This is a statement about a transaction + Only with a comment @@ -341,106 +349,106 @@ Importuj portfel - + Select import method Wybierz metodę importu - + Camera Aparat - + OR LUB - + Secret as text The seed-phrase or private key Sekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Next Dalej - + Address to import Adres do zaimportowania + + New Wallet Name + Nazwa nowego portfela + + + Force Single Address Wymuś pojedynczy adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - - New Wallet Name - Nazwa nowego portfela - - - - + + Start Rozpocznij - - Password - Hasło - - - - imported wallet password - hasło do importowanego portfela - - - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Path Ścieżka derywacji - - Nothing found for seed - Nic nie znaleziono dla klucza seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Hasło + + + + imported wallet password + hasło do importowanego portfela @@ -547,15 +555,28 @@ Change will come back to the imported key. PIN został ustawiony - + Ok OK + + MainView + + + Explore + Eksploruj + + + + Find More + Find More + + MenuOverlay - + Add Wallet Dodaj portfel @@ -704,37 +725,37 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoEdytuj kwotę - + Invalid QR code Nieprawidłowy kod QR - + I don't understand the scanned code. I'm sorry, I can't start a payment. Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. - + details szczegóły - + Scanned text: <pre>%1</pre> Zeskanowany tekst: <pre>%1</pre> - + Payment description Opis płatności - + Destination Address Adres docelowy - + Unlock Wallet Odblokuj portfel @@ -889,18 +910,46 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSzybkie Płatności wyłączone + + SelectDefaultConfigButton + + + Default Wallet + Domyślny Portfel + + + + %1 is used on startup + %1 jest używany przy starcie + + SendTransactionsTab - + Send Wyślij - + Start Payment Rozpocznij płatność + + + Scan QR + Scan QR + + + + Paste + Wklej + + + + Options + Options + SlideToApprove @@ -923,29 +972,30 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoKontynuuj - + Moving the world towards a Bitcoin Cash economy Przesuwamy świat w kierunku ekonomii Bitcoin Cash - - Scan me to send funds to your HD wallet - Zeskanuj mnie, aby wysłać środki do swojego portfela HD + + Claim a Cash Stamp + Odbierz Cash Stamp - + + OR LUB - - Add a different wallet - Dodaj inny portfel + + Scan to send to your wallet + Scan to send to your wallet - - Claim a Cash Stamp - Odbierz Cash Stamp + + Add a different wallet + Dodaj inny portfel @@ -1115,7 +1165,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoWysłano do - + Transaction Details Szczegóły transakcji @@ -1123,32 +1173,32 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego TransactionListItem - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymano - + Moved Przeniesiono - + Sent Wysłano - + Rejected Odrzucono @@ -1156,12 +1206,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego UnlockWidget - + Enter your wallet passcode Wprowadź hasło portfela - + Open open wallet with PIN Otwórz diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index 04c0120..03af020 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -18,6 +18,11 @@ Build Transaction Baue Transaktion + + + Manually select templates + Manually select templates + PayToOthers @@ -99,8 +104,8 @@ unset - indication of empty - ungesetzt + indication of desination not being set + unset @@ -131,17 +136,17 @@ Bitcoin Cash Adresse - + Warning Warnung - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + I am certain Ich bin mir sicher @@ -156,12 +161,12 @@ Ziehen zum Löschen - + Unlock Wallet Geldbörse entsperren - + Prepare Payment... Zahlung wird vorbereitet... diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts index 5975d56..04b16b2 100644 --- a/translations/module-build-transaction_es.ts +++ b/translations/module-build-transaction_es.ts @@ -18,6 +18,11 @@ Build Transaction Construir transacción + + + Manually select templates + Manually select templates + PayToOthers @@ -99,8 +104,8 @@ unset - indication of empty - no establecido + indication of desination not being set + unset @@ -131,17 +136,17 @@ Dirección de Bitcoin Cash - + Warning Advertencia - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + I am certain Estoy seguro @@ -156,12 +161,12 @@ Arrastre para eliminar - + Unlock Wallet Desbloquear Monedero - + Prepare Payment... Preparar pago... diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts index 276afee..1cb6dbc 100644 --- a/translations/module-build-transaction_ha.ts +++ b/translations/module-build-transaction_ha.ts @@ -18,6 +18,11 @@ Build Transaction Gina Ma'amala + + + Manually select templates + Manually select templates + PayToOthers @@ -99,8 +104,8 @@ unset - indication of empty - Rashin saitawa + indication of desination not being set + unset @@ -131,17 +136,17 @@ Adireshin Kuɗi na Bitcoin - + Warning Gargaɗi - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + I am certain Na tabbata @@ -156,12 +161,12 @@ Jawo don gogewa - + Unlock Wallet Buɗe Asusu - + Prepare Payment... Shirya Biya... diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index ac344e8..2d3be16 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -18,6 +18,11 @@ Build Transaction Bouw transactie + + + Manually select templates + Selecteer templates handmatig + PayToOthers @@ -99,8 +104,8 @@ unset - indication of empty - niets + indication of desination not being set + niet ingesteld @@ -131,17 +136,17 @@ Bitcoin Cash-adres - + Warning Waarschuwing - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + I am certain Ik ben zeker @@ -156,12 +161,12 @@ Sleep om te verwijderen - + Unlock Wallet Portemonnee ontgrendelen - + Prepare Payment... Betaling voorbereiden... diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index c5c7350..3393711 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -18,6 +18,11 @@ Build Transaction Utwórz transakcję + + + Manually select templates + Manually select templates + PayToOthers @@ -99,8 +104,8 @@ unset - indication of empty - nie ustawiono + indication of desination not being set + unset @@ -131,17 +136,17 @@ Adres Bitcoin Cash - + Warning Uwaga - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + I am certain Na pewno @@ -156,12 +161,12 @@ Przeciągnij, aby usunąć - + Unlock Wallet Odblokuj portfel - + Prepare Payment... Przygotuj płatność... diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index bb7bc1c..ba59fa5 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -9,18 +9,12 @@ Coins einlesen - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Scanne QR (WIF) um Geld zu finden - - - + Sweeping from address: Einlesen von Adresse: - + Found %1 coins on address. this is a simple number @@ -29,7 +23,7 @@ - + Ignoring %1 tokens. Number of CashTokens @@ -38,48 +32,48 @@ - + Failed to understand QR Konnte QR nicht verstehen - + Indexer results invalid. Please try again. Indexer-Ergebnisse ungültig. Bitte versuchen Sie es erneut. - + Transfer to: Transferieren nach: - + Sending Payment Sende Zahlung - + Payment Sent Zahlung gesendet - + Failed Fehlgeschlagen - + Transaction rejected by network Transaktion wurde vom Netzwerk abgelehnt - + The payment has been sent to: Followed by the address Die Zahlung wurde gesendet an: - + Close Schließen @@ -87,19 +81,28 @@ SendSweepModuleInfo - + Sweep & Send Einlesen & Senden - + Allows sweeping a paper-wallet, moving the contents to your own wallet Ermöglicht das Verschieben einer Papier-Geldbörse und das Verschieben des Inhalts auf Ihre eigene Geldbörse - + Sweep Paper Wallet Papier-Geldbörse einlesen + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + diff --git a/translations/module-send-sweep_es.ts b/translations/module-send-sweep_es.ts index 02a70f1..9554523 100644 --- a/translations/module-send-sweep_es.ts +++ b/translations/module-send-sweep_es.ts @@ -9,18 +9,12 @@ Barrido de monedas - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Escanear QR (WIF) para encontrar fondos - - - + Sweeping from address: Borrando desde dirección: - + Found %1 coins on address. this is a simple number @@ -29,7 +23,7 @@ - + Ignoring %1 tokens. Number of CashTokens @@ -38,48 +32,48 @@ - + Failed to understand QR Error al entender QR - + Indexer results invalid. Please try again. Resultados del indexador no válidos. Por favor, inténtelo de nuevo. - + Transfer to: Transferir a: - + Sending Payment Enviando Pago - + Payment Sent Pago Enviado - + Failed Fallido - + Transaction rejected by network Transacción rechazada por la red - + The payment has been sent to: Followed by the address El pago ha sido enviado a: - + Close Cerrar @@ -87,19 +81,28 @@ SendSweepModuleInfo - + Sweep & Send Barrido y Enviar - + Allows sweeping a paper-wallet, moving the contents to your own wallet Permite barrer una cartera de papel, moviendo el contenido a su propia cartera - + Sweep Paper Wallet Importar billetera de papel + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + diff --git a/translations/module-send-sweep_ha.ts b/translations/module-send-sweep_ha.ts index 4fdee41..43ad4a6 100644 --- a/translations/module-send-sweep_ha.ts +++ b/translations/module-send-sweep_ha.ts @@ -9,18 +9,12 @@ Sweep coins - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Scan QR (WIF) to find funds - - - + Sweeping from address: Sweeping from address: - + Found %1 coins on address. this is a simple number @@ -29,7 +23,7 @@ - + Ignoring %1 tokens. Number of CashTokens @@ -38,48 +32,48 @@ - + Failed to understand QR Failed to understand QR - + Indexer results invalid. Please try again. Indexer results invalid. Please try again. - + Transfer to: Transfer to: - + Sending Payment Aika Biyan Kuɗi - + Payment Sent An aika Biya - + Failed Ba a yi nasara ba - + Transaction rejected by network hanyar sadarwa ta ƙi ciniki - + The payment has been sent to: Followed by the address The payment has been sent to: - + Close Kulle @@ -87,19 +81,28 @@ SendSweepModuleInfo - + Sweep & Send Sweep & Send - + Allows sweeping a paper-wallet, moving the contents to your own wallet Allows sweeping a paper-wallet, moving the contents to your own wallet - + Sweep Paper Wallet Sweep Paper Wallet + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + diff --git a/translations/module-send-sweep_nl.ts b/translations/module-send-sweep_nl.ts index ebd4aef..d5682da 100644 --- a/translations/module-send-sweep_nl.ts +++ b/translations/module-send-sweep_nl.ts @@ -9,18 +9,12 @@ Munten opvegen - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Scan QR (WIF) om geld te vinden - - - + Sweeping from address: Opvegen van adres: - + Found %1 coins on address. this is a simple number @@ -29,7 +23,7 @@ - + Ignoring %1 tokens. Number of CashTokens @@ -38,48 +32,48 @@ - + Failed to understand QR Kon QR niet begrijpen - + Indexer results invalid. Please try again. Indexeren resultaten ongeldig. Probeer het opnieuw. - + Transfer to: Overmaken naar: - + Sending Payment Betaling wordt verzonden - + Payment Sent Betaling Verzonden - + Failed Mislukt - + Transaction rejected by network Transactie afgewezen door het netwerk - + The payment has been sent to: Followed by the address Betaling is verzonden naar: - + Close Sluiten @@ -87,19 +81,28 @@ SendSweepModuleInfo - + Sweep & Send Opvegen & Verzenden - + Allows sweeping a paper-wallet, moving the contents to your own wallet Biedt het opvegen van een papieren portemonnee, en de inhoud verplaatsen naar uw eigen portemonnee - + Sweep Paper Wallet Papieren portemonnee opvegen + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) om geld te vinden + + diff --git a/translations/module-send-sweep_pl.ts b/translations/module-send-sweep_pl.ts index 7427c33..cac8c62 100644 --- a/translations/module-send-sweep_pl.ts +++ b/translations/module-send-sweep_pl.ts @@ -9,18 +9,12 @@ Zgarnij monety - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Zeskanuj QR (WIF), aby znaleźć fundusze - - - + Sweeping from address: Zgarnianie z adresu: - + Found %1 coins on address. this is a simple number @@ -31,7 +25,7 @@ - + Ignoring %1 tokens. Number of CashTokens @@ -42,48 +36,48 @@ - + Failed to understand QR Nie udało się zrozumieć QR - + Indexer results invalid. Please try again. Wyniki indeksacji są nieprawidłowe. Spróbuj ponownie. - + Transfer to: Przekaż do: - + Sending Payment Wysyłanie Płatności - + Payment Sent Płatność Wysłana - + Failed Niepowodzenie - + Transaction rejected by network Transakcja odrzucona przez sieć - + The payment has been sent to: Followed by the address Płatność została wysłana do: - + Close Zamknij @@ -91,19 +85,28 @@ SendSweepModuleInfo - + Sweep & Send Zgarnij i wyślij - + Allows sweeping a paper-wallet, moving the contents to your own wallet Pozwala na zgarnięcie środków z portfela papierowego poprzez przeniesienie ich do własnego portfela. - + Sweep Paper Wallet Wyczyść papierowy portfel + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds + + -- 2.54.0 From 560b62960b7ea8dd3c5c31b6975fdd06bde3d637 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Jan 2025 17:40:51 +0100 Subject: [PATCH 1118/1428] Add NewIndicatorProvider This provides a backend for a 'new indicator' on menus we want to entice users to open. --- guis/mobile/AccountPageListItem.qml | 1 + guis/mobile/TextButton.qml | 38 +++++++++- src/CMakeLists.txt | 1 + src/NewIndicatorProvider.cpp | 109 ++++++++++++++++++++++++++++ src/NewIndicatorProvider.h | 53 ++++++++++++++ src/main.cpp | 6 +- 6 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/NewIndicatorProvider.cpp create mode 100644 src/NewIndicatorProvider.h diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 2e1bc30..050a25e 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -78,6 +78,7 @@ QQC2.Control { Layout.fillWidth: true enabled: root.account.isDecrypted onClicked: thePile.push(root.account.isHDWallet ? hdBackupDetails : backupDetails); + buttonId: 92387 Component { id: hdBackupDetails diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 8c74285..eeb719e 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -23,7 +23,7 @@ Item { id: root width: parent.width implicitWidth: label.x + Math.max(label.contentWidth, smallLabel.contentWidth) - + ((rightIcon.implicitWidth === 0) ? 0 : (10 + + rightIcon.implicitWidth)) + + ((rightIcon.implicitWidth === 0) ? 0 : (10 + rightIcon.implicitWidth)) height: label.height + (smallLabel.text === "" ? 0 : smallLabel.height + 6) + 20 baselineOffset: label.baselineOffset + 10 Layout.fillWidth: true @@ -38,6 +38,7 @@ Item { property string imageSource: "" property int imageWidth: 20 property int imageHeight: 20 + property int buttonId: 0 /// Add an icon before the label property string iconSource: "" @@ -59,6 +60,32 @@ Item { wrapMode: Text.WordWrap color: enabled ? palette.windowText : palette.brightText } + Rectangle { + id: newIndicator + color: Pay.useDarkSkin ? "#0b45a2" : "#0d61b4" + width: 14 + height: 14 + radius: 7 + x: { + if (!visible) + return 0; + var w = label.contentWidth; + return w - 1; + } + y: { + if (!visible) + return 0; + + return label.y + label.baselineOffset - height / 2 - label.font.pixelSize * 0.8 + } + + visible: { + var bID = root.buttonId; + if (bID === 0) + return false; + return NewIndicator.isNew(bID); + } + } Flowee.Label { id: smallLabel anchors.top: label.bottom @@ -72,7 +99,14 @@ Item { } MouseArea { anchors.fill: parent - onClicked: root.clicked() + onClicked: { + var bID = root.buttonId; + if (bID !== 0) { + NewIndicator.seen(bID); + newIndicator.visible = false; + } + root.clicked() + } } Loader { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ffdefd..00a00ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,7 @@ set (PAY_SOURCES IndexerServices.cpp MenuModel.cpp NetDataProvider.cpp + NewIndicatorProvider.cpp NewWalletConfig.cpp NotificationManager.cpp Payment.cpp diff --git a/src/NewIndicatorProvider.cpp b/src/NewIndicatorProvider.cpp new file mode 100644 index 0000000..bacbea5 --- /dev/null +++ b/src/NewIndicatorProvider.cpp @@ -0,0 +1,109 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NewIndicatorProvider.h" + +#include +#include + +constexpr const char *STARTDATE_KEY = "first-start"; +constexpr const char *SEEN = "seen"; + +namespace { +// these enums are reused by the actual QML pages. We don't export +// them or anything, so they are just random numbers to the reader... +enum Pages { + BackupPage = 92387, + WalletsPage = 32948, +}; +} + +NewIndicatorProvider::NewIndicatorProvider(QObject *parent) + : QObject(parent) +{ + m_pages.insert({WalletsPage, NewForEveryone}); + m_pages.insert({BackupPage, NewForEveryone}); + + load(); + if (m_appInstallDate == 0) { + // update this one when a new enum is introduced + m_appInstallDate = NewSince202501; + m_dirty = true; + } +} + +NewIndicatorProvider::~NewIndicatorProvider() +{ + save(); +} + +void NewIndicatorProvider::load() +{ + assert(m_seenPages.empty()); + QSettings appConfig; + appConfig.beginGroup("NewIndicator"); + m_appInstallDate = appConfig.value(STARTDATE_KEY, 0).toInt(); + auto seen = appConfig.value(SEEN, QString()).toString(); + for (const auto &sid : seen.split(' ')) { + bool ok; + int id = sid.toInt(&ok); + if (ok) + m_seenPages.insert(id); + } +} + +void NewIndicatorProvider::save() +{ + if (!m_dirty) + return; + + QSettings appConfig; + appConfig.beginGroup("NewIndicator"); + appConfig.setValue(STARTDATE_KEY, m_appInstallDate); + QStringList seenList; + for (auto i = m_seenPages.begin(); i != m_seenPages.end(); ++i) { + seenList.append(QString::number(*i)); + } + appConfig.setValue(SEEN, seenList.join(' ')); + m_dirty = false; +} + +bool NewIndicatorProvider::isNew(int id) const +{ + auto iter = m_seenPages.find(id); + if (iter != m_seenPages.end()) + return false; + auto i = m_pages.find(id); + if (i == m_pages.end()) + return false; + + return i->second == NewForEveryone || i->second >= m_appInstallDate; +} + +void NewIndicatorProvider::seen(int id) +{ + auto i = m_pages.find(id); + if (m_pages.end() == i) // we don't care + return; + auto iter = m_seenPages.find(id); + if (iter != m_seenPages.end()) // already seen + return; + m_seenPages.insert(id); + m_dirty = true; + // save changes after 45 secs. + QTimer::singleShot(45 * 1000, this, SLOT(save())); +} diff --git a/src/NewIndicatorProvider.h b/src/NewIndicatorProvider.h new file mode 100644 index 0000000..4542fd1 --- /dev/null +++ b/src/NewIndicatorProvider.h @@ -0,0 +1,53 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NEWINDICATORPROVIDER_H +#define NEWINDICATORPROVIDER_H + +#include +#include +#include + +class NewIndicatorProvider : public QObject +{ + Q_OBJECT +public: + explicit NewIndicatorProvider(QObject *parent = nullptr); + ~NewIndicatorProvider(); + + Q_INVOKABLE bool isNew(int id) const; + Q_INVOKABLE void seen(int id); + +public slots: + void save(); + +private: + void load(); + + enum PageNewNess { + NewForEveryone, ///< everyone should have a new indicator once. + // Users that started using the software after YYYYMM date won't see a new indicator. + NewSince202501, + }; + + int m_appInstallDate = 0; + std::map m_pages; + std::set m_seenPages; + bool m_dirty = false; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index ef43119..210038d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include "BitcoinValue.h" #include "FloweePay.h" #include "NewWalletConfig.h" +#include "NewIndicatorProvider.h" #include "PriceDataProvider.h" #include "Payment.h" #include "TransactionInfo.h" @@ -126,6 +127,9 @@ int main(int argc, char *argv[]) qmlRegisterType("Flowee.org.pay", 1, 0, "TransactionInfo"); qmlRegisterType("Flowee.org.pay", 1, 0, "PaymentRequest"); qmlRegisterUncreatableType("Flowee.org.pay", 1, 0, "FloweePay", ""); + + NewIndicatorProvider newIndicatorProvider; + MenuModel menuModel(&modules); QQmlApplicationEngine engine; #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) // quit on error in the QMLs @@ -141,9 +145,9 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("Intent", &paymentIntent); engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); - MenuModel menuModel(&modules); engine.rootContext()->setContextProperty("MenuModel", &menuModel); engine.rootContext()->setContextProperty("ModuleManager", &modules); + engine.rootContext()->setContextProperty("NewIndicator", &newIndicatorProvider); qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); #ifndef NO_MULTIMEDIA -- 2.54.0 From edcf1a403bfa4b915637c429f25474dc4ff4b458 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 3 Jan 2025 17:46:56 +0100 Subject: [PATCH 1119/1428] Add wallet/backup new indicator --- guis/mobile/MenuOverlay.qml | 3 ++- src/MenuModel.cpp | 17 ++++++++++------- src/MenuModel.h | 7 ++++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 8f8f282..4f19fba 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -184,6 +184,7 @@ Item { TextButton { text: model.name pageButton: true + buttonId: model.id onClicked: { var target = model.target if (target !== "") { diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index fc48db5..a46edc0 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,11 +24,11 @@ MenuModel::MenuModel(ModuleManager *mm) m_moduleManager(mm) { assert(mm); - m_baseItems.append({tr("Explore"), "./ExploreModules.qml"}); - m_baseItems.append({tr("Settings"), "./GuiSettings.qml"}); - m_baseItems.append({tr("Security"), "./LockApplication.qml"}); - m_baseItems.append({tr("About"), "./About.qml"}); - m_baseItems.append({tr("Wallets"), "AccountsList.qml"}); + m_baseItems.append({tr("Explore"), "./ExploreModules.qml", 0}); + m_baseItems.append({tr("Settings"), "./GuiSettings.qml", 0}); + m_baseItems.append({tr("Security"), "./LockApplication.qml", 0}); + m_baseItems.append({tr("About"), "./About.qml", 0}); + m_baseItems.append({tr("Wallets"), "AccountsList.qml", 32948}); initData(); connect (m_moduleManager, &ModuleManager::mainMenuSectionsChanged, this, [=]() { @@ -57,6 +57,8 @@ QVariant MenuModel::data(const QModelIndex &index, int role) const return item.name; case Target: return item.target; + case ButtonId: + return item.buttonId; } return QVariant(); } @@ -66,6 +68,7 @@ QHash MenuModel::roleNames() const QHash answer; answer[Name] = "name"; answer[Target] = "target"; + answer[ButtonId] = "id"; return answer; } @@ -82,7 +85,7 @@ void MenuModel::initData() // from plugins for (auto module : m_moduleManager->mainMenuSections()) { - m_data.append( { module->text(), module->startQMLFile() } ); + m_data.append( { module->text(), module->startQMLFile(), 0 } ); } // and the rest. diff --git a/src/MenuModel.h b/src/MenuModel.h index 8644337..9f8bb0c 100644 --- a/src/MenuModel.h +++ b/src/MenuModel.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,18 +31,19 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - private: void initData(); enum Roles { Name, - Target + Target, + ButtonId }; struct MenuItem { QString name; QString target; // the QML component to load + int buttonId; // for the NewIndicatorProvider }; QList m_baseItems; -- 2.54.0 From 1a93cce1408628dedff6211718106a6f95b82edd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 4 Jan 2025 11:58:56 +0100 Subject: [PATCH 1120/1428] Imported German translations made on CrowdIn --- translations/floweepay-common_de.ts | 4 ++-- translations/floweepay-desktop_de.ts | 2 +- translations/floweepay-mobile_de.ts | 14 +++++++------- translations/module-build-transaction_de.ts | 4 ++-- translations/module-send-sweep_de.ts | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 34d0610..00178f9 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -283,9 +283,9 @@ %1 new transactions found (%2) - + + %1 neue Transaktionen gefunden (%2) %1 neue Transaktionen gefunden (%2) - %1 new transactions found (%2) diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 3992d93..c7113cd 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -404,7 +404,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Nichts für Seed gefunden. Hat es ein Passwort? diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index c4bfaee..24efafe 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -294,13 +294,13 @@ Transactions Filter - Transactions Filter + Transaktions-Filter Only with a comment This is a statement about a transaction - Only with a comment + Nur mit einem Kommentar @@ -439,7 +439,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Nichts für Seed gefunden. Hat es ein Passwort? @@ -571,7 +571,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Find More - Find More + Finde mehr @@ -939,7 +939,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Scan QR - Scan QR + Scan QR @@ -949,7 +949,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Options - Options + Optionen @@ -991,7 +991,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Scan to send to your wallet - Scan to send to your wallet + Scannen um an Ihre Geldbörse zu senden diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index 03af020..e7de5dd 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -21,7 +21,7 @@ Manually select templates - Manually select templates + Templates manuell auswählen @@ -105,7 +105,7 @@ unset indication of desination not being set - unset + nicht eingestellt diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index ba59fa5..ab38e84 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -102,7 +102,7 @@ Scan QR (WIF) to find funds Please note that WIF and QR are names - Scan QR (WIF) to find funds + Scanne QR (WIF) um Geld zu finden -- 2.54.0 From 22fe9d1037a83cab9bee448876e37a17d465c0dc Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Jan 2025 14:39:40 +0100 Subject: [PATCH 1121/1428] Fix stray tx in popup --- guis/mobile/PopupOverlay.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 467e2d6..87eb3cc 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +63,7 @@ FocusScope { onVisibleChanged: { if (!visible) { // closing loader.sourceComponent = undefined; + overlayLoader.sourceComponent = undefined; } root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } -- 2.54.0 From fa4c49479deca75f69dc36a8397acd6365bb52b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Jan 2025 14:56:14 +0100 Subject: [PATCH 1122/1428] Clarify method name. --- src/Wallet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.h b/src/Wallet.h index d466d19..0e9632e 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -173,7 +173,7 @@ public: PrivateKey key; SignatureType sigType; }; - /// Fetch UTXO key + /// Fetch private key that can unlock (for spending) the \a ref utxo. PrivKeyData unlockKey(OutputRef ref) const; /** -- 2.54.0 From f06afc535199069963a9ebf4fc6764becf88ef59 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Jan 2025 18:00:20 +0100 Subject: [PATCH 1123/1428] Move allocation of target addresses Instead of pre-allocation, this moves it to just before we actually broadcast. This solves a possible failure of detecting target funds in case the number of transactions is larger than the gap and the user sends only one or two of the later once instead of all. --- modules/big-transfer/QMLTransferManager.cpp | 60 +++++++-------------- modules/big-transfer/QMLTransferManager.h | 8 +-- modules/big-transfer/ShowPrepared.qml | 32 ++--------- 3 files changed, 25 insertions(+), 75 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index 2078532..c4a8bb3 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -34,16 +34,13 @@ void QMLTransferManager::prepare() if (m_prepareRunning) return; assert(m_fromAccount); - assert(m_toAccount); - assert(m_fromAccount != m_toAccount); m_prepareRunning = true; - freeReservations(); // also clears the transactions list. + freePreparedTransactions(); // also clears the transactions list. // the following may take a bit longer if the wallet is large. // do this a tad later in order to avoid the button press not // seeming to do anything. const Wallet *wIn = m_fromAccount->wallet(); - Wallet *wTo = m_toAccount->wallet(); QTimer::singleShot(100, this, [=]() { // we create one transaction for each address (aka private key). // Regardless how many inputs this creates in a transaction. @@ -60,26 +57,15 @@ void QMLTransferManager::prepare() } if (foundOne) { - PreviewTx *prev = new PreviewTx(this); - prev->m_utxos = utxos; + PreviewTx *tx = new PreviewTx(this); + tx->m_utxos = utxos; for (const auto &utxo : utxos) { - prev->m_value += wIn->utxoOutputValue(utxo); + tx->m_value += wIn->utxoOutputValue(utxo); } const auto &fromSecret = i->second; - prev->m_from = new Address(renderAddress(fromSecret.address), - fromSecret.cloakedAddress(), prev); - - int reservation = wTo->reserveUnusedAddress(prev->m_toP2PKH, Wallet::ChangePath); - assert(!m_reservations.contains(reservation)); - m_reservations.insert(reservation); - - const auto toSecrets = wTo->walletSecrets(); - auto outSecret = toSecrets.find(reservation); - assert(outSecret != toSecrets.end()); - assert(prev->m_toP2PKH == outSecret->second.address); - prev->m_to = new Address(renderAddress(prev->m_toP2PKH), - outSecret->second.cloakedAddress(), prev); - m_transactions.append(prev); + tx->m_from = new Address(renderAddress(fromSecret.address), + fromSecret.cloakedAddress(), tx); + m_transactions.append(tx); } } @@ -173,20 +159,13 @@ void QMLTransferManager::setCoinCount(int c) emit coinCountChanged(); } -// we made reservations on the target account, if we don't -// use them we need to free them. -void QMLTransferManager::freeReservations() +void QMLTransferManager::freePreparedTransactions() { - if (m_toAccount == nullptr) - return; - auto *wallet = m_toAccount->wallet(); - for (int privId : std::as_const(m_reservations)) { - wallet->unreserveAddress(privId); + if (!m_transactions.isEmpty()) { + qDeleteAll(m_transactions); + m_transactions.clear(); + emit transactionsChanged(); } - m_reservations.clear(); - qDeleteAll(m_transactions); - m_transactions.clear(); - emit transactionsChanged(); } Tx QMLTransferManager::createTx(PreviewTx *tx) @@ -194,8 +173,11 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) assert(tx); assert(!tx->m_sent); assert(m_fromAccount); + assert(m_toAccount); const Wallet *wIn = m_fromAccount->wallet(); assert(wIn); + Wallet *wTo = m_toAccount->wallet(); + assert(wTo); TransactionBuilder builder; builder.setFeeTarget(1000); @@ -217,7 +199,9 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) } builder.appendOutput(value); - builder.pushOutputPay2Address(tx->m_toP2PKH); + KeyId targetAddress; + wTo->reserveUnusedAddress(targetAddress, Wallet::ChangePath); + builder.pushOutputPay2Address(targetAddress); builder.setOutputFeeSource(0); return builder.createTransaction(); @@ -250,7 +234,7 @@ void QMLTransferManager::setToAccount(AccountInfo *newToAccount) { if (m_toAccount == newToAccount) return; - freeReservations(); + freePreparedTransactions(); m_toAccount = newToAccount; emit toAccountChanged(); emit inputsOkChanged(); @@ -267,6 +251,7 @@ void QMLTransferManager::setFromAccount(AccountInfo *account) return; m_fromAccount = account; emit fromAccountChanged(); + freePreparedTransactions(); int addresses = 0; int totalCoins = 0; if (account->wallet()) { @@ -325,11 +310,6 @@ QObject *PreviewTx::from() const return m_from; } -QObject *PreviewTx::to() const -{ - return m_to; -} - int PreviewTx::inputCount() const { return m_utxos.size(); diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h index d6d3486..6ed4c6a 100644 --- a/modules/big-transfer/QMLTransferManager.h +++ b/modules/big-transfer/QMLTransferManager.h @@ -72,7 +72,7 @@ signals: private: void setAddressCount(int newAddressCount); void setCoinCount(int newCoinCount); - void freeReservations(); + void freePreparedTransactions(); Tx createTx(PreviewTx *tx); AccountInfo *m_fromAccount = nullptr; @@ -81,7 +81,6 @@ private: int m_coinCount = 0; bool m_prepareRunning = false; QList m_transactions; // PreviewTx instances - QSet m_reservations; }; class Address : public QObject { @@ -104,7 +103,6 @@ class PreviewTx : public QObject { Q_PROPERTY(int64_t value READ value CONSTANT FINAL) Q_PROPERTY(int inputCount READ inputCount CONSTANT FINAL) Q_PROPERTY(QObject* from READ from CONSTANT FINAL) - Q_PROPERTY(QObject* to READ to CONSTANT FINAL) Q_PROPERTY(bool sent READ sent NOTIFY sentChanged FINAL) Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged FINAL) public: @@ -114,20 +112,18 @@ public: int64_t value() const; QObject *from() const; - QObject *to() const; int inputCount() const; int64_t m_value = 0; Address *m_from = nullptr; - Address *m_to = nullptr; bool m_sent = false; std::vector m_utxos; - KeyId m_toP2PKH; void setFinalTx(const std::shared_ptr &finalTx); FloweePay::BroadcastStatus broadcastStatus() const; + signals: void sentChanged(); void broadcastStatusChanged(); diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 633fcd8..922800d 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -41,7 +41,7 @@ Mobile.Page { id: delegateRoot width: ListView.view.width color: (index % 2) === 0 ? palette.base : palette.alternateBase - height: topRow.height + 6 + addresses.height + 10 + height: topRow.height + 6 + address.height + 10 property int offset: modelData.sent ? 80 : 0 opacity: { if (helpText.opacity > 0 && (dragItem.x === 0 || dragItem > 260)) @@ -97,19 +97,11 @@ Mobile.Page { } Item { - id: addresses - property bool tall: fromLabel.width + 10 + pointerLabel.width - + 10 + toLabel.width > width; - + id: address width: parent.width y: topRow.height + 6 x: modelData.sent ? 80 : 0 - height: { - var h = 5 + fromLabel.height + 5; - if (tall) - h += pointerLabel.height + toLabel.height; - return h; - } + height: fromLabel.height + 10 opacity: 1 - dragItem.x / 200 Flowee.AddressLabel { @@ -120,24 +112,6 @@ Mobile.Page { txInfo: modelData.from highlight: false } - - Flowee.Label { - id: pointerLabel - text: "⇒" - x: parent.tall ? parent.width / 3 : (fromLabel.x + fromLabel.width + 10); - y: parent.tall ? fromLabel.height + 11 : 5 - } - - Flowee.AddressLabel { - id: toLabel - width: Math.min(implicitWidth, parent.width) - txInfo: modelData.to - highlight: false - showCopyIcon: false - x: parent.tall ? 0 : pointerLabel.x + pointerLabel.width + 10; - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - } } -- 2.54.0 From da74669dbea14613f38fe738dec5e2ee9dcdee73 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 6 Jan 2025 21:47:04 +0100 Subject: [PATCH 1124/1428] Refactors and cleanups. --- guis/Flowee/BigCloseButton.qml | 41 ++++++++++++ guis/Flowee/BroadcastFeedback.qml | 10 +-- guis/Flowee/ProgressCheckIcon.qml | 28 ++++++--- guis/mobile/SendTransactionsTab.qml | 1 - guis/widgets.qrc | 1 + modules/big-transfer/ShowPrepared.qml | 43 ++++--------- modules/send-sweep/SendPage.qml | 89 +++++++-------------------- 7 files changed, 96 insertions(+), 117 deletions(-) create mode 100644 guis/Flowee/BigCloseButton.qml diff --git a/guis/Flowee/BigCloseButton.qml b/guis/Flowee/BigCloseButton.qml new file mode 100644 index 0000000..1491b09 --- /dev/null +++ b/guis/Flowee/BigCloseButton.qml @@ -0,0 +1,41 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Rectangle { + id: root + color: palette.windowText + radius: 10 + width: parent.width - 20 + height: closeButtonLabel.height + 25 + signal clicked; + + Label { + id: closeButtonLabel + text: qsTr("Close") + anchors.centerIn: parent + color: "#7bb688"; + } + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + focus: true + onClicked: root.clicked(); + } +} diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index c8e9866..9a7f67d 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -54,31 +54,23 @@ QQC2.Control { opacity: 1 y: 0 } - PropertyChanges { target: progressIcon; sweepAngle: 90 } StateChangeScript { script: ControlColors.applyLightSkin(root) } }, State { name: "sent1" // sent to only one peer extend: "preparing" when: payment.broadcastStatus === FloweePay.TxSent1 - PropertyChanges { target: progressIcon; sweepAngle: 150 } PropertyChanges { target: root; status: qsTr("Sending Payment") } }, State { name: "waiting" // waiting for possible rejection. when: payment.broadcastStatus === FloweePay.TxWaiting extend: "preparing" - PropertyChanges { target: progressIcon; sweepAngle: 320 } }, State { name: "success" // no reject, great success when: payment.broadcastStatus === FloweePay.TxBroadcastSuccess extend: "preparing" - PropertyChanges { target: progressIcon - sweepAngle: 320 - startAngle: -20 - } - PropertyChanges { target: progressIcon; showCheck: true } PropertyChanges { target: root; status: qsTr("Payment Sent") } }, State { @@ -87,7 +79,6 @@ QQC2.Control { extend: "preparing" StateChangeScript { script: ControlColors.applyDarkSkin(root) } PropertyChanges { target: background; color: "#7f0000" } - PropertyChanges { target: progressIcon; opacity: 0 } PropertyChanges { target: root; status: qsTr("Failed") } PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } } @@ -117,6 +108,7 @@ QQC2.Control { ProgressCheckIcon { id: progressIcon + broadcastStatus: payment.broadcastStatus anchors.horizontalCenter: parent.horizontalCenter } diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml index 2ba8092..7aee289 100644 --- a/guis/Flowee/ProgressCheckIcon.qml +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,17 +17,15 @@ */ import QtQuick import QtQuick.Shapes +import Flowee.org.pay; Item { id: root width: 160 height: 160 - property alias sweepAngle: progressCircle.sweepAngle - property alias startAngle: progressCircle.startAngle - property bool showCheck: false + property int broadcastStatus: FloweePay.NotStarted; - // The 'progress' icon. Shape { id: circleShape width: 160 @@ -45,8 +43,22 @@ Item { centerX: 80 centerY: 80 radiusX: 70; radiusY: 70 - startAngle: -80 - sweepAngle: 0 + startAngle: { + var s = root.broadcastStatus; + if (s === FloweePay.TxBroadcastSuccess) + return -20; + return -80; + } + sweepAngle: { + var s = root.broadcastStatus; + if (s === FloweePay.TxOffered) + return 90; + if (s === FloweePay.TxSent1) + return 150; + if (s === FloweePay.TxWaiting || s === FloweePay.TxBroadcastSuccess) + return 320; + return 10; + } Behavior on sweepAngle { NumberAnimation { duration: 2500 } } Behavior on startAngle { NumberAnimation { } } } @@ -57,7 +69,7 @@ Item { id: checkShape anchors.fill: circleShape smooth: true - opacity: root.showCheck ? 1 : 0 + opacity: root.broadcastStatus === FloweePay.TxBroadcastSuccess ? 1 : 0 ShapePath { id: checkPath strokeWidth: 16 diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 44bc74c..83cc752 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -94,7 +94,6 @@ FocusScope { return; } else if (t === ClipboardHelper.PrivateKey) { -console.log("hahaha"); if (WorkflowStarter.startSweep(text)) return; } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 573aaf7..392ad63 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -21,6 +21,7 @@ Flowee/ArrowPoint.qml Flowee/BitcoinAmountLabel.qml Flowee/BigButton.qml + Flowee/BigCloseButton.qml Flowee/Button.qml Flowee/CardTypeSelector.qml Flowee/CheckBox.qml diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 922800d..54c9910 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -114,15 +114,11 @@ Mobile.Page { } } - Flowee.ProgressCheckIcon { - id: bla - showCheck: true - sweepAngle: 270 - scale: 0.4 + broadcastStatus: modelData.broadcastStatus transformOrigin: Item.TopLeft - // TODO add proper broadcast feedback visible: modelData.sent + scale: 0.4 } } } @@ -136,7 +132,7 @@ Mobile.Page { anchors.horizontalCenter: parent.horizontalCenter y: { var baseCopy = base; - if (baseCopy.width == 1) + if (baseCopy.width === 1) return 0; if (height + 10 < baseCopy.y) // place it above return baseCopy.y - height - 10 @@ -178,33 +174,18 @@ Mobile.Page { anchors.right: parent.right anchors.bottomMargin: 10 } - Rectangle { // TODO merge with the one from the send-sweep module. - id: closeButtonBig - color: "#e8e8e8" - radius: 10 - width: parent.width - 20 - visible: root.transferManager.unsentTxCount === 0 + + Flowee.BigCloseButton { x: 10 - height: closeButtonLabel.height + 25 + visible: root.transferManager.unsentTxCount === 0 anchors.bottom: parent.bottom anchors.bottomMargin: 10 - Flowee.Label { - id: closeButtonLabel - text: qsTr("Close") - anchors.centerIn: parent - color: background.color - } - - MouseArea { - anchors.fill: parent - anchors.margins: -10 - focus: true - onClicked: { - var mainView = thePile.get(0); - mainView.currentIndex = 0; // go to the 'main' tab. - thePile.pop(); - thePile.pop(); - } + enabled: false + onClicked: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); + thePile.pop(); } } } diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index a73a72d..cd9ce43 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -176,58 +176,13 @@ Mobile.Page { property int fiatAmount: bitcoinAmount / 100000000 * Fiat.price property string targetAddress: sweeper.targetAddress - states: [ - State { - name: "notStarted" - when: broadcastFeedback.status === FloweePay.NotStarted - }, - State { - name: "preparing" - when: broadcastFeedback.status === FloweePay.TxOffered - PropertyChanges { target: progressIcon; sweepAngle: 90 } - }, - State { - name: "sent1" // sent to only one peer - extend: "preparing" - when: broadcastFeedback.status === FloweePay.TxSent1 - PropertyChanges { target: progressIcon; sweepAngle: 150 } - PropertyChanges { target: statusLabel; text: qsTr("Sending Payment") } - }, - State { - name: "waiting" // waiting for possible rejection. - when: broadcastFeedback.status === FloweePay.TxWaiting - extend: "preparing" - PropertyChanges { target: progressIcon; sweepAngle: 320 } - }, - State { - name: "success" // no reject, great success - when: broadcastFeedback.status === FloweePay.TxBroadcastSuccess - extend: "preparing" - PropertyChanges { target: progressIcon - sweepAngle: 320 - startAngle: -20 - } - PropertyChanges { target: progressIcon; showCheck: true } - PropertyChanges { target: statusLabel; text: qsTr("Payment Sent") } - }, - State { - name: "rejected" // a peer didn't like our tx - when: broadcastFeedback.status === FloweePay.TxRejected - extend: "preparing" - PropertyChanges { target: background; color: "#7f0000" } - PropertyChanges { target: progressIcon; opacity: 0 } - PropertyChanges { target: statusLabel; text: qsTr("Failed") } - PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } - } - ] - Rectangle { id: background width: parent.width height: parent.height opacity: 0 visible: opacity > 0 - color: "#7bb688" + color: broacastFeedback.status === FloweePay.TxRejected ? "#7f0000" : "#7bb688"; y: height + 2 MouseArea { anchors.fill: parent // eat all mouse events. @@ -238,13 +193,25 @@ Mobile.Page { font.bold: true y: 30 color: "#e8e8e8" + text: { + var s = broadcastFeedback.status; + if (s === FloweePay.TxSent1 || s === FloweePay.TxWaiting) + return qsTr("Sending Payment"); + if (s === FloweePay.TxBroadcastSuccess) + return qsTr("Payment Sent"); + if (s === FloweePay.TxRejected) + return qsTr("Failed"); + return ""; + } } Flowee.ProgressCheckIcon { id: progressIcon + broadcastStatus: broadcastFeedback.status anchors.horizontalCenter: parent.horizontalCenter anchors.top: statusLabel.bottom anchors.topMargin: 20 + opacity: broadcastFeedback.status === FloweePay.TxRejected ? 0 : 1 } Column { @@ -267,6 +234,8 @@ Mobile.Page { y: 10 width: parent.width - 20 horizontalAlignment: Qt.AlignHCenter + text: broadcastFeedback.status === FloweePay.TxRejected + ? qsTr("Transaction rejected by network") : "" } } Flowee.Label { @@ -309,31 +278,15 @@ Mobile.Page { } } - Rectangle { + Flowee.BigCloseButton { id: closeButtonBg - color: statusLabel.color - radius: 10 - width: parent.width - 20 x: 10 - height: closeButtonLabel.height + 25 anchors.bottom: parent.bottom anchors.bottomMargin: 10 - Flowee.Label { - id: closeButtonLabel - text: qsTr("Close") - anchors.centerIn: parent - color: background.color - } - - MouseArea { - anchors.fill: parent - anchors.margins: -10 - focus: true - onClicked: { - var mainView = thePile.get(0); - mainView.currentIndex = 0; // go to the 'main' tab. - thePile.pop(); - } + onClicked: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); } } -- 2.54.0 From 5d93c0efb89c63d139a3917ad0471814af752548 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Jan 2025 00:35:33 +0100 Subject: [PATCH 1125/1428] Clarify api docs. --- src/FloweePay.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index a0e4335..63a1d69 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -301,11 +301,12 @@ public: /** * The best known chainHeight. - * On startup the headerChainHeight is likely going to be outdated - * as much as our accountBlockHeight is. Which would result in bad info. - * For the time we have not yet gotten the headers therefore we will + * This combines the best of headerChainHeight and expectedChainHeight in one, giving the + * user the best know height based on all the info we have. + * On startup the headerChainHeight is likely going to be outdated. Which would result in bad info. + * So before those headers have arrived we therefore will * use the mathematically determined expectedChainHeight. We stop using - * that as soon as we get some movement in the headers one as the expected + * that as soon as we get some movement in the headers, as the expected * one may be off by several blocks. */ int chainHeight(); -- 2.54.0 From dd6f2a4d3ac742a18007a9546477434f73716809 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Jan 2025 00:36:51 +0100 Subject: [PATCH 1126/1428] Show checks on recently sent transactions The idea is that if you send a transaction you may want to see it getting confirmed. This now shows directly in the main UI in an unobtrusive manner with checks. --- guis/desktop/Transaction.qml | 39 +++++++++++++++++++- guis/mobile/AccountHistory.qml | 57 ++++++++++++++++++++++++++++- guis/mobile/TransactionListItem.qml | 3 +- 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index f284495..d27dec2 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -167,6 +167,43 @@ Rectangle { return palette.windowText } } + Item { + id: checks + width: row.width + height: 18 + anchors.right: parent.right + anchors.top: mainLabel.bottom + property int txHeight: model.height + property int confirmations: Pay.chainHeight - txHeight + visible: txHeight === -1 || confirmations < 5 + + Row { + id: row + height: parent.height + spacing: -3 + Flowee.Label { + text: "?" + font.bold: true + font.pixelSize: 14 + color: Pay.useDarkSkin ? "#5d94c7" : mainWindow.floweeBlue + visible: checks.txHeight === -1 + } + + Repeater { + model: { + if (checks.txHeight < 0) + return 0; + var c = checks.confirmations; + return c < 5 ? c : 0; + } + Flowee.Label { + text: "✓" + font.pixelSize: 18 + color: Pay.useDarkSkin ? "#86ffa8" : "green"; + } + } + } + } MouseArea { anchors.fill: parent diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index beb0985..e39ddd5 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls as QQC2 +import QtQuick.Shapes import "../Flowee" as Flowee import "../Utils.js" as Utils import Flowee.org.pay; @@ -230,7 +231,61 @@ ListView { border.color: palette.midlight } + Item { + id: checks // confirmations + width: Math.max(row.width, 14) + height: 12 + anchors.right: parent.right + anchors.rightMargin: 25 + anchors.top: txListItem.baseline + anchors.topMargin: 4 + property int txHeight: model.height + property int confirmations: Pay.chainHeight - txHeight + visible: txHeight === -1 || confirmations < 5 + + Rectangle { + color: "#00000000" + border.width: 1.3 + border.color: Pay.useDarkSkin ? palette.dark : mainWindow.floweeBlue + opacity: 0.6 + width: 14 + height: 13 + radius: 4 + visible: checks.confirmations < 2 || checks.txHeight == -1 + } + Row { + id: row + height: parent.height + + Repeater { + model: { + if (checks.txHeight < 0) + return 0; + var c = checks.confirmations; + return c < 5 ? c : 0; + } + + Shape { + width: 12 + height: 12 + ShapePath { + fillColor: "#00000000" + strokeColor: Pay.useDarkSkin ? "#57a56c" : "green"; + capStyle: ShapePath.FlatCap + joinStyle: ShapePath.RoundJoin + startX: 3; startY: 6 + strokeWidth: 2 + PathLine { x: 5; y: 9 } + PathLine { x: 12; y: 2 } + } + } + } + } + } + + TransactionListItem { + id: txListItem anchors.fill: parent } diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 2be4b90..620ff88 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -23,13 +23,14 @@ Item { : (model.fundsOut - model.fundsIn) property bool isRejected: model.height === -2 // special height as defined by the wallet + baselineOffset: dateLine.y + dateLine.baselineOffset implicitWidth: 360 implicitHeight: 80 Item { id: ruler width: parent.width - height: 6 + height: 4 anchors.verticalCenter: parent.verticalCenter } -- 2.54.0 From a8ad9450f88380a93c2050ba9681ada93b02c849 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 7 Jan 2025 17:08:20 +0100 Subject: [PATCH 1127/1428] Move to next month --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index d1c2e5e..94fabd7 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="33" android:versionName="2025.01.0"> diff --git a/src/main.cpp b/src/main.cpp index 210038d..874a5af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.00.0"); + qapp.setApplicationVersion("2025.01.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 316894416100dccc2127299079ba22d4aa3f21be Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 15:57:54 +0100 Subject: [PATCH 1128/1428] Fix prices position on testnet4 --- guis/mobile/TransactionListItem.qml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 620ff88..6c145b9 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -122,14 +122,14 @@ Item { height: amount.height + 8 anchors.right: parent.right y: { - if (Pay.activityShowsBch || !Pay.isMainChain) // my bottom at parent verticalCenter + if (Pay.activityShowsBch) // my bottom at parent verticalCenter return parent.height / 2 - height return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter } anchors.rightMargin: 20 radius: 6 baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: - visible: !model.isFused + visible: !model.isFused && Pay.isMainChain color: (isMoved || amountBch < 0) ? "#00000000" : (Pay.useDarkSkin ? "#1d6828" : "#97e282") // green background @@ -156,7 +156,7 @@ Item { opacity: price.opacity } Flowee.BitcoinAmountLabel { - visible: price.visible && (Pay.activityShowsBch || !Pay.isMainChain) + visible: !Pay.isMainChain || (price.visible && Pay.activityShowsBch) anchors.right: parent.right anchors.rightMargin: 25 anchors.baseline: dateLine.baseline @@ -165,5 +165,4 @@ Item { fontPixelSize: amount.font.pixelSize * 0.9 colorize: isMoved === false } - } -- 2.54.0 From 150088b1a872595d18ec07e4ad2ddc32bd77148b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 17:35:57 +0100 Subject: [PATCH 1129/1428] BigTransfer: allow splitting. This shows the amount of coins stored on a single address, which we sweep in one transction by default for privacy reasons. This adds the ability of the user to specify how many outputs a single transaction should have when we create it. --- modules/big-transfer/CMakeLists.txt | 1 + modules/big-transfer/NumberModel.cpp | 60 +++++++++++++ modules/big-transfer/NumberModel.h | 40 +++++++++ modules/big-transfer/QMLTransferManager.cpp | 45 ++++++++-- modules/big-transfer/QMLTransferManager.h | 14 +++ modules/big-transfer/ShowPrepared.qml | 99 +++++++++++++++++++++ 6 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 modules/big-transfer/NumberModel.cpp create mode 100644 modules/big-transfer/NumberModel.h diff --git a/modules/big-transfer/CMakeLists.txt b/modules/big-transfer/CMakeLists.txt index 7fd0d16..eadfe25 100644 --- a/modules/big-transfer/CMakeLists.txt +++ b/modules/big-transfer/CMakeLists.txt @@ -18,6 +18,7 @@ project(bigtransfer) set (SOURCES BigTransferModuleInfo.cpp + NumberModel.cpp QMLTransferManager.cpp ) add_library (big-transfer_module_lib STATIC ${SOURCES}) diff --git a/modules/big-transfer/NumberModel.cpp b/modules/big-transfer/NumberModel.cpp new file mode 100644 index 0000000..6afd845 --- /dev/null +++ b/modules/big-transfer/NumberModel.cpp @@ -0,0 +1,60 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "NumberModel.h" + +/* + * This number model has some ranges. + * 1 - 39 as number (count = 39) + * 40 - 100 every 10. (count = 7) + * 130 - 400 every 30 (count = 9) + */ + +NumberModel::NumberModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + + +int NumberModel::rowCount(const QModelIndex &) const +{ + return 56; +} + +QVariant NumberModel::data(const QModelIndex &index_, int role) const +{ + assert(role == Number); + if (role != Number) + return QVariant(); + const int index = index_.row(); + int rc = index; + if (index < 40) + rc = index + 1; + else if (index < 46) + rc = (index - 39) * 10 + 40; + else + rc = (index - 46) * 30 + 130; + + return QVariant::fromValue(rc); +} + +QHash NumberModel::roleNames() const +{ + QHash answer; + answer[Number] = "number"; + return answer; +} diff --git a/modules/big-transfer/NumberModel.h b/modules/big-transfer/NumberModel.h new file mode 100644 index 0000000..f6278ee --- /dev/null +++ b/modules/big-transfer/NumberModel.h @@ -0,0 +1,40 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NUMBERMODEL_H +#define NUMBERMODEL_H + +#include + +class NumberModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit NumberModel(QObject *parent = nullptr); + + enum { + Number = Qt::UserRole, + }; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + +private: +}; + +#endif diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index c4a8bb3..9538551 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -84,7 +84,13 @@ void QMLTransferManager::send(QObject *previewTx) if (data->m_sent) return; // create the actual minable transaction - auto tx = createTx(data); + Tx tx; + try { + tx = createTx(data); + } catch (const std::exception &e) { + logCritical() << e; + return; + } data->m_sent = true; // call to fromWallet to mark outputs locked and save tx. @@ -174,6 +180,8 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) assert(!tx->m_sent); assert(m_fromAccount); assert(m_toAccount); + assert(tx->outputCount() >= 1); + assert(tx->outputCount() < 400); const Wallet *wIn = m_fromAccount->wallet(); assert(wIn); Wallet *wTo = m_toAccount->wallet(); @@ -198,11 +206,16 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse); } - builder.appendOutput(value); - KeyId targetAddress; - wTo->reserveUnusedAddress(targetAddress, Wallet::ChangePath); - builder.pushOutputPay2Address(targetAddress); - builder.setOutputFeeSource(0); + uint64_t perOutput = value / tx->outputCount(); + if (perOutput < 700) + throw std::runtime_error("Too low"); + for (int i = 0; i < tx->outputCount(); ++i) { + builder.appendOutput(perOutput); + KeyId targetAddress; + wTo->reserveUnusedAddress(targetAddress, Wallet::ChangePath); + builder.pushOutputPay2Address(targetAddress); + builder.setOutputFeeSource(0); + } return builder.createTransaction(); } @@ -212,6 +225,13 @@ QList QMLTransferManager::transactions() const return m_transactions; } +NumberModel *QMLTransferManager::outputModel() +{ + if (m_model == nullptr) + m_model = new NumberModel(this); + return m_model; +} + int QMLTransferManager::addressCount() const { return m_addressCount; @@ -332,6 +352,19 @@ FloweePay::BroadcastStatus PreviewTx::broadcastStatus() const return FloweePay::NotStarted; } +int PreviewTx::outputCount() const +{ + return m_outputCount; +} + +void PreviewTx::setOutputCount(int c) +{ + if (m_outputCount == c) + return; + m_outputCount = c; + emit outputCountChanged(); +} + bool PreviewTx::sent() const { return m_sent; diff --git a/modules/big-transfer/QMLTransferManager.h b/modules/big-transfer/QMLTransferManager.h index 6ed4c6a..69979a3 100644 --- a/modules/big-transfer/QMLTransferManager.h +++ b/modules/big-transfer/QMLTransferManager.h @@ -18,6 +18,8 @@ #ifndef TRANSFERMANAGER_H #define TRANSFERMANAGER_H +#include "NumberModel.h" + #include #include @@ -35,6 +37,7 @@ class QMLTransferManager : public QObject Q_PROPERTY(int coinCount READ coinCount NOTIFY coinCountChanged FINAL) Q_PROPERTY(int unsentTxCount READ unsentTxCount NOTIFY unsentTxCountChanged FINAL) Q_PROPERTY(bool inputsOk READ inputsOk NOTIFY inputsOkChanged FINAL) + Q_PROPERTY(NumberModel* outputModel READ outputModel FINAL) public: QMLTransferManager(QObject *parent = nullptr); @@ -60,6 +63,8 @@ public: QList transactions() const; + NumberModel *outputModel(); + signals: void fromAccountChanged(); void toAccountChanged(); @@ -81,8 +86,10 @@ private: int m_coinCount = 0; bool m_prepareRunning = false; QList m_transactions; // PreviewTx instances + NumberModel *m_model = nullptr; }; + class Address : public QObject { Q_OBJECT Q_PROPERTY(QString address READ address CONSTANT FINAL) @@ -98,6 +105,7 @@ private: QString m_cloaked; }; + class PreviewTx : public QObject { Q_OBJECT Q_PROPERTY(int64_t value READ value CONSTANT FINAL) @@ -105,6 +113,7 @@ class PreviewTx : public QObject { Q_PROPERTY(QObject* from READ from CONSTANT FINAL) Q_PROPERTY(bool sent READ sent NOTIFY sentChanged FINAL) Q_PROPERTY(FloweePay::BroadcastStatus broadcastStatus READ broadcastStatus NOTIFY broadcastStatusChanged FINAL) + Q_PROPERTY(int outputCount READ outputCount WRITE setOutputCount NOTIFY outputCountChanged FINAL) public: PreviewTx(QObject *parent = nullptr); @@ -124,13 +133,18 @@ public: FloweePay::BroadcastStatus broadcastStatus() const; + int outputCount() const; + void setOutputCount(int c); + signals: void sentChanged(); void broadcastStatusChanged(); + void outputCountChanged(); private: // the one we created std::shared_ptr m_finalTx; + int m_outputCount = 1; }; #endif diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 54c9910..28679ad 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -96,6 +96,40 @@ Mobile.Page { } } + Flowee.GroupBox { + id: outputSelector + title: qsTr("Coins") + x: parent.x + offset + parent.width - width - 10 + collapsable: false + drawBorder: true + width: Math.max(label.width + 30, 130) + height: implicitHeight - 10 + property int outputCount: modelData.outputCount + onOutputCountChanged: modelData.outputCount = outputCount; + + Item { + implicitWidth: label.implicitWidth + implicitHeight: label.height - 10 + Flowee.Label { + id: label + width: parent.width + text: modelData.inputCount + " ⇒ " + outputSelector.outputCount + y: -6 // digits only, so above the x-height its empty. + } + } + } + Flowee.ArrowPoint { + anchors.right: outputSelector.right + anchors.rightMargin: 8 + y: outputSelector.y + outputSelector.height - label.height / 2 - height + rotation: 90 + } + MouseArea { + anchors.fill: outputSelector + anchors.margins: -5 + onClicked: popup.open(outputSelector) + } + Item { id: address width: parent.width @@ -188,4 +222,69 @@ Mobile.Page { thePile.pop(); } } + FocusScope { + id: popup // its basically a full page overlay + height: 400 + width: 200 + anchors.fill: parent + visible: false + + property var targetItem: null + function open(sourceItem) { + targetItem = sourceItem; + content.model = root.transferManager.outputModel; + visible = true; + popup.forceActiveFocus(); + reviewList.enabled = false; + } + + onVisibleChanged: { + if (!visible) { + root.forceActiveFocus(); + reviewList.enabled = true; + content.model = undefined; + } + } + + MouseArea { + anchors.fill: parent; + onClicked: popup.visible = false; + } + + Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + anchors.bottom: parent.bottom + anchors.bottomMargin: -2 + anchors.horizontalCenter: parent.horizontalCenter + width: 160 + height: 360 + ListView { + id: content + anchors.fill: parent + anchors.margins: 6 + clip: true + header: Flowee.Label { text: qsTr("Target Coins", "indicates a number") + ":" } + delegate: Rectangle { + width: ListView.view.width + height: Math.max(60, label2.height + 10); + color: (index % 2) === 0 ? palette.base : palette.alternateBase + Flowee.Label { + id: label2 + text: model.number + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: { + popup.targetItem.outputCount = model.number + popup.visible = false; + } + } + } + } + } + } } -- 2.54.0 From f80554bd4cdbee491c839a324a602f1fe4bd46b3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 18:23:07 +0100 Subject: [PATCH 1130/1428] Minor UX tweaks --- guis/Flowee/GroupBox.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index c9d001b..06e9f30 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -81,6 +81,7 @@ QQC2.Control { id: titleLabel x: 3 color: palette.windowText + opacity: 0.8 // focus indicator Rectangle { @@ -127,7 +128,7 @@ QQC2.Control { GridLayout { // user set content id: child x: 12 - y: titleArea.height + 10 + y: titleArea.height + 6 width: root.width - 24 height: implicitHeight visible: !root.effectiveCollapsed -- 2.54.0 From 097fbb9c01ef4a17fe0968c7a076ca735c7c2be5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 18:24:58 +0100 Subject: [PATCH 1131/1428] Add missing margins --- guis/desktop/ReceiveTransactionPane.qml | 2 +- guis/desktop/SettingsPane.qml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index 8a46149..3e23a09 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -47,7 +47,7 @@ Item { Flowee.Label { id: instructions anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 + y: 20 text: qsTr("Share your QR code or copy address to receive") opacity: 0.5 } diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index ebe04cc..67ab172 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -31,6 +31,7 @@ Item { rowSpacing: 10 columnSpacing: 6 width: parent.width + y: 10 Flowee.Label { text: qsTr("Unit") + ":" -- 2.54.0 From 02efe2dcd1952b475995ccb6907213c59e2a1ec2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 18:30:13 +0100 Subject: [PATCH 1132/1428] minor issue. --- modules/big-transfer/QMLTransferManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index 9538551..f395653 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -181,7 +181,7 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) assert(m_fromAccount); assert(m_toAccount); assert(tx->outputCount() >= 1); - assert(tx->outputCount() < 400); + assert(tx->outputCount() <= 400); const Wallet *wIn = m_fromAccount->wallet(); assert(wIn); Wallet *wTo = m_toAccount->wallet(); -- 2.54.0 From b83e19b33ba2945368c2c39722ccee44ac9e2d29 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 19:17:49 +0100 Subject: [PATCH 1133/1428] Make possible to use for single-address wallets. This uses the strategy to create one transaction per utxo instead of one per address which makes this module actually provide some usefullness to such wallets. --- modules/big-transfer/QMLTransferManager.cpp | 31 +++++++++++++++++++-- modules/big-transfer/ShowPrepared.qml | 22 +++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index f395653..9beddbd 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -41,6 +41,35 @@ void QMLTransferManager::prepare() // do this a tad later in order to avoid the button press not // seeming to do anything. const Wallet *wIn = m_fromAccount->wallet(); + + if (m_fromAccount->isSingleAddressAccount()) { + // We create one transaction per UTXO, since the alternative + // is one big transaction for this kind of wallet. + QTimer::singleShot(100, this, [=]() { + const auto walletSecrets = wIn->walletSecrets(); + assert(walletSecrets.size() == 1); + auto secret = walletSecrets.begin(); + const auto utxos = wIn->unspentOutputsForKey(secret->first); + for (const auto &utxo : utxos) { + // NOTICE in future we may want to support token moving as well. + if (wIn->txOutput(utxo).hasCashToken == false) { + PreviewTx *tx = new PreviewTx(this); + tx->m_utxos.push_back(utxo); + tx->m_value += wIn->utxoOutputValue(utxo); + const auto &fromSecret = secret->second; + tx->m_from = new Address(renderAddress(fromSecret.address), + fromSecret.cloakedAddress(), tx); + m_transactions.append(tx); + } + } + emit transactionsChanged(); + emit unsentTxCountChanged(); + m_prepareRunning = false; + }); + + return; + } + QTimer::singleShot(100, this, [=]() { // we create one transaction for each address (aka private key). // Regardless how many inputs this creates in a transaction. @@ -146,8 +175,6 @@ bool QMLTransferManager::inputsOk() const return false; if (!m_fromAccount->isDecrypted()) return false; - if (m_fromAccount->isSingleAddressAccount()) - return false; if (m_toAccount == nullptr) return false; if (m_toAccount->needsPinToOpen() && !m_toAccount->isDecrypted()) diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 28679ad..948bb9f 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -41,7 +41,7 @@ Mobile.Page { id: delegateRoot width: ListView.view.width color: (index % 2) === 0 ? palette.base : palette.alternateBase - height: topRow.height + 6 + address.height + 10 + height: Math.max(address.y + address.height, outputSelector.y + outputSelector.height) + 10 property int offset: modelData.sent ? 80 : 0 opacity: { if (helpText.opacity > 0 && (dragItem.x === 0 || dragItem > 260)) @@ -55,8 +55,9 @@ Mobile.Page { x: parent.offset } Flowee.BitcoinAmountLabel { + id: amountLabel y: 5 - x: topRow.width + topRow.x + 10 + x: topRow.width + topRow.x + 6 value: modelData.value } @@ -99,10 +100,16 @@ Mobile.Page { Flowee.GroupBox { id: outputSelector title: qsTr("Coins") - x: parent.x + offset + parent.width - width - 10 + x: parent.x + offset + parent.width - width + y: { + if (width + amountLabel.x + amountLabel.width + 10 > root.width) + return amountLabel.y + amountLabel.height + 6 + return 0; + } + collapsable: false drawBorder: true - width: Math.max(label.width + 30, 130) + width: Math.max(label.width + 20, 110) height: implicitHeight - 10 property int outputCount: modelData.outputCount onOutputCountChanged: modelData.outputCount = outputCount; @@ -133,8 +140,13 @@ Mobile.Page { Item { id: address width: parent.width - y: topRow.height + 6 x: modelData.sent ? 80 : 0 + y: { + // tall vs wide + if (outputSelector.width + 10 + fromLabel.implicitWidth > root.width) + return outputSelector.y + outputSelector.height + 6; + return topRow.height + 6; + } height: fromLabel.height + 10 opacity: 1 - dragItem.x / 200 -- 2.54.0 From 6caf9d6e287a624e33ae91cccae4768d8fe8ca72 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 19:47:37 +0100 Subject: [PATCH 1134/1428] Add anonimity. --- modules/big-transfer/QMLTransferManager.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index 9beddbd..cd409ee 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -237,7 +237,14 @@ Tx QMLTransferManager::createTx(PreviewTx *tx) if (perOutput < 700) throw std::runtime_error("Too low"); for (int i = 0; i < tx->outputCount(); ++i) { - builder.appendOutput(perOutput); + // diff is a fun thing. + // The idea is to make each output have a slightly random different amount. + // The anonimize feature of the builder will then sort them based on amount, + // and this will effectively randomize the outputs ordering. + // This avoids a large number of addresses being in perfect order on-chain for forever, + // just in case someday in the future someone can reverse engineer a HD seed from that. + uint32_t diff = std::rand() % 15; + builder.appendOutput(perOutput - diff); KeyId targetAddress; wTo->reserveUnusedAddress(targetAddress, Wallet::ChangePath); builder.pushOutputPay2Address(targetAddress); -- 2.54.0 From aceb0387a15f9762f3bfef20a5037bd9fbc577fb Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 19:52:51 +0100 Subject: [PATCH 1135/1428] Send on tx swipe now works in either direction --- modules/big-transfer/ShowPrepared.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 948bb9f..529d5ac 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -67,13 +67,14 @@ Mobile.Page { height: parent.height onXChanged: { + var absX = Math.abs(x); delegateRoot.offset = x / 2 - if (x > 250) { + if (absX > 250) { root.transferManager.send(modelData); helpText.visible = false; delegateRoot.offset = 80 } - helpText.opacity = dragItem.x / 200 + helpText.opacity = absX / 200 } DragHandler { @@ -81,7 +82,7 @@ Mobile.Page { yAxis.enabled: false xAxis.enabled: true xAxis.maximum: 270 // swipe left - xAxis.minimum: 0 // swipe right + xAxis.minimum: -270 // swipe right enabled: !modelData.sent onActiveChanged: { if (active) { @@ -148,7 +149,7 @@ Mobile.Page { return topRow.height + 6; } height: fromLabel.height + 10 - opacity: 1 - dragItem.x / 200 + opacity: 1 - Math.abs(dragItem.x) / 200 Flowee.AddressLabel { id: fromLabel -- 2.54.0 From 8865a51b8bf2ae822c01bd1348f685bafc8c9aa1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 20:06:18 +0100 Subject: [PATCH 1136/1428] Redo all the strings. This upgrades the initial ideas and texts of this module to the latest insight and designs to be coherent and more easy to understand. --- modules/big-transfer/BigTransferModuleInfo.cpp | 10 +++++----- modules/big-transfer/Main.qml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/big-transfer/BigTransferModuleInfo.cpp b/modules/big-transfer/BigTransferModuleInfo.cpp index 66757c2..29e4124 100644 --- a/modules/big-transfer/BigTransferModuleInfo.cpp +++ b/modules/big-transfer/BigTransferModuleInfo.cpp @@ -29,17 +29,17 @@ ModuleInfo * BigTransferModuleInfo::build() BigTransferModuleInfo::BigTransferModuleInfo() { setId("bigTransfer"); - setTitle(tr("Transfer All")); - setDescription(tr("Move all coins to another wallet, with one transaction per coin to preserve anonimity.")); + setTitle(tr("Wallet to Wallet")); + setDescription(tr("Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses.")); ModuleSection *sendTab = new ModuleSection(ModuleSection::SendMethod, this); - sendTab->setText(tr("Transfer All Coins")); - sendTab->setSubtext(tr("Move coins to another wallet")); + sendTab->setText(tr("Wallet to Wallet")); + sendTab->setSubtext(tr("Move funds to another wallet")); sendTab->setStartQMLFile("qrc:/bigtransfer/Main.qml"); addSection(sendTab); ModuleSection *app = new ModuleSection(ModuleSection::OtherSectionType, this); - app->setText(tr("Transfer All")); + app->setText(tr("Wallet to Wallet")); app->setSubtext(sendTab->subtext()); app->setStartQMLFile(sendTab->startQMLFile()); addSection(app); diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml index 885c3ae..056c125 100644 --- a/modules/big-transfer/Main.qml +++ b/modules/big-transfer/Main.qml @@ -25,7 +25,7 @@ import Flowee.org.pay.bigtransfer; Mobile.Page { id: root - headerText: qsTr("Transfer All") + headerText: qsTr("Wallet to Wallet") property alias initialWallet: fromWalletSelector.startingAccount ColumnLayout { @@ -35,11 +35,11 @@ Mobile.Page { Flowee.Label { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Move all coins to another wallet, with one transaction per coin to preserve anonimity.") + text: qsTr("Select two wallets to transfer funds simply, using anonimity preserving transactions.") } Mobile.PageTitledBox { - title: qsTr("Starting Wallet") + title: qsTr("Spending Wallet") // TODO Popup with just the name Mobile.AccountSelectorWidget { -- 2.54.0 From 281619324e97d3eb9aa82daeed12fa27c784d4da Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 9 Jan 2025 20:50:13 +0100 Subject: [PATCH 1137/1428] Make script a little more robust Docker doesn't like relative paths, so ensure that the path we pass is absolute regardless of user input. --- android/build-pay.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index 37ca4d5..b080564 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -1,6 +1,6 @@ #!/bin/bash # This file is part of the Flowee project -# Copyright (C) 2022-2024 Tom Zander +# Copyright (C) 2022-2025 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ if test -z "$_thehub_dir_"; then echo " build-pay [PAY_NATIVE_builddir]" echo "" echo "Start this client in your builddir" - echo "HUB-builddir is the dir where the android build of flowe-thehub is." + echo "HUB-builddir is the dir where the android build of flowee-thehub is." echo "Pay_NATIVE-builddir for a native build of flowee-pay, for translations." exit fi @@ -38,7 +38,7 @@ _thehub_dir_=`realpath $_thehub_dir_` _ok=0 if test -f $_thehub_dir_/lib/libflowee_p2p.a; then if grep -q -- -DANDROID $_thehub_dir_/build.ninja; then - _ok=1 + _ok=1 fi fi @@ -57,6 +57,7 @@ if test -d "$_pay_native_name_"; then fi floweePaySrcDir=`dirname $0`/.. +floweePaySrcDir=`realpath $floweePaySrcDir` if test -f $floweePaySrcDir/android/netlog.conf; then developerSwitches="-DNetworkLogClient=ON -Dquick_deploy=ON" fi -- 2.54.0 From a0856469b161dea9ba3a27edc1c8d8e8fa85a9ad Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 22 Jan 2025 18:04:30 +0100 Subject: [PATCH 1138/1428] Fiat fixes This removes Qt bug workarounds for a the old 6.5 we no longer use on mobile. This adds a new workaround for CHF as that somehow no longer has a 'symbol' set, so now we provide our own. Also make sure we run a fetch when the user changes the currency. --- guis/mobile/CurrencySelector.qml | 24 +++++------------------- src/PriceDataProvider.cpp | 5 +++-- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index 7cee8a0..fa5aa06 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -80,8 +80,7 @@ Page { Flowee.Label { id: iso y: 10 - text: Qt.locale(modelData).currencySymbol(0) - // text: Qt.locale(modelData).currencySymbol(Locale.CurrencyIsoCode) + text: Qt.locale(modelData).currencySymbol(Locale.CurrencyIsoCode) } Flowee.Label { @@ -91,24 +90,11 @@ Page { anchors.leftMargin: 10 anchors.right: parent.right text: { - /* - * On top of the Locale issues as QTBUG-116527 describes, - * Qt 65 misses several actual texts. - * Here are the work-arounds: - * - * hi - * ko - * si - * my - * ru - * th - * uk - * - */ var loc = Qt.locale(modelData); - return loc.currencySymbol(2) + " [" + loc.currencySymbol(1) + "]"; - // return loc.currencySymbol(Locale.CurrencyDisplayName) - // + " [" + loc.currencySymbol(Locale.CurrencySymbol) + "]"; + var symbol = loc.currencySymbol(Locale.CurrencySymbol); + if (symbol === "" && modelData == "de_CH") + symbol = "Fr."; + return loc.currencySymbol(Locale.CurrencyDisplayName) + " [" + symbol + "]"; } } diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 5957529..261dad9 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -67,6 +67,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) auto newCurrency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); if (m_currency == newCurrency) return; + m_currentPrice = Price(); m_currency = newCurrency; m_currencySymbolPrefix = countryLocale.currencySymbol(QLocale::CurrencySymbol); m_currencySymbolPost.clear(); @@ -98,7 +99,7 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) || m_currency == QLatin1String("ARS")); if (m_currency == QLatin1String("CHF")) { - m_currencySymbolPrefix += QLatin1String(" "); + m_currencySymbolPrefix = QLatin1String("Fr. "); } // some currencies need conversion from USD -- 2.54.0 From a694e970e20aad646a1f22a86d1950d701a66082 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 13:15:12 +0100 Subject: [PATCH 1139/1428] Show some feedback before we start the sync. This now has a bit of a jittery bahavior when we're waiting on the network and maybe to find some peers. Going back to normal smooth progressbar behavior as soon as one block has been downloaded. --- guis/desktop/AccountListItem.qml | 22 ++++++++++++++++++++++ src/FloweePay.h | 1 + 2 files changed, 23 insertions(+) diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 3de8b81..3f4ce90 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -47,6 +47,21 @@ Item { return Pay.useDarkSkin ? "#EEE" : "#7b7f7f" } + Timer { + id: startTimer + property int wavy: 0 + running: !Pay.offline && root.account.lastBlockSynched === root.startPos; + interval: 300 + repeat: true + onTriggered: { + var w = wavy; + if (w >= 2) + wavy = 0; + else + wavy = w + 1; + } + } + Rectangle { height: parent.height width: { @@ -55,6 +70,12 @@ Item { if (startPos == 0) return 0; let currentPos = root.account.lastBlockSynched; + if (currentPos === startPos) { // waiting for start + var wave = startTimer.wavy; + if (wave > 10) + wave = 20 - wave; + return wave * 8; + } let totalDistance = end - startPos; let ourProgress = currentPos - startPos; return parent.width * (ourProgress / totalDistance); @@ -65,6 +86,7 @@ Item { color: mainWindow.floweeGreen opacity: root.account.uptodate ? 0 : 0.2 Behavior on opacity { NumberAnimation {} } + Behavior on width { NumberAnimation { easing.type: Easing.InExpo; duration: startTimer.running ? 300 : 1} } } Rectangle { height: parent.height diff --git a/src/FloweePay.h b/src/FloweePay.h index 63a1d69..7008a30 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -73,6 +73,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa // notifications Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) + Q_PROPERTY(bool offline READ isOffline CONSTANT) Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) public: -- 2.54.0 From b3396483389a328fd5e5b8bfe0c71efdc9d552d1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 13:19:54 +0100 Subject: [PATCH 1140/1428] In this module we use rand, make sure to seed To make this not depend on another part of Pay doing this already. --- modules/big-transfer/BigTransferModuleInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/big-transfer/BigTransferModuleInfo.cpp b/modules/big-transfer/BigTransferModuleInfo.cpp index 29e4124..63bc418 100644 --- a/modules/big-transfer/BigTransferModuleInfo.cpp +++ b/modules/big-transfer/BigTransferModuleInfo.cpp @@ -22,6 +22,7 @@ ModuleInfo * BigTransferModuleInfo::build() { + std::srand(time(nullptr)); qmlRegisterType("Flowee.org.pay.bigtransfer", 1, 0, "TransferManager"); return new BigTransferModuleInfo(); } -- 2.54.0 From 5ed8ccae044eba3f7aff39dc57c2a2da11e43b6a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 13:34:42 +0100 Subject: [PATCH 1141/1428] Move widget to desktop dir This widget is only used by the desktop, so no point in storing it in the Flowee Subdir. It basically turned out that it is so simple to do this that mobile got its own more specilistic version. --- guis/desktop.qrc | 1 + guis/{Flowee/TabBar.qml => desktop/TabBarWidget.qml} | 5 +++-- guis/desktop/TransactionDetails.qml | 2 +- guis/desktop/main.qml | 2 +- guis/widgets.qrc | 1 - 5 files changed, 6 insertions(+), 5 deletions(-) rename guis/{Flowee/TabBar.qml => desktop/TabBarWidget.qml} (98%) diff --git a/guis/desktop.qrc b/guis/desktop.qrc index d6db54d..6daf101 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -35,6 +35,7 @@ desktop/SettingsPane.qml desktop/Transaction.qml desktop/TransactionDetails.qml + desktop/TabBarWidget.qml desktop/TransactionInfoSmall.qml desktop/WalletEncryption.qml desktop/WalletEncryptionStatus.qml diff --git a/guis/Flowee/TabBar.qml b/guis/desktop/TabBarWidget.qml similarity index 98% rename from guis/Flowee/TabBar.qml rename to guis/desktop/TabBarWidget.qml index aa97f05..8f9ec12 100644 --- a/guis/Flowee/TabBar.qml +++ b/guis/desktop/TabBarWidget.qml @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee FocusScope { id: floweeTabBar @@ -74,14 +75,14 @@ FocusScope { Image { id: tabIcon - visible: source != "" + visible: source !== "" source: enabled ? header.tabs[index].icon : "" anchors.bottom: payTabButtonText.baseline anchors.bottomMargin: -2 height: payTabButtonText.height width: height } - Label { + Flowee.Label { id: payTabButtonText y: 6 x: tabIcon.visible ? tabIcon.width + 6 : 0 diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index fd9b75e..9111f55 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -53,7 +53,7 @@ QQC2.ApplicationWindow { } } - Flowee.TabBar { + TabBarWidget { id: tabbar anchors.fill: parent diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 18067aa..f95ce0e 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -201,7 +201,7 @@ ApplicationWindow { radius: 5 color: Pay.useDarkSkin ? "black" : "white" } - Flowee.TabBar { + TabBarWidget { id: tabbar anchors.fill: parent diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 392ad63..e0824cc 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -34,7 +34,6 @@ Flowee/MultilineTextField.qml Flowee/RadioButton.qml Flowee/ScrollThumb.qml - Flowee/TabBar.qml Flowee/TextField.qml Flowee/TextPasteDecorator.qml Flowee/ComboBox.qml -- 2.54.0 From ea7434f88131cea21b1d728e27dd53066fb8e20d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 15:35:38 +0100 Subject: [PATCH 1142/1428] Add 'copy' context menu option. --- guis/Flowee/TextField.qml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index b8ae456..d68747e 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -77,6 +77,10 @@ QQC2.TextField { QQC2.Menu { id: menu + QQC2.MenuItem { + text: qsTr("Copy") + onTriggered: Pay.copyToClipboard(root.totalText) + } QQC2.MenuItem { text: qsTr("Paste") ClipboardHelper { -- 2.54.0 From d1fcdf1104c839b19de04f0829b075319442860a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 18:03:14 +0100 Subject: [PATCH 1143/1428] Add various filter options to desktop --- guis/desktop.qrc | 1 + guis/desktop/ActivityConfigBar.qml | 206 +++++++++++++++++++++++++++++ guis/desktop/Transaction.qml | 1 - guis/desktop/main.qml | 67 +++++++++- 4 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 guis/desktop/ActivityConfigBar.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 6daf101..84e5645 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -39,5 +39,6 @@ desktop/TransactionInfoSmall.qml desktop/WalletEncryption.qml desktop/WalletEncryptionStatus.qml + desktop/ActivityConfigBar.qml diff --git a/guis/desktop/ActivityConfigBar.qml b/guis/desktop/ActivityConfigBar.qml new file mode 100644 index 0000000..f24d8a0 --- /dev/null +++ b/guis/desktop/ActivityConfigBar.qml @@ -0,0 +1,206 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +// import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee +import Flowee.org.pay; + +Item { + id: root + height: 50 + Item { + x: -11 + height: parent.height + width: 20 + clip: true + Rectangle { + color: mainWindow.floweeGreen + height: 80 + width: 40 + anchors.bottom: parent.bottom + radius: 10 + + Rectangle { + width: parent.width + height: parent.height + y: -6 + x: 6 + radius: 6 + color: palette.light + } + } + } + + function toggleFlag(flag, on) { + var cf = portfolio.current.transactions.includeFlags + if (((cf & flag) > 0) === on) // nothing to do. + return; + if (on) + cf += flag; + else + cf -= flag; + portfolio.current.transactions.includeFlags = cf + } + + Flowee.TextField { + id: filterText + y: 6 + width: Math.min(500, parent.width - 20) + placeholderText: qsTr("Filter") + } + + Rectangle { + id: cfCheckbox + anchors.left: filterText.right + anchors.leftMargin: 10 + y: 4 + width: 40 + height: 40 + radius: 2 + color: checked ? palette.alternateBase : palette.base + border.width: 2 + border.color: { + if (cfHoverArea.containsMouse) + return Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue; + if (checked) + return palette.midlight; + return palette.mid + } + visible: portfolio.current.hasAnonimityTransactions + + property bool checked: (portfolio.current.transactions.includeFlags + & Wallet.IncludeCFs) > 0; + + Image { + source: "qrc:/cf.svg"; + width: 30 + height: 30 + smooth: true + anchors.centerIn: parent + opacity: cfCheckbox.checked ? 1 : 0.5 + } + MouseArea { + id: cfHoverArea + anchors.fill: parent + hoverEnabled: true + onClicked: toggleFlag(Wallet.IncludeCFs, !cfCheckbox.checked); + } + } + + Rectangle { + id: sendReceiveFilter + anchors.left: cfCheckbox.right + anchors.leftMargin: 10 + border.width: 2 + color: palette.base + border.color: sendReceiveMouse.containsMouse + ? (Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue) + : palette.mid + height: 44 + width: grid.width + 12 + y: 4 + radius: 2 + + property bool includeSent: { + var flags = portfolio.current.transactions.includeFlags; + var filter = Wallet.IncludeSentTransactions; + return (flags & filter) === filter; + } + property bool includeReceived: { + var flags = portfolio.current.transactions.includeFlags; + var filter = Wallet.IncludeReceivedTransactions; + return (flags & filter) === filter; + } + + Grid { + id: grid + y: 4 + x: 6 + columns: 2 + columnSpacing: 6 + Rectangle { + color: "#00000000" + border.width: 1.3 + border.color: palette.button + width: 14 + height: 14 + + Flowee.Label { + text: "✓" + y: -6 + x: 2 + font.pixelSize: 16 + color: Pay.useDarkSkin ? "#86ffa8" : "green"; + visible: sendReceiveFilter.includeReceived + } + } + Flowee.Label { + text: qsTr("Received") + font.pixelSize: 14 + } + Rectangle { + color: "#00000000" + border.width: 1.3 + border.color: palette.button + width: 14 + height: 14 + Flowee.Label { + text: "✓" + y: -6 + x: 2 + font.pixelSize: 16 + color: Pay.useDarkSkin ? "#86ffa8" : "green"; + visible: sendReceiveFilter.includeSent + } + } + Flowee.Label { + text: qsTr("Sent") + font.pixelSize: 14 + } + } + + MouseArea { + id: sendReceiveMouse + anchors.fill: parent + hoverEnabled: true + onClicked: popupTabsOverlay.open(sendReceivePopup, mapToGlobal(width, height)); + } + } + Component { + id: sendReceivePopup + GridLayout { + rowSpacing: 10 + columns: 2 + Flowee.CheckBox { + checked: sendReceiveFilter.includeReceived + onClicked: toggleFlag(Wallet.IncludeReceivedTransactions, checked); + } + Flowee.Label { + text: qsTr("Received") + } + Flowee.CheckBox { + checked: sendReceiveFilter.includeSent + onClicked: toggleFlag(Wallet.IncludeSentTransactions, checked); + } + Flowee.Label { + text: qsTr("Sent") + } + } + } +} diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index d27dec2..eb70fc9 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -29,7 +29,6 @@ Rectangle { return rc; } width: mainLabel.width + bitcoinAmountLabel.width + 30 - color: (index % 2) == 0 ? palette.light : palette.alternateBase property int minedHeight: model.height property bool isRejected: minedHeight === -2 // -2 is the magic block-height indicating 'rejected' diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index f95ce0e..b284151 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -220,9 +220,20 @@ ApplicationWindow { id: activityHeader width: parent.width spacing: 6 + + Item { + width: parent.width + height: 40 + Loader { + source: isLoading ? "" : "./ActivityConfigBar.qml" + width: parent.width + y: -13 + } + } + Rectangle { width: parent.width - height: warn.height + unarchiveButton.height + 26 + height: visible ? (warn.height + unarchiveButton.height + 26) : 0 color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" // yellow visible: !isLoading && portfolio.current.isArchived radius: 7 @@ -252,7 +263,7 @@ ApplicationWindow { Rectangle { id: needsDecryptPane width: parent.width - height: decryptText.height + decryptPwd.height + decryptButton.height + 36 + height: visible ? (decryptText.height + decryptPwd.height + decryptButton.height + 36): 0 color: Pay.useDarkSkin ? "#c1ba58" : "#f6e992" // yellow visible: !isLoading && portfolio.current.needsPinToOpen && !portfolio.current.isDecrypted @@ -330,7 +341,10 @@ ApplicationWindow { id: activityView model: isLoading ? 0 : portfolio.current.transactions clip: true - delegate: Transaction { width: activityView.width } + delegate: Transaction { + width: activityView.width + color: (index % 2) == 0 ? palette.light : palette.alternateBase + } anchors.top: activityHeader.bottom anchors.left: parent.left anchors.right: parent.right @@ -397,6 +411,51 @@ ApplicationWindow { } Behavior on opacity { NumberAnimation { } } visible: opacity > 0 + Item { + id: popupTabsOverlay + property alias isOpen: thePopup.visible + anchors.fill: parent + + Rectangle { + color: "black" + opacity: 0.3 + } + visible: isOpen + + function open(sourceComponent, rightAnchor) { + thePopup.anchorTopRight = popupTabsOverlay.mapFromGlobal(rightAnchor); + loader.sourceComponent = sourceComponent; + } + + QQC2.Popup { + id: thePopup + property point anchorTopRight: Qt.point(0, 0); + + closePolicy: QQC2.Popup.CloseOnEscape + QQC2.Popup.CloseOnReleaseOutside + width: loader.width + 20 + height: loader.height + 20 + modal: true + leftPadding: 10 + topPadding: 10 + visible: false + onVisibleChanged: if (!visible) loader.sourceComponent = undefined; + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 6 + } + Loader { + id: loader + onLoaded: thePopup.visible = true + onWidthChanged: { + height = loader.item.height + thePopup.x = thePopup.anchorTopRight.x - width + thePopup.y = thePopup.anchorTopRight.y + } + } + } + } } Loader { -- 2.54.0 From 6b6f5383a02c3790f85ede8b3d1ada0f7d9764cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 20:39:27 +0100 Subject: [PATCH 1144/1428] Make free text search work in Desktop It interprets block-id, it finds on txid it finds on (own) address and it searches the comment. --- guis/desktop/ActivityConfigBar.qml | 4 +- src/Wallet.cpp | 8 +++ src/Wallet.h | 5 ++ src/WalletHistoryModel.cpp | 86 ++++++++++++++++++++++++++++-- src/WalletHistoryModel.h | 31 ++++++++++- 5 files changed, 129 insertions(+), 5 deletions(-) diff --git a/guis/desktop/ActivityConfigBar.qml b/guis/desktop/ActivityConfigBar.qml index f24d8a0..3710f05 100644 --- a/guis/desktop/ActivityConfigBar.qml +++ b/guis/desktop/ActivityConfigBar.qml @@ -16,7 +16,6 @@ * along with this program. If not, see . */ import QtQuick -// import QtQuick.Controls as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; @@ -47,6 +46,8 @@ Item { } } + property QtObject wallet: portfolio.current; + onWalletChanged: wallet.transactions.filterString = filterText.text function toggleFlag(flag, on) { var cf = portfolio.current.transactions.includeFlags if (((cf & flag) > 0) === on) // nothing to do. @@ -63,6 +64,7 @@ Item { y: 6 width: Math.min(500, parent.width - 20) placeholderText: qsTr("Filter") + onTextChanged: portfolio.current.transactions.filterString = text } Rectangle { diff --git a/src/Wallet.cpp b/src/Wallet.cpp index d9e33e4..dfdc28d 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1255,6 +1255,14 @@ void Wallet::setName(const QString &name) m_walletNameChanged = true; } +int Wallet::walletTransactionIndex(const uint256 &txid) const +{ + auto i = m_txidCache.find(txid); + if (i != m_txidCache.end()) + return i->second; + return -1; +} + const uint256 &Wallet::txid(int txIndex) const { QMutexLocker locker(&m_lock); diff --git a/src/Wallet.h b/src/Wallet.h index 0e9632e..982e5c7 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -151,6 +151,11 @@ public: /// set the user-visible name for the wallet. void setName(const QString &name); + /** + * returns the txIndex, or -1 if no hit. + */ + int walletTransactionIndex(const uint256 &txid) const; + /// Fetch UTXO txid inline const uint256 &txid(OutputRef ref) const { return txid(ref.txIndex()); diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 9c81ee9..365a65e 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -19,6 +19,7 @@ #include "FloweePay.h" #include +#include #include #include @@ -219,6 +220,25 @@ int WalletHistoryModel::groupIdForTxIndex(int txIndex) const return m_groupCache.groupId; } +QString WalletHistoryModel::filterString() const +{ + return m_filterString; +} + +void WalletHistoryModel::setFilterString(const QString &filter) +{ + if (m_filterString == filter) + return; + m_filterString = filter; + emit filterStringChanged(); + m_filterStringParsed = false; + + if (m_recreateTriggered) + return; + m_recreateTriggered = true; + QTimer::singleShot(300, this, SLOT(createMap())); +} + QString WalletHistoryModel::groupingPeriod(int groupId) const { @@ -304,7 +324,7 @@ void WalletHistoryModel::appendTransactions(int firstNew, int count) auto iter = m_wallet->m_walletTransactions.find(i); if (iter == m_wallet->m_walletTransactions.end()) continue; // already removed by wallet again. - if (!filterTransaction(iter->second)) + if (!filterTransaction(iter->first, iter->second)) continue; m_rowsProxy.push_back(i); addTxIndexToGroups(iter->first, iter->second.minedBlockHeight); @@ -378,7 +398,7 @@ void WalletHistoryModel::createMap() QMutexLocker locker(&m_wallet->m_lock); m_rowsProxy.reserve(m_wallet->m_walletTransactions.size()); for (const auto &iter : m_wallet->m_walletTransactions) { - if (!filterTransaction(iter.second)) + if (!filterTransaction(iter.first, iter.second)) continue; m_rowsProxy.push_back(iter.first); // Last, resolve grouping @@ -453,7 +473,7 @@ void WalletHistoryModel::savePreferences() } } -bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) const +bool WalletHistoryModel::filterTransaction(int txIndex, const Wallet::WalletTransaction &wtx) { assert(QThread::currentThread() == thread()); if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) @@ -470,6 +490,66 @@ bool WalletHistoryModel::filterTransaction(const Wallet::WalletTransaction &wtx) return false; if (!m_includeFlags.testFlag(WalletEnums::IncludeTxWithoutComment) && wtx.userComment.isEmpty()) return false; + + if (!m_filterStringParsed) { + m_filterOnKeyId = -1; + m_filterOnBlockHeight = -1; + m_filterOnTxIndex = -1; + m_filterTargetScript.clear(); + auto latin1 = m_filterString.toLatin1(); + if (latin1.size() == 64 + || (latin1.size() == 66 && m_filterString.startsWith("0x", Qt::CaseInsensitive))) { + uint256 hash = uint256S(latin1.constData()); + assert(m_wallet); + m_filterOnTxIndex = m_wallet->walletTransactionIndex(hash); + if (m_filterOnTxIndex == -1) + m_filterOnBlockHeight = FloweePay::instance()->p2pNet()->blockchain().blockHeightFor(hash); + } + else { + // try to parse a cash address. + int prefixEnd = latin1.indexOf(':'); + std::string address; + if ((prefixEnd == -1 && latin1.size() > 40 && latin1.size() < 45) + || (latin1.size() > 40 + prefixEnd && latin1.size() < 45 + prefixEnd)) + address = m_filterString.toStdString(); + CashAddress::Content c = CashAddress::decodeCashAddrContent(address, chainPrefix()); + if (!c.hash.empty() && c.type == CashAddress::PUBKEY_TYPE) { + KeyId filterTargetAddress(uint160(c.hash)); + m_filterOnKeyId = m_wallet->findPrivKeyId(filterTargetAddress); + auto pool = Streaming::pool(25); + static const uint8_t P2PKHPrefix[3] = { 0x76, 0xA9, 20}; // OP_DUP OP_HASH160, 20-byte-push + static const uint8_t P2PKHPostfix[2] = { 0x88, 0xAC }; // OP_EQUALVERIFY OP_CHECKSIG + pool->write(P2PKHPrefix, 3); + pool->write(&c.hash[0], 20); + pool->write(P2PKHPostfix, 2); + m_filterTargetScript = pool->commit(); + } + } + + m_filterStringParsed = true; + } + if (m_filterOnBlockHeight != -1) { + return wtx.minedBlockHeight == m_filterOnBlockHeight; + } else if (m_filterOnTxIndex != -1) { + return txIndex == m_filterOnTxIndex; + } else if (m_filterOnKeyId != -1) { + for (auto i = wtx.outputs.begin(); i != wtx.outputs.end(); ++i) { + if (i->second.walletSecretId == m_filterOnKeyId ) + return true; + } + for (auto i = wtx.inputToWTX.begin(); i != wtx.inputToWTX.end(); ++i) { + if (m_wallet->txOutput(Wallet::OutputRef(i->second)).outputScript == m_filterTargetScript) + return true; + } + return false; + } else if (!m_filterTargetScript.isEmpty()) { + // TODO load the actual transaction and check all outputs. + return false; + } + else if (!m_filterString.isEmpty()) { + return wtx.userComment.indexOf(m_filterString,0 , Qt::CaseInsensitive) != -1; + } + return true; } diff --git a/src/WalletHistoryModel.h b/src/WalletHistoryModel.h index da17dca..1868eb7 100644 --- a/src/WalletHistoryModel.h +++ b/src/WalletHistoryModel.h @@ -38,6 +38,10 @@ class WalletHistoryModel : public QAbstractListModel * will cause all the new items that were found while frozen to be reported for UI update. */ Q_PROPERTY(bool freezeModel READ isModelFrozen WRITE freezeModel NOTIFY freezeModelChanged) + /** + * Only show transactions with the given string. + */ + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged FINAL) public: explicit WalletHistoryModel(Wallet *wallet, QObject *parent = nullptr); @@ -93,11 +97,16 @@ public: int filterCount() const; + QString filterString() const; + void setFilterString(const QString &newFilterString); + signals: void lastSyncIndicatorChanged(); void includeFlagsChanged(); void freezeModelChanged(); + void filterStringChanged(); + protected: // virtual to allow the unit test to not use p2pNet for this /// return the timestamp of a block (aka nTime) as defined by the block-header @@ -115,7 +124,7 @@ protected slots: private: /// returns true if the filters indicate the transaction should stay. - bool filterTransaction(const Wallet::WalletTransaction &wtx) const; + bool filterTransaction(int txIndex, const Wallet::WalletTransaction &wtx); /// Update m_groups to include this transaction. void addTxIndexToGroups(int txIndex, int blockheight); @@ -157,6 +166,26 @@ private: /// while the listview has been frozen. /// See the freezeModel property for more. int m_rowsSilentlyInserted = -1; + + /* + * Filtering of free text feature. + * The basic user input is stored in m_filterString + * When this is an address, and this is owned by the wallet, the + * field m_filterKeyId is set to the private key matching the address. + * + * If the string is a hash we check if it is a block-id. If so, then + * m_filterOnBlockHeight is set. + * Otherwise the hash is assumed to be a txid and we store that in the + * m_filterOnTxIndex; + * + * When all 3 ints are -1, we then use the string to string-match comments. + */ + QString m_filterString; + bool m_filterStringParsed = true; + int m_filterOnKeyId = -1; + int m_filterOnBlockHeight = -1; + int m_filterOnTxIndex = -1; + Streaming::ConstBuffer m_filterTargetScript; }; #endif -- 2.54.0 From 0efd9463af1abd42b6eb72019b37d8b3811ad849 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 27 Jan 2025 23:14:19 +0100 Subject: [PATCH 1145/1428] Add searching all outputs for an address This actually reads all the raw transactions from disk in order to parse the outputs and compare it with the user typed address. To my surprise, it's actually fast. 4000 transactions took 26ms to filter. --- src/WalletHistoryModel.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 365a65e..51f7caa 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -543,7 +543,12 @@ bool WalletHistoryModel::filterTransaction(int txIndex, const Wallet::WalletTran } return false; } else if (!m_filterTargetScript.isEmpty()) { - // TODO load the actual transaction and check all outputs. + auto tx = m_wallet->loadTransaction(wtx.txid, Streaming::pool(0)); + Tx::Iterator iter(tx); + while (iter.next(Tx::OutputScript) == Tx::OutputScript) { + if (iter.byteData() == m_filterTargetScript) + return true; + } return false; } else if (!m_filterString.isEmpty()) { -- 2.54.0 From 32f7bfd44b076e30c0035a5943abdb10c507d9b6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 Jan 2025 13:16:19 +0100 Subject: [PATCH 1146/1428] Add trim() of user input before sending to backend. --- guis/desktop/ActivityConfigBar.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/ActivityConfigBar.qml b/guis/desktop/ActivityConfigBar.qml index 3710f05..6a70121 100644 --- a/guis/desktop/ActivityConfigBar.qml +++ b/guis/desktop/ActivityConfigBar.qml @@ -47,7 +47,7 @@ Item { } property QtObject wallet: portfolio.current; - onWalletChanged: wallet.transactions.filterString = filterText.text + onWalletChanged: wallet.transactions.filterString = filterText.text.trim(); function toggleFlag(flag, on) { var cf = portfolio.current.transactions.includeFlags if (((cf & flag) > 0) === on) // nothing to do. @@ -64,7 +64,7 @@ Item { y: 6 width: Math.min(500, parent.width - 20) placeholderText: qsTr("Filter") - onTextChanged: portfolio.current.transactions.filterString = text + onTextChanged: portfolio.current.transactions.filterString = text.trim(); } Rectangle { -- 2.54.0 From 118c9d91942b5d0ae22afbea8893e42cadf5b598 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 28 Jan 2025 14:01:46 +0100 Subject: [PATCH 1147/1428] Give a bit more spacing on the side for the address. --- guis/mobile/StartupScreen.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 5cf4014..835e4c7 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -144,7 +144,8 @@ Page { x: column.width / 10 } Flowee.QRWidget { - width: parent.width + width: parent.width - 20 + x: 10 qrText: request.qr } -- 2.54.0 From 5cce0ad32f84e109d0ce60d6cb6eaa57fd03a77a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Jan 2025 14:25:36 +0100 Subject: [PATCH 1148/1428] Make values line up. On the desktop activity view the prices are right aligned but due to adding fiat prices this means that the bch amounts are not lining up. This adds a little space between the two in order to make them much more readable. --- guis/Flowee/BitcoinAmountLabel.qml | 37 ++++++++++++++++++++++-------- guis/desktop/Transaction.qml | 1 + 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 92ddff4..316f3a2 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,6 +45,12 @@ QQC2.Control { property color color: palette.windowText property alias fontPixelSize: main.font.pixelSize + /** + * To allow aligning the values in a list, this property + * allows hardcoding the fiat-label width. + */ + property double fiatWidth: 0 + implicitHeight: row.implicitHeight implicitWidth: row.implicitWidth @@ -127,16 +133,27 @@ QQC2.Control { Layout.alignment: Qt.AlignBaseline } - Label { - visible: root.showFiat + Item { Layout.alignment: Qt.AlignBaseline - text: { - var fiatPrice; - if (root.fiatTimestamp == undefined) - fiatPrice = Fiat.price; // todays price - else - fiatPrice = Fiat.historicalPrice(root.fiatTimestamp); - return Fiat.formattedPrice(root.value, fiatPrice) + visible: root.showFiat + baselineOffset: fiatLabel.baselineOffset + implicitWidth: { + if (!visible) + return 0; + return Math.max(root.fiatWidth, fiatLabel.contentWidth); + } + Label { + id: fiatLabel + anchors.right: parent.right + color: main.color + text: { + var fiatPrice; + if (root.fiatTimestamp == undefined) + fiatPrice = Fiat.price; // todays price + else + fiatPrice = Fiat.historicalPrice(root.fiatTimestamp); + return Fiat.formattedPrice(root.value, fiatPrice) + } } } } diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index eb70fc9..9eca1dd 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -133,6 +133,7 @@ Rectangle { id: bitcoinAmountLabel visible: Pay.activityShowsBch || !Pay.isMainChain fiatTimestamp: model.date + fiatWidth: 90 value: { let inputs = model.fundsIn let outputs = model.fundsOut -- 2.54.0 From 68a69c61d52882b7aec770e10377b4a0cef0807f Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 29 Jan 2025 16:54:24 +0100 Subject: [PATCH 1149/1428] Start new social-feed module --- modules/social-feed/CMakeLists.txt | 24 ++++++++++++++ modules/social-feed/Listing.qml | 26 +++++++++++++++ modules/social-feed/SocialFeedModuleInfo.cpp | 35 ++++++++++++++++++++ modules/social-feed/SocialFeedModuleInfo.h | 31 +++++++++++++++++ modules/social-feed/social-feed-data.qrc | 6 ++++ modules/social-feed/social-feed.svg | 7 ++++ 6 files changed, 129 insertions(+) create mode 100644 modules/social-feed/CMakeLists.txt create mode 100644 modules/social-feed/Listing.qml create mode 100644 modules/social-feed/SocialFeedModuleInfo.cpp create mode 100644 modules/social-feed/SocialFeedModuleInfo.h create mode 100644 modules/social-feed/social-feed-data.qrc create mode 100644 modules/social-feed/social-feed.svg diff --git a/modules/social-feed/CMakeLists.txt b/modules/social-feed/CMakeLists.txt new file mode 100644 index 0000000..6ec90a5 --- /dev/null +++ b/modules/social-feed/CMakeLists.txt @@ -0,0 +1,24 @@ +# This file is part of the Flowee project +# Copyright (C) 2025 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +project(social-feed_module) + +set (SOURCES + SocialFeedModuleInfo.cpp +) +add_library (social-feed_module_lib STATIC ${SOURCES}) +target_link_libraries(social-feed_module_lib pay_lib) + diff --git a/modules/social-feed/Listing.qml b/modules/social-feed/Listing.qml new file mode 100644 index 0000000..307f21a --- /dev/null +++ b/modules/social-feed/Listing.qml @@ -0,0 +1,26 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Layouts +import "../Flowee" as Flowee +import "../mobile"; + +Page { + headerText: qsTr("Videos") + +} diff --git a/modules/social-feed/SocialFeedModuleInfo.cpp b/modules/social-feed/SocialFeedModuleInfo.cpp new file mode 100644 index 0000000..2c2fe6c --- /dev/null +++ b/modules/social-feed/SocialFeedModuleInfo.cpp @@ -0,0 +1,35 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "SocialFeedModuleInfo.h" + +ModuleInfo * SocialFeedModuleInfo::build() +{ + ModuleInfo *info = new ModuleInfo(); + info->setId("socialFeedModule"); + info->setTitle(tr("Help and Learning")); + info->setDescription(tr("Want to see the experts show how to use Bitcoin Cash " + "with Flowee Pay? Find all you want via this library of videos.")); + info->setIconSource("qrc:/social-feed/social-feed.svg"); + + auto last = new ModuleSection(ModuleSection::OtherSectionType, info); + last->setText(info->title()); + last->setStartQMLFile("qrc:/social-feed/Listing.qml"); + info->addSection(last); + + return info; +} diff --git a/modules/social-feed/SocialFeedModuleInfo.h b/modules/social-feed/SocialFeedModuleInfo.h new file mode 100644 index 0000000..13a3c23 --- /dev/null +++ b/modules/social-feed/SocialFeedModuleInfo.h @@ -0,0 +1,31 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class SocialFeedModuleInfo : public QObject +{ + Q_OBJECT +public: + static ModuleInfo *build(); + + static const char *translationUnit() { + return "social-feed"; + } +}; diff --git a/modules/social-feed/social-feed-data.qrc b/modules/social-feed/social-feed-data.qrc new file mode 100644 index 0000000..92ddd9d --- /dev/null +++ b/modules/social-feed/social-feed-data.qrc @@ -0,0 +1,6 @@ + + + social-feed.svg + Listing.qml + + diff --git a/modules/social-feed/social-feed.svg b/modules/social-feed/social-feed.svg new file mode 100644 index 0000000..e67c7de --- /dev/null +++ b/modules/social-feed/social-feed.svg @@ -0,0 +1,7 @@ + + + + + + + -- 2.54.0 From 6c9f1c465195b816ffff09ad8209e3ad4c8a4a80 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 31 Jan 2025 23:24:49 +0100 Subject: [PATCH 1150/1428] Merge all the instances of QNetworkAccessManager Instead of all places having their own copy and being wasteful in that way, we move ownership of an app wide version to the application singleton. --- modules/blocks/BlockHeadersChecker.cpp | 6 +++--- modules/blocks/BlockHeadersChecker.h | 6 +++--- src/FloweePay.cpp | 11 ++++++++++- src/FloweePay.h | 5 +++++ src/PaymentProtocol.cpp | 4 ++-- src/PaymentProtocol_p.h | 2 -- src/PriceDataProvider.cpp | 7 ++++--- src/PriceDataProvider.h | 5 +++-- src/PriceHistoryDataProvider.cpp | 5 +++-- src/PriceHistoryDataProvider.h | 7 +------ 10 files changed, 34 insertions(+), 24 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index d244c9c..6228660 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -90,7 +90,7 @@ void BlockHeadersChecker::startChecking() if (m_headerReply == nullptr) { QNetworkRequest headRequest((QUrl(HEADERFILE))); headRequest.setTransferTimeout(6000); - m_headerReply = m_network.head(headRequest); + m_headerReply = FloweePay::instance()->network()->head(headRequest); connect(m_headerReply, SIGNAL(finished()), this, SLOT(headerReturned())); } } @@ -176,7 +176,7 @@ void BlockHeadersChecker::headerReturned() setStatus(Downloading); // actually start the download - m_downloadReply = m_network.get(downloadRequest); + m_downloadReply = FloweePay::instance()->network()->get(downloadRequest); connect(m_downloadReply, SIGNAL(readyRead()), this, SLOT(onBytesDownloaded())); connect(m_downloadReply, SIGNAL(finished()), this, SLOT(downloadFinished())); } diff --git a/modules/blocks/BlockHeadersChecker.h b/modules/blocks/BlockHeadersChecker.h index 5e2d907..d95ea45 100644 --- a/modules/blocks/BlockHeadersChecker.h +++ b/modules/blocks/BlockHeadersChecker.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,9 +19,10 @@ #define BLOCKHEADERSCHECKER_H #include -#include #include +class QNetworkReply; + class BlockHeadersChecker : public QObject { Q_OBJECT @@ -84,7 +85,6 @@ private: int m_totalDownload = 0; int m_bytesDownloaded = 0; // download progress - QNetworkAccessManager m_network; QNetworkReply *m_headerReply = nullptr; QNetworkReply *m_downloadReply = nullptr; QFile *m_newHeaders = nullptr; diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 2cfb86a..82ce29d 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -955,6 +955,15 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +QNetworkAccessManager* FloweePay::network() +{ + assert(QThread::currentThread() == thread()); + if (m_network.get() == nullptr) { + m_network.reset(new QNetworkAccessManager()); + } + return m_network.get(); +} + IndexerServices *FloweePay::indexerServices() const { return m_indexerServices; diff --git a/src/FloweePay.h b/src/FloweePay.h index 7008a30..9e96e7b 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -33,6 +33,7 @@ #include #include #include +#include class Wallet; @@ -390,6 +391,9 @@ public: void removeWallet(Wallet *wallet); void addWallet(Wallet *wallet); + // return the app-wide network access manager. + QNetworkAccessManager* network(); + signals: void loadComplete(); /// \internal @@ -447,6 +451,7 @@ private: std::string m_chainPrefix; std::unique_ptr m_downloadManager; std::unique_ptr m_prices; + std::unique_ptr m_network; NotificationManager m_notifications; CameraController* m_cameraController; IndexerServices *m_indexerServices; // the Electrum indexer index. diff --git a/src/PaymentProtocol.cpp b/src/PaymentProtocol.cpp index 4f6dbaa..4e60e11 100644 --- a/src/PaymentProtocol.cpp +++ b/src/PaymentProtocol.cpp @@ -163,7 +163,7 @@ void PaymentProtocolBip70::setUri(const QString &uri) req.setRawHeader("accept", "application/bitcoincash-paymentrequest"); // binary protocol req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); // accept: application/payment-request (JPP) - m_reply = m_network.get(req); + m_reply = FloweePay::instance()->network()->get(req); logInfo(10003) << "fetching bip70 file from:" << req.url().toString(); connect(m_reply, SIGNAL(finished()), this, SLOT(fetchedRequest())); connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), @@ -401,7 +401,7 @@ void PaymentProtocolBip70::sendReply() req.setRawHeader("accept", "application/bitcoincash-paymentack"); req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoincash-payment"); - m_reply = m_network.post(req, messageData); + m_reply = FloweePay::instance()->network()->post(req, messageData); logInfo(10003) << "Sending bip70 reply to:" << req.url().toString(); connect(m_reply, SIGNAL(finished()), this, SLOT(sentTransaction())); connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), diff --git a/src/PaymentProtocol_p.h b/src/PaymentProtocol_p.h index 83b4c98..1c2ea47 100644 --- a/src/PaymentProtocol_p.h +++ b/src/PaymentProtocol_p.h @@ -20,7 +20,6 @@ #include "PaymentProtocol.h" -#include #include #include @@ -60,7 +59,6 @@ private slots: void sendReply(); private: - QNetworkAccessManager m_network; QNetworkReply *m_reply = nullptr; Streaming::BufferPool m_pool; diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 261dad9..45fa283 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -16,7 +16,8 @@ * along with this program. If not, see . */ #include "PriceDataProvider.h" -#include "Logger.h" +#include "FloweePay.h" +#include #include #include @@ -231,7 +232,7 @@ void PriceDataProvider::fetch() if (m_reply) return; if (m_yadioViaUSD && m_currencyConversions.isEmpty()) { - m_reply = m_network.get(QNetworkRequest(QUrl(YadioURL))); + m_reply = FloweePay::instance()->network()->get(QNetworkRequest(QUrl(YadioURL))); logInfo() << "fetching yadio.io USD-conversions"; connect(m_reply, SIGNAL(finished()), this, SLOT(finishedYadioDownload())); return; // one feed at a time. @@ -240,7 +241,7 @@ void PriceDataProvider::fetch() QString url(CoinGeckoURL); url = url.arg(m_yadioViaUSD ? "usd" : m_currency.toLower()); logInfo() << "fetch" << url; - m_reply = m_network.get(QNetworkRequest(QUrl(url))); + m_reply = FloweePay::instance()->network()->get(QNetworkRequest(QUrl(url))); connect(m_reply, SIGNAL(finished()), this, SLOT(finishedDownload())); } diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 32d5f94..6fa6bc1 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -21,8 +21,10 @@ #include #include "PriceHistoryDataProvider.h" -#include #include +#include + +class QNetworkReply; class PriceDataProvider : public QObject { @@ -126,7 +128,6 @@ private: }; Price m_currentPrice; - QNetworkAccessManager m_network; QNetworkReply *m_reply = nullptr; QString m_currency; QString m_currencySymbolPrefix, m_currencySymbolPost; diff --git a/src/PriceHistoryDataProvider.cpp b/src/PriceHistoryDataProvider.cpp index f3a4167..bea527b 100644 --- a/src/PriceHistoryDataProvider.cpp +++ b/src/PriceHistoryDataProvider.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "PriceHistoryDataProvider.h" +#include "FloweePay.h" #include "crypto/compat/endian.h" #include "streaming/BufferPools.h" @@ -347,7 +348,7 @@ void InitialHistoryFetcher::fetch(const QString &path, const QString ¤cy) app->applicationName(), app->applicationVersion()); req.setRawHeader("User-Agent", useragent.toLatin1()); - auto reply = m_network.get(req); + auto reply = FloweePay::instance()->network()->get(req); connect (reply, &QNetworkReply::finished, reply, [=]() { // either everything went fine, or the server doesn't have our file. // in both cases we will not retry and we create a file. diff --git a/src/PriceHistoryDataProvider.h b/src/PriceHistoryDataProvider.h index 0853140..ca98dfd 100644 --- a/src/PriceHistoryDataProvider.h +++ b/src/PriceHistoryDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,6 @@ #include #include -#include - #include class QFile; @@ -102,9 +100,6 @@ public: signals: void success(const QString ¤cy); - -private: - QNetworkAccessManager m_network; }; #endif -- 2.54.0 From 37c1dc044dc9b23ccd2adb3f9bf8c3c156bbc303 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 11:33:18 +0100 Subject: [PATCH 1151/1428] Starting new data provider This reads the social data from a dedicated json file on our webserver and provides this to QML --- modules/social-feed/CMakeLists.txt | 1 + modules/social-feed/FeedDataProvider.cpp | 174 +++++++++++++++++++ modules/social-feed/FeedDataProvider.h | 76 ++++++++ modules/social-feed/Listing.qml | 5 + modules/social-feed/SocialFeedModuleInfo.cpp | 6 + 5 files changed, 262 insertions(+) create mode 100644 modules/social-feed/FeedDataProvider.cpp create mode 100644 modules/social-feed/FeedDataProvider.h diff --git a/modules/social-feed/CMakeLists.txt b/modules/social-feed/CMakeLists.txt index 6ec90a5..af45609 100644 --- a/modules/social-feed/CMakeLists.txt +++ b/modules/social-feed/CMakeLists.txt @@ -18,6 +18,7 @@ project(social-feed_module) set (SOURCES SocialFeedModuleInfo.cpp + FeedDataProvider.cpp ) add_library (social-feed_module_lib STATIC ${SOURCES}) target_link_libraries(social-feed_module_lib pay_lib) diff --git a/modules/social-feed/FeedDataProvider.cpp b/modules/social-feed/FeedDataProvider.cpp new file mode 100644 index 0000000..adcab1c --- /dev/null +++ b/modules/social-feed/FeedDataProvider.cpp @@ -0,0 +1,174 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "FeedDataProvider.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr const char *FEED = "https://flowee.org/v/index.json"; +constexpr const char *FLOWEE_JSON = "flowee.json"; + +FeedDataProvider::FeedDataProvider(QObject *parent) + : QObject(parent) +{ + QFileInfo info(FLOWEE_JSON); + bool useOld = info.exists(); + if (useOld) { + m_lastCheck = info.lastModified(); + readFeed(); + } + else if (m_lastCheck.isNull() + || m_lastCheck.msecsTo(QDateTime::currentDateTime()) > 4 * 60 * 60 * 1000) { + QTimer::singleShot(10, this, SLOT(start())); + } +} + +void FeedDataProvider::start() +{ + QUrl uri(FEED); + QNetworkRequest req(uri); + req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); + + if (m_lastCheck.isNull()) + m_reply = FloweePay::instance()->network()->get(req); + else + m_reply = FloweePay::instance()->network()->head(req); + logInfo() << "fetching social feed file from:" << FEED; + connect(m_reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(m_reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), + this, SLOT(errored(QNetworkReply::NetworkError))); + connect(m_reply, SIGNAL(sslErrors(QList)), + this, SLOT(sslErrors(QList))); +} + +void FeedDataProvider::downloadFinished() +{ + assert(m_reply); + if (m_reply->operation() == QNetworkAccessManager::HeadOperation) { + auto lastModified = m_reply->header(QNetworkRequest::LastModifiedHeader); + m_reply->deleteLater(); + m_reply = nullptr; + // check if the server has a newer one. + if (lastModified.toDateTime() > m_lastCheck) { + m_lastCheck = QDateTime(); + assert(m_lastCheck.isNull()); + start(); // this will now do a download of the actual data. + } + return; + } + auto data = m_reply->readAll(); + m_reply->deleteLater(); + m_reply = nullptr; + QFile feed(FLOWEE_JSON); + if (feed.open(QIODevice::WriteOnly)) { + feed.write(data); + feed.close(); + } + readFeed(); +} + +void FeedDataProvider::readFeed() +{ + QFile feed(FLOWEE_JSON); + if (!feed.open(QIODevice::ReadOnly)) + return; + auto data = feed.readAll(); + feed.close(); + QJsonDocument doc = QJsonDocument::fromJson(data); + if (doc.isNull()) { + logWarning() << "Parsing error of social json"; + return; + } + m_listItems.clear(); + QJsonArray list = doc.array(); + for (auto i = list.begin(); i != list.end(); ++i) { + auto obj = i->toObject(); + auto text = obj["text"]; + if (!text.isString()) { + logWarning() << "Skipping item in JSON, missing text attribute"; + continue; + } + auto *item = new ListItem(this); + item->m_text = text.toString(); + item->m_url = obj["url"].toString(); + item->m_videoLength = obj["length"].toString(); + item->m_title = obj["title"].toString(); + auto date = obj["date"].toString(); + item->m_date = QDate::fromString(date, Qt::ISODate); + + m_listItems.append(item); + } + + emit listItemsChanged(); +} + +QList FeedDataProvider::listItems() const +{ + return m_listItems; +} + +void FeedDataProvider::errored(QNetworkReply::NetworkError err) +{ + // TODO +} + +void FeedDataProvider::sslErrors(const QList &errors) +{ + // TODO +} + + +// ------------------------------------ + +ListItem::ListItem(QObject *parent) + : QObject(parent) +{ +} + +QDate ListItem::date() const +{ + return m_date; +} + +QString ListItem::videoLength() const +{ + return m_videoLength; +} + +QString ListItem::text() const +{ + return m_text; +} + +QString ListItem::title() const +{ + return m_title; +} + +QString ListItem::url() const +{ + return m_url; +} diff --git a/modules/social-feed/FeedDataProvider.h b/modules/social-feed/FeedDataProvider.h new file mode 100644 index 0000000..08a082e --- /dev/null +++ b/modules/social-feed/FeedDataProvider.h @@ -0,0 +1,76 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef FEEDDATAPROVIDER_H +#define FEEDDATAPROVIDER_H + +#include +#include +#include +#include + +class ListItem : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDate date READ date CONSTANT FINAL) + Q_PROPERTY(QString videoLength READ videoLength CONSTANT FINAL) + Q_PROPERTY(QString text READ text CONSTANT FINAL) + Q_PROPERTY(QString title READ title CONSTANT FINAL) + Q_PROPERTY(QString url READ url CONSTANT FINAL) +public: + ListItem(QObject *parent = nullptr); + + QDate date() const; + QString videoLength() const; + QString text() const; + QString title() const; + QString url() const; + + QDate m_date; + QString m_videoLength; + QString m_text; + QString m_title; + QString m_url; +}; + +class FeedDataProvider : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList items READ listItems NOTIFY listItemsChanged FINAL) +public: + explicit FeedDataProvider(QObject *parent = nullptr); + + QList listItems() const; + +signals: + void listItemsChanged(); + +private slots: + void start(); + void downloadFinished(); + void errored(QNetworkReply::NetworkError err); + void sslErrors(const QList &errors); + +private: + void readFeed(); + + QNetworkReply *m_reply = nullptr; + QDateTime m_lastCheck; + QList m_listItems; +}; + +#endif diff --git a/modules/social-feed/Listing.qml b/modules/social-feed/Listing.qml index 307f21a..aee5b0d 100644 --- a/modules/social-feed/Listing.qml +++ b/modules/social-feed/Listing.qml @@ -19,8 +19,13 @@ import QtQuick import QtQuick.Layouts import "../Flowee" as Flowee import "../mobile"; +import Flowee.org.pay.socialfeed; Page { headerText: qsTr("Videos") + Item { // data + Feeds { + } + } } diff --git a/modules/social-feed/SocialFeedModuleInfo.cpp b/modules/social-feed/SocialFeedModuleInfo.cpp index 2c2fe6c..2fe1947 100644 --- a/modules/social-feed/SocialFeedModuleInfo.cpp +++ b/modules/social-feed/SocialFeedModuleInfo.cpp @@ -16,6 +16,9 @@ * along with this program. If not, see . */ #include "SocialFeedModuleInfo.h" +#include "FeedDataProvider.h" + +#include // for the qmlRegisterType ModuleInfo * SocialFeedModuleInfo::build() { @@ -31,5 +34,8 @@ ModuleInfo * SocialFeedModuleInfo::build() last->setStartQMLFile("qrc:/social-feed/Listing.qml"); info->addSection(last); + // notice that a dash is not allowed in the name + qmlRegisterType("Flowee.org.pay.socialfeed", 1, 0, "Feeds"); + return info; } -- 2.54.0 From c78e44f0f63c72573cc31822fe108e5454951b29 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 15:39:44 +0100 Subject: [PATCH 1152/1428] Add display --- modules/social-feed/Listing.qml | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/modules/social-feed/Listing.qml b/modules/social-feed/Listing.qml index aee5b0d..a09f0e2 100644 --- a/modules/social-feed/Listing.qml +++ b/modules/social-feed/Listing.qml @@ -23,9 +23,70 @@ import Flowee.org.pay.socialfeed; Page { headerText: qsTr("Videos") + id: root Item { // data Feeds { + id: feeds + } + } + + ListView { + anchors.fill: parent + model: feeds.items + + delegate: Item { + width: ListView.view.width + height: content.height + 10 + + Column { + id: content + width: parent.width + spacing: 6 + + Flowee.Label { + text: modelData.title + font.pixelSize: root.font.pixelSize * 1.2 + width: parent.width + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Flowee.Label { + text: Pay.formatDate(modelData.date) + font.pixelSize: root.font.pixelSize * 0.8 + } + Flowee.Label { + text: modelData.text + width: parent.width + wrapMode: Text.WordWrap + } + Item { + width: parent.width + height: row.height + Row { + id: row + y: -6 + anchors.right: parent.right + height: buttonText.height + spacing: 10 + Flowee.Label { + id: buttonText + text: qsTr("Run time:") + " " + modelData.videoLength; + } + Image { + id: icon + width: 12 + height: 8 + source: Pay.useDarkSkin ? "qrc:/smallArrow-light.svg" : "qrc:/smallArrow.svg"; + rotation: 270 + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + MouseArea { + anchors.fill: parent + onClicked: Qt.openUrlExternally(modelData.url) + } } } } -- 2.54.0 From 122234c2e3e796e6aae77c93064588d1cc139a0e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 17:17:57 +0100 Subject: [PATCH 1153/1428] Add getter to find a certain plugin, any section --- src/ModuleManager.cpp | 18 +++++++++++++++++- src/ModuleManager.h | 3 ++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index adcbe6f..092c4ef 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -299,6 +299,22 @@ ModuleSection *ModuleManager::sectionOnPlugin(const QString &pluginId, const QSt return nullptr; } +ModuleSection *ModuleManager::moduleSection(const QString &pluginId) const +{ + for (const auto *m : m_modules) { + if (m->id() == pluginId) { + if (m->sections().empty()) + return nullptr; + for (auto *s : m->sections()) { + if (s->type() == ModuleSection::OtherSectionType) + return s; + } + return m->sections().first(); + } + } + return nullptr; +} + void ModuleManager::allSections(const std::function &handler) { for (const auto *m : std::as_const(m_modules)) { diff --git a/src/ModuleManager.h b/src/ModuleManager.h index f5de030..6019bd5 100644 --- a/src/ModuleManager.h +++ b/src/ModuleManager.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -58,6 +58,7 @@ public: QList oftenUsedItems() const; Q_INVOKABLE ModuleSection* sectionOnPlugin(const QString &pluginId, const QString §ionId) const; + Q_INVOKABLE ModuleSection* moduleSection(const QString &pluginId) const; void allSections(const std::function &handler); -- 2.54.0 From 6c1d9d590c3d4c6bf61e87f80cc65d63697513bf Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 17:42:21 +0100 Subject: [PATCH 1154/1428] Add video links to the startup screen. --- guis/mobile.qrc | 1 + guis/mobile/StartupScreen.qml | 67 ++++++++++++++++++++++++++++++ guis/mobile/images/play-button.svg | 16 +++++++ 3 files changed, 84 insertions(+) create mode 100644 guis/mobile/images/play-button.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index bb350d6..c732ab0 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -47,6 +47,7 @@ mobile/images/shrug-light.svg mobile/images/more.svg mobile/images/more-light.svg + mobile/images/play-button.svg mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index 835e4c7..fd4b68f 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -86,6 +86,73 @@ Page { horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter width: column.width * 0.8 + font.pixelSize: root.font.pixelSize * 1.2 + } + + Item { width: 1; height: 10 } // spacer + + GridLayout { + id: videoSection + property QtObject infoObject: + ModuleManager.moduleSection("socialFeedModule"); + columnSpacing: 20 + rowSpacing: 0 + width: 120 + 120 + 20 + + columns: infoObject == null ? 1 : 2 + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + id: logo + height: 120 + width: 120 + radius: 26 + color: mainWindow.floweeBlue + Item { + // clip the logo only, ignore the text part + width: 85 + height: 85 + clip: true + x: 20 + y: 25 + Image { + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: height / 77 * 449 + height: 85 + } + } + Image { + source: "qrc:/play-button.svg" + width: 90 + height: 90 + opacity: 0.8 + anchors.centerIn: parent + } + MouseArea { anchors.fill: parent; onClicked: Qt.openUrlExternally("https://flowee.org/v/gettingstarted.mp4") } + } + + Image { + source: "qrc:/social-feed/social-feed.svg" + Layout.minimumWidth: 120 + Layout.minimumHeight: 120 + visible: parent.infoObject !== null + MouseArea { anchors.fill: parent; onClicked: thePile.push(videoSection.infoObject.qml); } + } + + Flowee.Label { + text: qsTr("Getting Started") + wrapMode: Text.WordWrap + Layout.maximumWidth: 140 + MouseArea { anchors.fill: parent; onClicked: Qt.openUrlExternally("https://flowee.org/v/gettingstarted.mp4") } + } + TextButton { + text: qsTr("All Videos") + visible: parent.infoObject !== null + Layout.maximumWidth: 140 + pageButton: true + onClicked: thePile.push(parent.infoObject.qml); + } } Item { width: 1; height: 10 } // spacer diff --git a/guis/mobile/images/play-button.svg b/guis/mobile/images/play-button.svg new file mode 100644 index 0000000..2b563bf --- /dev/null +++ b/guis/mobile/images/play-button.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + -- 2.54.0 From 62b54655985d37a51df04eeaede79dc39961bd56 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 20:09:34 +0100 Subject: [PATCH 1155/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 94fabd7..18f5e8d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="34" android:versionName="2025.01.1"> diff --git a/src/main.cpp b/src/main.cpp index 874a5af..7978999 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.01.0"); + qapp.setApplicationVersion("2025.01.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 035b5cd68d1f2b4aaf5350bbe8cb24131e1c287d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 1 Feb 2025 20:18:06 +0100 Subject: [PATCH 1156/1428] Fix unreadable button This picks a better color to fit the background and always have good contrast for max readability --- guis/mobile/AccountPageListItem.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 050a25e..9125bb0 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -397,10 +397,11 @@ QQC2.Control { width: reindexLabel.width + 30 radius: 3 - Flowee.Label { + QQC2.Label { id: reindexLabel text: qsTr("Re-scan Chain"); anchors.centerIn: parent + color: "#fcfcfc" } MouseArea { anchors.fill: parent @@ -423,10 +424,11 @@ QQC2.Control { visible: root.account.isArchived radius: 3 - Flowee.Label { + QQC2.Label { id: removeWalletLabel text: qsTr("Remove Wallet"); anchors.centerIn: parent + color: "#fcfcfc" } MouseArea { anchors.fill: parent -- 2.54.0 From c9a767e612d4e9ee803fc921331bb9b730ee863b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Feb 2025 12:54:06 +0100 Subject: [PATCH 1157/1428] Stop showing 'want to update' on archived wallets. --- guis/desktop/AccountListItem.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 3f4ce90..29be615 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -50,7 +50,8 @@ Item { Timer { id: startTimer property int wavy: 0 - running: !Pay.offline && root.account.lastBlockSynched === root.startPos; + running: !root.account.isArchived && !Pay.offline + && root.account.lastBlockSynched === root.startPos; interval: 300 repeat: true onTriggered: { -- 2.54.0 From 35d8d6752d2de455a57d8af0d35c9122e55321e8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 13:09:59 +0100 Subject: [PATCH 1158/1428] Make work better This uses a nicer filename with context. We also build the user agent more correctly. Last, this makes work the usage of HEAD to keep up to date without full download. --- modules/social-feed/FeedDataProvider.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/social-feed/FeedDataProvider.cpp b/modules/social-feed/FeedDataProvider.cpp index adcab1c..2b7c0da 100644 --- a/modules/social-feed/FeedDataProvider.cpp +++ b/modules/social-feed/FeedDataProvider.cpp @@ -27,9 +27,10 @@ #include #include #include +#include constexpr const char *FEED = "https://flowee.org/v/index.json"; -constexpr const char *FLOWEE_JSON = "flowee.json"; +constexpr const char *FLOWEE_JSON = "social-feed-flowee.json"; FeedDataProvider::FeedDataProvider(QObject *parent) : QObject(parent) @@ -40,7 +41,7 @@ FeedDataProvider::FeedDataProvider(QObject *parent) m_lastCheck = info.lastModified(); readFeed(); } - else if (m_lastCheck.isNull() + if (m_lastCheck.isNull() || m_lastCheck.msecsTo(QDateTime::currentDateTime()) > 4 * 60 * 60 * 1000) { QTimer::singleShot(10, this, SLOT(start())); } @@ -50,7 +51,12 @@ void FeedDataProvider::start() { QUrl uri(FEED); QNetworkRequest req(uri); - req.setHeader(QNetworkRequest::UserAgentHeader, "flowee-pay"); + auto app = QCoreApplication::instance(); + QString useragent = QString("%1%2/%3") + .arg(app->organizationName(), + app->applicationName(), + app->applicationVersion()); + req.setHeader(QNetworkRequest::UserAgentHeader, useragent); if (m_lastCheck.isNull()) m_reply = FloweePay::instance()->network()->get(req); -- 2.54.0 From b8206357c3ac55811ce31223f5c9f53f0b627a3d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 13:49:13 +0100 Subject: [PATCH 1159/1428] New year --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index 91673ed..ac6b756 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -51,7 +51,7 @@ Page { } TextButton { text: qsTr("Credits") - subtext: qsTr("© 2020-2024 Tom Zander and contributors") + subtext: qsTr("© 2020-2025 Tom Zander and contributors") pageButton: true onClicked: thePile.push(creditsPage) -- 2.54.0 From b709cad86c6cc797b89b0f820a3922c0f1a4b09b Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 16:06:23 +0100 Subject: [PATCH 1160/1428] [desktop] Add 'copy' option on the seed phrase. --- guis/desktop/AccountDetails.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 7451311..26fdfcf 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -311,6 +311,19 @@ Item { mouseSelectionMode: TextEdit.SelectWords wrapMode: TextEdit.WordWrap padding: 0 + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: menu.popup(parent); + + QQC2.Menu { + id: menu + QQC2.MenuItem { + text: qsTr("Copy") + onTriggered: Pay.copyToClipboard(root.account.mnemonic) + } + } + } } Flowee.Label { text: qsTr("Password") + ":" -- 2.54.0 From a038ff7c2276611d4d4431510b8a17ebb19c138e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 16:57:13 +0100 Subject: [PATCH 1161/1428] Add a QR button to the seed phrase page. --- guis/desktop/AccountDetails.qml | 43 +++++++++++++++++++++- guis/{mobile => }/images/qr-code-light.svg | 0 guis/{mobile => }/images/qr-code.svg | 0 guis/mobile.qrc | 2 - guis/widgets.qrc | 2 + 5 files changed, 44 insertions(+), 3 deletions(-) rename guis/{mobile => }/images/qr-code-light.svg (100%) rename guis/{mobile => }/images/qr-code.svg (100%) diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 26fdfcf..8dd2ce1 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -44,6 +44,32 @@ Item { root.account.secrets.showUsedAddresses = usedAddresses.checked } } + Item { + // non-layoutable items. + + QQC2.Popup { + id: qrPopup + width: 270 + height: 270 + x: (root.width - width) / 2 + y: 100 + modal: true + closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent + background: Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + } + Flowee.QRWidget { + id: seedQr + qrSize: 250 + textVisible: false + useRawString: true + anchors.centerIn: parent + } + } + } Flowee.Label { id: walletDetailsLabel @@ -299,7 +325,7 @@ Item { id: grid visible: root.account.isDecrypted width: parent.width - columns: 2 + columns: 3 Flowee.Label { text: qsTr("Seed-phrase") + ":" } @@ -325,11 +351,24 @@ Item { } } } + Image { + Layout.maximumWidth: 20 + Layout.maximumHeight: 20 + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + MouseArea { + anchors.fill: parent + onClicked: { + seedQr.qrText = root.account.mnemonic + qrPopup.open(); + } + } + } Flowee.Label { text: qsTr("Password") + ":" visible: root.account.mnemonicPwd !== "" } Flowee.Label { + Layout.columnSpan: 2 text: root.account.mnemonicPwd visible: text !== "" } @@ -338,6 +377,7 @@ Item { visible: root.account.isElectrumMnemonic } Flowee.Label { + Layout.columnSpan: 2 id: seedPhraseFormat font.bold: true text: "Electrum" @@ -347,6 +387,7 @@ Item { text: qsTr("Derivation") + ":" } Flowee.LabelWithClipboard { + Layout.columnSpan: 2 text: root.account.hdDerivationPath } } diff --git a/guis/mobile/images/qr-code-light.svg b/guis/images/qr-code-light.svg similarity index 100% rename from guis/mobile/images/qr-code-light.svg rename to guis/images/qr-code-light.svg diff --git a/guis/mobile/images/qr-code.svg b/guis/images/qr-code.svg similarity index 100% rename from guis/mobile/images/qr-code.svg rename to guis/images/qr-code.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c732ab0..e52ef53 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -15,8 +15,6 @@ mobile/images/sending-light.svg mobile/images/sending.svg mobile/images/receive.svg - mobile/images/qr-code.svg - mobile/images/qr-code-light.svg mobile/images/backspace.svg mobile/images/backspace-light.svg mobile/images/confirmIcon.svg diff --git a/guis/widgets.qrc b/guis/widgets.qrc index e0824cc..933d4aa 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -16,6 +16,8 @@ images/flash.svg images/flash-light.svg images/Flowee-Symbols.otf + images/qr-code.svg + images/qr-code-light.svg images/qr-code-scan.svg images/qr-code-scan-light.svg Flowee/ArrowPoint.qml -- 2.54.0 From 5b3a779aa4bd7b48413f8ceafbcdd0c9e7c19919 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 16:59:47 +0100 Subject: [PATCH 1162/1428] Make 'secrets' input field use monospace font To input something like a private key, using monospace will be much easier. --- guis/desktop/NewAccountImportAccount.qml | 3 ++- guis/mobile/ImportWalletPage.qml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 21715b4..2ca0d23 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -163,6 +163,7 @@ Item { width: parent.width clip: true height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) + font.family: "monospace" } Flowee.TextPasteDecorator { diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index e22d9f6..f305e8d 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -229,6 +229,7 @@ Page { width: parent.width clip: true height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) + font.family: "monospace" } Flowee.TextPasteDecorator { -- 2.54.0 From e5c390040dcb13bea8fd111a7cecabf0cb3d5d51 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 17:26:39 +0100 Subject: [PATCH 1163/1428] Add 'are you sure' on deletion of wallet. --- guis/mobile/AccountPageListItem.qml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 9125bb0..c901089 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -434,12 +434,23 @@ QQC2.Control { anchors.fill: parent cursorShape: Qt.ArrowCursor onClicked: { - thePile.pop(); - root.account.removeAccount(); + errorDialog.visible = true + errorDialog.forceActiveFocus() } } } Item { width: 1; height: 20 } // spacer. } + + Flowee.Dialog { + id: errorDialog + standardButtons: QQC2.DialogButtonBox.Ok + QQC2.DialogButtonBox.Cancel + title: qsTr("Really Delete?") + text: qsTr("Removing wallet \"%1\" can not be undone.", "argument is the wallet name").arg(root.account.name) + onAccepted: { + thePile.pop(); + root.account.removeAccount(); + } + } } -- 2.54.0 From 923d472316d7231104aa7ed84114608d65f5899a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 17:35:19 +0100 Subject: [PATCH 1164/1428] Move 'go up' button to bottom of screen. This is a UX improvements and it also helps fit the Android expected behavior. The list has a button to jump back to the top, it is now positioned better and avoids the scrollbar handle. --- guis/mobile/AccountHistory.qml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index e39ddd5..831b420 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -32,14 +32,22 @@ ListView { id: backToTopButton width: 60 height: 60 + radius: 35 anchors.right: parent.right + anchors.rightMargin: 30 y: { var indexAtTopOfScreen = root.indexAt(10, root.contentY + 10); + if (indexAtTopOfScreen === -1) { // just in case that's just a section-header + indexAtTopOfScreen = root.indexAt(10, root.contentY + 80); + } + if (indexAtTopOfScreen > 3) - return 0; + return root.height - height - 15; return height * -1; // out of screen. } color: "#66000000" + border.width: 1.3 + border.color: palette.button Image { id: upIcon -- 2.54.0 From 383c9f7debd9ae4e3a36d329cdce97ee8cc4a419 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Feb 2025 17:41:47 +0100 Subject: [PATCH 1165/1428] Check for more derivation paths. --- src/ImportHandler.cpp | 13 ++++++++++++- src/ImportHandler.h | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index 05b4c06..a2bb860 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,11 +93,22 @@ void ImportHandler::checkNext() switch (m_nextToCheck) { case MainDerivation: type = HDMasterKey::BIP39Mnemonic; + m_nextToCheck = MainDerivationChange; + break; + case MainDerivationChange: + type = HDMasterKey::BIP39Mnemonic; + derivation[3] = 1; m_nextToCheck = Derivation145; break; case Derivation145: type = HDMasterKey::BIP39Mnemonic; derivation[1] = 145 + HDMasterKey::Hardened; + m_nextToCheck = Derivation145Change; + break; + case Derivation145Change: + type = HDMasterKey::BIP39Mnemonic; + derivation[1] = 145 + HDMasterKey::Hardened; + derivation[3] = 1; m_nextToCheck = m_type == FromSeed ? MainDerivationElectrumMnemonic : Done; break; case MainDerivationElectrumMnemonic: diff --git a/src/ImportHandler.h b/src/ImportHandler.h index 8751a35..02e2f66 100644 --- a/src/ImportHandler.h +++ b/src/ImportHandler.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -69,7 +69,9 @@ private: // for seeds, we need to check multiple options, we iterate through these. enum NextToCheck { MainDerivation, + MainDerivationChange, Derivation145, + Derivation145Change, MainDerivationElectrumMnemonic, Derivation145ElectrumMnemonic, Done -- 2.54.0 From 705137e649ef7ac6f2684d8f4635f108eded1ce2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 12:12:34 +0100 Subject: [PATCH 1166/1428] Make sure we put the time on historical transactions. --- guis/mobile/TransactionListItem.qml | 15 +++------------ src/FloweePay.cpp | 6 +++--- src/FloweePay.h | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 6c145b9..58b2530 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -17,6 +17,7 @@ */ import QtQuick import "../Flowee" as Flowee +import Flowee.org.pay Item { property double amountBch: isMoved ? model.fundsIn @@ -72,7 +73,7 @@ Item { anchors.bottom: ruler.top anchors.right: price.visible ? price.left : price.right anchors.left: parent.left - anchors.leftMargin: 80 + anchors.leftMargin: 72 clip: true // future, maybe wordwrap? font.strikeout: isRejected text: { @@ -101,17 +102,7 @@ Item { if (minedHeight === -2) return qsTr("Rejected") var date = model.date; - var today = new Date(); - if (date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth()) { - let day = today.getDate(); - if (date.getDate() === day || date.getDate() === day - 1) { - // Then this is an item in the 'today' or the 'yesterday' group. - // specify more specific date/time - return Qt.formatTime(date); - } - } - - return Pay.formatDate(model.date); + return Pay.formatDate(date, FloweePay.NoYear) + " " + Qt.formatTime(date); } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 82ce29d..76288e2 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -641,14 +641,14 @@ QString FloweePay::amountToString(qint64 price, UnitOfBitcoin unit) return QString::fromLatin1(str); } -QString FloweePay::formatDate(QDateTime date) const +QString FloweePay::formatDate(QDateTime date, FloweePay::DateFormatOption type) const { static QString format = QLocale::system().dateFormat(QLocale::ShortFormat); if (!date.isValid() || date.isNull()) return QString(); const QDateTime now = QDateTime::currentDateTime(); - if (now > date) { + if (type == SmartDate && now > date) { // use the 'yesterday' style if the date is reasonably close. const auto days = date.daysTo(now); if (days == 0) @@ -659,7 +659,7 @@ QString FloweePay::formatDate(QDateTime date) const return date.toString("dddd"); } - if (date.date().year() == QDate::currentDate().year()) { + if (type == NoYear || date.date().year() == QDate::currentDate().year()) { static QString shortFormat; if (shortFormat.isEmpty()) { // We basically just need to know if this locale has months first or not diff --git a/src/FloweePay.h b/src/FloweePay.h index 9e96e7b..0951abc 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -187,7 +187,21 @@ public: /// for a price, in satoshis, return a formatted string static QString amountToString(qint64 price, UnitOfBitcoin unit); - Q_INVOKABLE QString formatDate(QDateTime date) const; + enum DateFormatOption { + NoYear, ///< always avoid adding the year. + SmartDate ///< Use 'yesterday' and similar short hands, add year if it is not this year. + }; + Q_ENUM(DateFormatOption) + + /** + * Returns a locale adjusted rendering of a date. + * @code + * import Flowee.org.pay + * Label { text = Pay.formatDate(myDate, FloweePay.SmartDate) } + * @endcode + * @see formatDateTime() + */ + Q_INVOKABLE QString formatDate(QDateTime date, DateFormatOption format = SmartDate) const; /** * Returns a user-friendly formatting of the given date-time. * This incudes us returning words (translated) like "today" or "an hour ago". -- 2.54.0 From b6c598510dfc73b66b0dbc9fada657af8da868e1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 12:32:22 +0100 Subject: [PATCH 1167/1428] Make overlay component distinct Instead of making the overlay live inside of the popup outline, make it live outside which basically means it should not move and look just like it is the original except not shaded. --- guis/mobile/AccountHistory.qml | 16 ++++++++++++++-- guis/mobile/PopupOverlay.qml | 21 +++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 831b420..4928985 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -331,8 +331,20 @@ ListView { } Component { id: overlayTxListItem - TransactionListItem { - implicitHeight: 60 + Item { + height: 80 + width: 10 + Rectangle { + color: palette.base + width: parent.width - 18 + height: parent.height + x: 9 + } + TransactionListItem { + width: parent.width + implicitHeight: 60 + y: 10 + } } } diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 87eb3cc..aae09e3 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -57,6 +57,10 @@ FocusScope { id: thePopup width: parent.width height: 100 + leftInset: 0 + rightInset: 0 + topInset: 0 + bottomInset: 0 modal: true closePolicy: QQC2.Popup.CloseOnEscape + QQC2.Popup.CloseOnReleaseOutside property rect sourceRect: Qt.rect(0, 0, 0, 0) @@ -69,8 +73,6 @@ FocusScope { } background: Rectangle { color: palette.light - border.color: palette.midlight - border.width: 1 radius: 5 } Loader { @@ -92,19 +94,19 @@ FocusScope { thePopup.height += h2 + 10; if (root.height - rect.bottom >= h) { // fits below - thePopup.y = rect.bottom - h2 - 22; + thePopup.y = rect.bottom - h2 - 12; overlayLoader.y = 0 loader.y = h2 + 10; } else if (h < rect.y) { // fits above - thePopup.y = rect.y - h; + thePopup.y = rect.y - h - 22; overlayLoader.y = h + 10; loader.y = 0; } else { thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps overlayLoader.y = 0 // item above - loader.y = h2 + 10; + loader.y = h2 - 10; } return; @@ -117,7 +119,14 @@ FocusScope { thePopup.y = rect.y - h; else thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps - + } + Rectangle { + color: "#00000000" + border.color: palette.midlight + border.width: 1 + radius: 5 + anchors.fill: parent + anchors.margins: -10 } } Loader { -- 2.54.0 From fc2bf18632eb035ab85b966c8bcb75303994a441 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 12:56:12 +0100 Subject: [PATCH 1168/1428] Be smarter with mined-date in popup In case the printed date would be identical, we now avoid wasting time in the popup on the mined date. Additionally, when a transaction is unconfirmed, we print that it is waiting for a block now. --- guis/mobile/TransactionInfoSmall.qml | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index b58154c..0923d13 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -34,7 +34,7 @@ ColumnLayout { property int minedHeight: model.height // local cache Flowee.Label { - property bool isRejected: root.minedHeight == -2; // -2 is the magic block-height indicating 'rejected' + property bool isRejected: root.minedHeight === -2; // -2 is the magic block-height indicating 'rejected' text: { if (isRejected) return qsTr("Transaction is rejected") @@ -51,17 +51,30 @@ ColumnLayout { width: parent.width Flowee.Label { - visible: root.minedHeight > 0 + id: minedLabel + visible: { + var h = root.minedHeight; + if (h == -2) + return false; + if (h <= 0 ) + return true; + var boringTime = Pay.formatDateTime(model.date); + return boringTime !== minedDateLabel.text + } text: qsTr("Mined") + ":" } Flowee.Label { + id: minedDateLabel Layout.fillWidth: true - visible: root.minedHeight > 0 + visible: minedLabel.visible text: { - if (root.minedHeight <= 0) - return ""; - var rc = Pay.formatBlockTime(root.minedHeight); - var confirmations = Pay.headerChainHeight - root.minedHeight + 1; + var h = root.minedHeight; + if (h <= 0) { + if (root.minedHeight !== -2) // is not rejected + return qsTr("Waiting for block"); + } + var rc = Pay.formatBlockTime(h); + var confirmations = Pay.headerChainHeight - h + 1; if (confirmations > 0 && confirmations < 20) rc += " (" + qsTr("%1 blocks ago", "Confirmations", confirmations).arg(confirmations) + ")"; return rc; -- 2.54.0 From e6505a03676fedb5a716a5fe065c3cdd70435e35 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 12:36:45 +0100 Subject: [PATCH 1169/1428] Avoid showing the same bch amount twice. In the popup the line with the bch amount will now not be shown when it is already shown in the main view. --- guis/mobile/TransactionInfoSmall.qml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 0923d13..1dcb289 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -83,25 +83,28 @@ ColumnLayout { Flowee.Label { id: paymentTypeLabel Layout.columnSpan: isMoved ? 2 : 1 + visible: text !== "" text: { if (model.isCoinbase) return qsTr("Miner Reward") + ":"; if (model.isFused) return qsTr("Fees") + ":"; - if (model.fundsIn === 0) - return qsTr("Received") + ":"; if (isMoved) return qsTr("Payment to self"); + if (Pay.activityShowsBch) + return ""; + if (model.fundsIn === 0) + return qsTr("Received") + ":"; return qsTr("Sent") + ":"; } } Flowee.BitcoinAmountLabel { - visible: isMoved === false + visible: isMoved === false && paymentTypeLabel.visible Layout.fillWidth: true colorizeValue: model.fundsOut - model.fundsIn + (infoObject == null ? 0 : infoObject.fees) value: Math.abs(colorizeValue) fiatTimestamp: model.date - showFiat: false // might not fit + showFiat: false } } -- 2.54.0 From 300c6842acecf68cd0d34e29e94b0f00dee11cb2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 15:50:24 +0100 Subject: [PATCH 1170/1428] Make user comment editable on first popup This revisits the usage of the Info object and who owns it, in order to increase the stability of the UI/UX And, as said, it adds a way to edit the user comment directly in the first popup in a nice user experience. --- guis/mobile/AccountHistory.qml | 3 +- guis/mobile/EditableLabel.qml | 9 ++-- guis/mobile/TransactionDetails.qml | 63 +++++++++-------------- guis/mobile/TransactionInfoSmall.qml | 4 +- guis/mobile/TransactionListItem.qml | 75 ++++++++++++++++++++++------ src/TransactionInfo.h | 7 ++- 6 files changed, 98 insertions(+), 63 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 4928985..cef233c 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -313,7 +313,7 @@ ListView { onClicked: { root.currentIndex = index; var newItem = popupOverlay.open(selectedItem, transactionDelegate, overlayTxListItem); - newItem.infoObject = portfolio.current.txInfo(model.walletIndex, parent); + newItem.infoObject = portfolio.current.txInfo(model.walletIndex, newItem); root.model.freezeModel = true; } Connections { @@ -343,6 +343,7 @@ ListView { TransactionListItem { width: parent.width implicitHeight: 60 + commentEditable: true y: 10 } } diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index 56c98ca..c8d53c9 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ Item { height: editWidget.height property alias text: ourLabel.text property bool editable: true + baselineOffset: editWidget.baselineOffset signal edited; @@ -39,7 +40,7 @@ Item { Image { id: editIcon anchors.right: parent.right - anchors.bottom: ourLabel.bottom + y: editWidget.baselineOffset - height width: 16 height: 16 smooth: true @@ -52,7 +53,7 @@ Item { return 1; // enabled } visible: opacity > 0 - enabled: editWidget.visible == false + enabled: editWidget.visible === false MouseArea { anchors.fill: parent anchors.margins: -15 @@ -68,7 +69,7 @@ Item { id: editWidget anchors.left: parent.left anchors.right: editIcon.left - anchors.rightMargin: 10 + anchors.rightMargin: 6 visible: false focus: visible text: ourLabel.text diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index ccbf227..d64601f 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,13 +23,14 @@ import Flowee.org.pay; Page { id: root - property QtObject transaction: null - property QtObject infoObject: null + required property QtObject wallet; + required property int txIndex; + readonly property QtObject infoObject: wallet.txInfo(txIndex, root) headerText: qsTr("Transaction Details") property QtObject openInExplorer: QQC2.Action { text: qsTr("Open in Explorer") - onTriggered: Pay.openInExplorer(root.transaction.txid); + onTriggered: Pay.openInExplorer(root.infoObject.txid); } menuItems: [ openInExplorer ] @@ -50,7 +51,7 @@ Page { Flowee.Label { id: txidLabel - text: root.transaction == null ? "" : root.transaction.txid + text: root.infoObject.txid anchors.left: parent.left anchors.right: copyIcon.left anchors.rightMargin: 10 @@ -92,24 +93,17 @@ Page { title: qsTr("First Seen") Flowee.Label { Layout.fillWidth: true - text: { - var tx = root.transaction; // delayed initialized - if (tx == null) - return ""; - return Qt.formatDateTime(tx.date); - } + text: Qt.formatDateTime(root.infoObject.date); } } PageTitledBox { title: { - if (root.transaction == null) - return "" - var h = root.transaction.height; + var h = root.infoObject.minedHeight; if (h === -2) return qsTr("Rejected") if (h === -1) - return qsTr("Unconfirmed") + return qsTr("Waiting for block") return qsTr("Mined at") } Flowee.Label { @@ -117,10 +111,7 @@ Page { wrapMode: Text.WrapAtWordBoundaryOrAnywhere Layout.fillWidth: true text: { - var tx = root.transaction; - if (tx == null) - return ""; - let txHeight = tx.height; + let txHeight = root.infoObject.minedHeight; if (txHeight < 1) return ""; @@ -139,9 +130,9 @@ Page { EditableLabel { id: editableLabel width: parent.width - text: root.transaction == null ? "" : root.transaction.comment - editable: root.infoObject != null && infoObject.commentEditable - onEdited: if (root.infoObject != null) infoObject.userComment = text + text: root.infoObject.userComment + editable: infoObject.commentEditable + onEdited: infoObject.userComment = text } } @@ -149,18 +140,18 @@ Page { visible: fiatPrices.visible Flowee.FiatTxInfo { id: fiatPrices - txInfo: root.transaction + txInfo: root.infoObject width: parent.width } } PageTitledBox { Flowee.Label { - text: root.infoObject == null ? "" : qsTr("Size: %1 bytes").arg(infoObject.size) + text: qsTr("Size: %1 bytes").arg(infoObject.size) } Flowee.Label { text: qsTr("Coinbase") - visible: root.transaction != null && root.transaction.isCoinbase + visible: root.infoObject.isCoinbase } } @@ -168,14 +159,13 @@ Page { // this account created. PageTitledBox { title: qsTr("Fees paid") - visible: infoObject != null && infoObject.fees > 0 + visible: infoObject.fees > 0 Flowee.Label { - text: root.infoObject == null ? "" : qsTr("%1 Satoshi / 1000 bytes") - .arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) + text: qsTr("%1 Satoshi / 1000 bytes").arg((infoObject.fees * 1000 / infoObject.size).toFixed(0)) } Flowee.BitcoinAmountLabel { - value: root.infoObject == null ? 0 : infoObject.fees - fiatTimestamp: root.transaction === null ? undefined : root.transaction.date + value: infoObject.fees + fiatTimestamp: root.infoObject.date colorize: false } } @@ -183,8 +173,6 @@ Page { PageTitledBox { spacing: 10 title: { - if (infoObject == null) - return ""; if (infoObject.isFused) return qsTr("Fused from my addresses"); if (infoObject.createdByUs) @@ -193,7 +181,6 @@ Page { return qsTr("Sent from addresses"); return ""; } - visible: title !== "" Repeater { /* @@ -201,7 +188,7 @@ Page { * no details about which address or anything else these funds come from if we didn't * create the Tx. So just don't show anything. */ - model: parent.visible ? infoObject.knownInputs : 0 + model: infoObject.knownInputs delegate: Item { Layout.alignment: Qt.AlignRight width: content.width @@ -226,7 +213,7 @@ Page { Flowee.BitcoinAmountLabel { id: amount value: -1 * modelData.value - fiatTimestamp: root.transaction.date + fiatTimestamp: root.infoObject.date anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 10 @@ -239,8 +226,6 @@ Page { PageTitledBox { spacing: 10 title: { - if (infoObject == null) - return ""; if (infoObject.isFused) return qsTr("Fused into my addresses"); if (infoObject.createdByUs) @@ -249,7 +234,7 @@ Page { } Repeater { - model: root.infoObject == null ? 0 : infoObject.knownOutputs + model: infoObject.knownOutputs delegate: Item { Layout.alignment: Qt.AlignRight width: content.width @@ -280,7 +265,7 @@ Page { Flowee.BitcoinAmountLabel { id: outAmount value: modelData.value - fiatTimestamp: root.transaction.date + fiatTimestamp: root.infoObject.date colorize: modelData.forMe anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 1dcb289..6c4f37c 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -159,10 +159,8 @@ ColumnLayout { text: qsTr("Transaction Details") pageButton: true onClicked: { - var newItem = thePile.push("./TransactionDetails.qml") + var newItem = thePile.push("./TransactionDetails.qml", { "wallet": portfolio.current, "txIndex": root.infoObject.txIndex }) popupOverlay.close(); - newItem.transaction = model; - newItem.infoObject = root.infoObject; } } } diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 58b2530..ef29c7a 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -23,6 +23,7 @@ Item { property double amountBch: isMoved ? model.fundsIn : (model.fundsOut - model.fundsIn) property bool isRejected: model.height === -2 // special height as defined by the wallet + property alias commentEditable: editIcon.visible baselineOffset: dateLine.y + dateLine.baselineOffset implicitWidth: 360 @@ -70,28 +71,72 @@ Item { Flowee.Label { id: commentLabel - anchors.bottom: ruler.top anchors.right: price.visible ? price.left : price.right + anchors.rightMargin: 10 anchors.left: parent.left anchors.leftMargin: 72 - clip: true // future, maybe wordwrap? - font.strikeout: isRejected - text: { + y: { + if (price.visible && Pay.activityShowsBch) // baseline align to price + return price.y + price.baselineOffset - baselineOffset + return parent.height / 2 - height + } + + Component.onCompleted: fetchText(); + function fetchText() { var comment = model.comment if (comment !== "") - return comment; - - if (model.isCoinbase) - return qsTr("Miner Reward"); - if (model.isFused) - return qsTr("Fused"); - if (model.fundsIn === 0) - return qsTr("Received"); - if (isMoved) - return qsTr("Moved"); - return qsTr("Sent"); + text = comment; + else if (model.isCoinbase) + text = qsTr("Miner Reward"); + else if (model.isFused) + text = qsTr("Fused"); + else if (model.fundsIn === 0) + text = qsTr("Received"); + else if (isMoved) + text = qsTr("Moved"); + else + text = qsTr("Sent"); } } + + Image { + id: editIcon + x: commentLabel.x + commentLabel.contentWidth + 6 + y: commentLabel.y + commentLabel.baselineOffset - height + width: 16 + height: 16 + smooth: true + visible: false + source: "qrc:/edit-pen" + (Pay.useDarkSkin ? "-light" : "") + ".svg" + enabled: editWidget.visible === false + MouseArea { + anchors.fill: parent + anchors.margins: -15 + onClicked: { + editWidget.visible = !editWidget.visible; + if (editWidget.focus) + editWidget.forceActiveFocus(); + } + } + } + Flowee.TextField { + id: editWidget + property QtObject infoObject: null + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.right: commentLabel.right + visible: false + focus: visible + text: model.comment + onEditingFinished: { + if (editWidget.infoObject === null) + editWidget.infoObject = portfolio.current.txInfo(model.walletIndex, editWidget); + editWidget.infoObject.userComment = text; + commentLabel.fetchText(); + visible = false; + } + } + Flowee.Label { id: dateLine anchors.top: ruler.bottom diff --git a/src/TransactionInfo.h b/src/TransactionInfo.h index 96eaed3..2c30d7b 100644 --- a/src/TransactionInfo.h +++ b/src/TransactionInfo.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -121,6 +121,7 @@ class TransactionInfo : public QObject Q_PROPERTY(QDateTime date READ transactionDate CONSTANT FINAL) Q_PROPERTY(double fundsIn READ fundsIn CONSTANT FINAL) Q_PROPERTY(double fundsOut READ fundsOut CONSTANT FINAL) + Q_PROPERTY(int txIndex READ txIndex CONSTANT FINAL) public: explicit TransactionInfo(QObject *parent = nullptr); @@ -154,6 +155,10 @@ public: /// the transactionId, as string. QString txid() const; + int txIndex() const { + return m_walletIndex; + } + /** * The API allows the user to set a 'user comment'. Which needs * to be forwarded to the wallet to make that persistent. -- 2.54.0 From 4b910af4653fed676179be852163095e2f14a505 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 16:57:40 +0100 Subject: [PATCH 1171/1428] Monitor selected currency. When the user changes currency, this will now get recalculated and thus the prices all will update on the main tab. --- guis/mobile/TransactionListItem.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index ef29c7a..1be6980 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -172,11 +172,8 @@ Item { Flowee.Label { id: amount text: { - var dat = model.date; - if (typeof dat === "undefined") // unconfirmed transactions have no date - var fiatPrice = Fiat.price; - else - fiatPrice = Fiat.historicalPrice(dat); + var dummy = Fiat.currencyName; // trigger a recalc when user changes currency + var fiatPrice = Fiat.historicalPrice(model.date); return Fiat.formattedPrice(Math.abs(amountBch), fiatPrice); } anchors.centerIn: parent -- 2.54.0 From 89e8ba602f4acfa8bade7c58905a6515319b8e16 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 18:07:46 +0100 Subject: [PATCH 1172/1428] Split big file --- .../build-transaction/DestinationEditPage.qml | 159 ++++++++++++++++++ modules/build-transaction/PayToOthers.qml | 135 +-------------- .../build-transactions-data.qrc | 3 +- 3 files changed, 162 insertions(+), 135 deletions(-) create mode 100644 modules/build-transaction/DestinationEditPage.qml diff --git a/modules/build-transaction/DestinationEditPage.qml b/modules/build-transaction/DestinationEditPage.qml new file mode 100644 index 0000000..ed38655 --- /dev/null +++ b/modules/build-transaction/DestinationEditPage.qml @@ -0,0 +1,159 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick; +import QtQuick.Controls as QQC2; +import "../Flowee" as Flowee; +import "../mobile"; +import Flowee.org.pay; + + +Page { + id: destinationEditPage + headerText: qsTr("Edit Destination") + + property QtObject sendAllAction: QQC2.Action { + checkable: true + checked: paymentDetail.maxSelected + text: qsTr("Send All", "all money in wallet") + onTriggered: paymentDetail.maxSelected = checked + } + + Flowee.Label { + id: destinationLabel + text: qsTr("Bitcoin Cash Address") + ":" + } + + Flowee.MultilineTextField { + id: destination + anchors.top: destinationLabel.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.right: parent.right + height: Math.max(destinationLabel.height * 2.3, implicitHeight) + focus: enabled + property var addressType: Pay.identifyString(totalText); + text: paymentDetail.address + nextFocusTarget: priceInput + enabled: paymentDetail.editable + onTotalTextChanged: { + paymentDetail.address = totalText + addressInfo.createInfo(); + } + color: { + if (!activeFocus && totalText !== "" && !addressInfo.addressOk) + return mainWindow.errorRed + return palette.windowText + } + } + + Flowee.LabelWithClipboard { + id: nativeLabel + width: parent.width + anchors.top: destination.bottom + anchors.topMargin: 10 + + // only show if its substantially different + visible: text!== "" && text !== destination.text && destination.text !== paymentDetail.formattedTarget + text: paymentDetail.niceAddress + clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: + font.italic: true + menuText: qsTr("Copy Address") + } + Flowee.AddressInfoWidget { + id: addressInfo + anchors.top: nativeLabel.visible ? nativeLabel.bottom : destination.bottom + width: parent.width + addressType: destination.addressType + } + PriceInputWidget { + id: priceInput + width: parent.width + anchors.top: addressInfo.bottom + paymentBackend: paymentDetail + fiatFollowsSats: paymentDetail.fiatFollows + onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats + } + + AccountSelectorWidget { + id: walletNameBackground + anchors.bottom: numericKeyboard.top + anchors.bottomMargin: 10 + stickyAccount: true + onSelectedAccountChanged: payment.account = selectedAccount + + balanceActions: [ sendAllAction ] + } + + NumericKeyboardWidget { + id: numericKeyboard + anchors.bottom: parent.bottom + anchors.bottomMargin: 15 + width: parent.width + enabled: !paymentDetail.maxSelected + dataInput: priceInput + } + + Rectangle { + color: mainWindow.errorRedBg + radius: 15 + width: parent.width + height: warningColumn.height + 20 + anchors.top: nativeLabel.bottom + // BTC address entered warning. + visible: (destination.addressType === Wallet.LegacySH + || destination.addressType === Wallet.LegacyPKH) + && paymentDetail.forceLegacyOk === false; + + Column { + id: warningColumn + x: 10 + y: 10 + width: parent.width - 20 + spacing: 10 + Flowee.Label { + font.bold: true + font.pixelSize: warning.font.pixelSize * 1.2 + text: qsTr("Warning") + color: "white" + anchors.horizontalCenter: parent.horizontalCenter + } + Flowee.Label { + id: warning + width: parent.width + color: "white" + text: qsTr("This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address?") + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + Item { + width: parent.width + height: okButton.height + QQC2.Button { + id: okButton + anchors.right: parent.right + text: qsTr("I am certain") + onClicked: paymentDetail.forceLegacyOk = true + } + } + } + } + Flowee.TextPasteDecorator { + buddy: destination + clipboardTypes: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses + y: destination.y + destination.height - height / 3 + } +} diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 97f848a..aab0e49 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -249,140 +249,7 @@ Page { Component { id: destinationEditPage - Page { - headerText: qsTr("Edit Destination") - - property QtObject sendAllAction: QQC2.Action { - checkable: true - checked: paymentDetail.maxSelected - text: qsTr("Send All", "all money in wallet") - onTriggered: paymentDetail.maxSelected = checked - } - - Flowee.Label { - id: destinationLabel - text: qsTr("Bitcoin Cash Address") + ":" - } - - Flowee.MultilineTextField { - id: destination - anchors.top: destinationLabel.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.right: parent.right - height: Math.max(destinationLabel.height * 2.3, implicitHeight) - focus: enabled - property var addressType: Pay.identifyString(totalText); - text: paymentDetail.address - nextFocusTarget: priceInput - enabled: paymentDetail.editable - onTotalTextChanged: { - paymentDetail.address = totalText - addressInfo.createInfo(); - } - color: { - if (!activeFocus && totalText !== "" && !addressInfo.addressOk) - return mainWindow.errorRed - return palette.windowText - } - } - - Flowee.LabelWithClipboard { - id: nativeLabel - width: parent.width - anchors.top: destination.bottom - anchors.topMargin: 10 - - // only show if its substantially different - visible: text!== "" && text !== destination.text && destination.text !== paymentDetail.formattedTarget - text: paymentDetail.niceAddress - clipboardText: paymentDetail.formattedTarget // the one WITH bitcoincash: - font.italic: true - menuText: qsTr("Copy Address") - } - Flowee.AddressInfoWidget { - id: addressInfo - anchors.top: nativeLabel.visible ? nativeLabel.bottom : destination.bottom - width: parent.width - addressType: destination.addressType - } - PriceInputWidget { - id: priceInput - width: parent.width - anchors.top: addressInfo.bottom - paymentBackend: paymentDetail - fiatFollowsSats: paymentDetail.fiatFollows - onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats - } - - AccountSelectorWidget { - id: walletNameBackground - anchors.bottom: numericKeyboard.top - anchors.bottomMargin: 10 - stickyAccount: true - onSelectedAccountChanged: payment.account = selectedAccount - - balanceActions: [ sendAllAction ] - } - - NumericKeyboardWidget { - id: numericKeyboard - anchors.bottom: parent.bottom - anchors.bottomMargin: 15 - width: parent.width - enabled: !paymentDetail.maxSelected - dataInput: priceInput - } - - Rectangle { - color: mainWindow.errorRedBg - radius: 15 - width: parent.width - height: warningColumn.height + 20 - anchors.top: nativeLabel.bottom - // BTC address entered warning. - visible: (destination.addressType === Wallet.LegacySH - || destination.addressType === Wallet.LegacyPKH) - && paymentDetail.forceLegacyOk === false; - - Column { - id: warningColumn - x: 10 - y: 10 - width: parent.width - 20 - spacing: 10 - Flowee.Label { - font.bold: true - font.pixelSize: warning.font.pixelSize * 1.2 - text: qsTr("Warning") - color: "white" - anchors.horizontalCenter: parent.horizontalCenter - } - Flowee.Label { - id: warning - width: parent.width - color: "white" - text: qsTr("This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address?") - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - } - Item { - width: parent.width - height: okButton.height - QQC2.Button { - id: okButton - anchors.right: parent.right - text: qsTr("I am certain") - onClicked: paymentDetail.forceLegacyOk = true - } - } - } - } - Flowee.TextPasteDecorator { - buddy: destination - clipboardTypes: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses - y: destination.y + destination.height - height / 3 - } - } + DestinationEditPage { } } /* * A helper page that allows unlocking an account prior to paying from it. diff --git a/modules/build-transaction/build-transactions-data.qrc b/modules/build-transaction/build-transactions-data.qrc index 4606eb5..482d86d 100644 --- a/modules/build-transaction/build-transactions-data.qrc +++ b/modules/build-transaction/build-transactions-data.qrc @@ -1,9 +1,10 @@ - PayToOthers.qml edit.svg edit-light.svg recycle.svg recycle-light.svg + DestinationEditPage.qml + PayToOthers.qml -- 2.54.0 From 2d4eeeeaa7b1089e7811f0783f287f80481905ea Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 18:36:35 +0100 Subject: [PATCH 1173/1428] Add QR scanner option to a destination --- .../build-transaction/DestinationEditPage.qml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/build-transaction/DestinationEditPage.qml b/modules/build-transaction/DestinationEditPage.qml index ed38655..7405df7 100644 --- a/modules/build-transaction/DestinationEditPage.qml +++ b/modules/build-transaction/DestinationEditPage.qml @@ -156,4 +156,26 @@ Page { clipboardTypes: ClipboardHelper.Addresses + ClipboardHelper.LegacyAddresses y: destination.y + destination.height - height / 3 } + + Flowee.ImageButton { + source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + iconSize: 28 + anchors.right: destination.right + anchors.rightMargin: 10 + anchors.top: destination.top + anchors.topMargin: 10 + visible: destination.totalText.length < 5 + onClicked: scanner.start(); + } + Item { + QRScanner { + id: scanner + isPayment: true // well, thats the main intent anyway + onFinished: { + if (scanType === QRScanner.PaymentDetails) { + destination.text = scanResult; + } + } + } + } } -- 2.54.0 From 30ccadb026ffba09a6975d831b49160deb822b27 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 21:20:35 +0100 Subject: [PATCH 1174/1428] Unify the broadcast feedback screen. This avoids duplication in the send-sweep module. --- guis/Flowee/BroadcastFeedback.qml | 219 +++++++++------------- guis/mobile/PayWithQR.qml | 13 +- modules/build-transaction/PayToOthers.qml | 16 +- modules/send-sweep/QMLSweepHandler.cpp | 7 +- modules/send-sweep/SendPage.qml | 138 +------------- 5 files changed, 115 insertions(+), 278 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 9a7f67d..bb77499 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,11 +19,8 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts -// import QtQuick.Shapes -import "../ControlColors.js" as ControlColors import Flowee.org.pay - // screen to show the result of a Payment broadcast. // Please notice you need to have a 'payment' object // in the parents namespace. @@ -32,57 +29,19 @@ import Flowee.org.pay QQC2.Control { id: root anchors.fill: parent + anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. + anchors.rightMargin: -10 signal closeButtonPressed; - property string status: "" function start() { background.y = 0; background.opacity = 1; - ControlColors.applyLightSkin(root); } - - states: [ - State { - name: "notStarted" - when: payment.broadcastStatus === FloweePay.NotStarted - }, - State { - name: "preparing" - when: payment.broadcastStatus === FloweePay.TxOffered - PropertyChanges { target: background; - opacity: 1 - y: 0 - } - StateChangeScript { script: ControlColors.applyLightSkin(root) } - }, - State { - name: "sent1" // sent to only one peer - extend: "preparing" - when: payment.broadcastStatus === FloweePay.TxSent1 - PropertyChanges { target: root; status: qsTr("Sending Payment") } - }, - State { - name: "waiting" // waiting for possible rejection. - when: payment.broadcastStatus === FloweePay.TxWaiting - extend: "preparing" - }, - State { - name: "success" // no reject, great success - when: payment.broadcastStatus === FloweePay.TxBroadcastSuccess - extend: "preparing" - PropertyChanges { target: root; status: qsTr("Payment Sent") } - }, - State { - name: "rejected" // a peer didn't like our tx - when: payment.broadcastStatus === FloweePay.TxRejected - extend: "preparing" - StateChangeScript { script: ControlColors.applyDarkSkin(root) } - PropertyChanges { target: background; color: "#7f0000" } - PropertyChanges { target: root; status: qsTr("Failed") } - PropertyChanges { target: errorLabel; text: qsTr("Transaction rejected by network") } - } - ] + required property double bitcoinAmount + required property int fiatPrice + required property string targetAddress// : payment.targetAddress + required property int status; Rectangle { id: background @@ -90,117 +49,111 @@ QQC2.Control { height: parent.height opacity: 0 visible: opacity > 0 - color: mainWindow.floweeGreen + color: root.status === FloweePay.TxRejected ? "#7f0000" : "#7bb688"; y: height + 2 MouseArea { anchors.fill: parent // eat all mouse events. } + Label { + id: statusLabel + anchors.horizontalCenter: parent.horizontalCenter + font.bold: true + y: 30 + color: "#e8e8e8" + text: { + var s = root.status; + if (s === FloweePay.TxSent1 || s === FloweePay.TxWaiting) + return qsTr("Sending Payment"); + if (s === FloweePay.TxBroadcastSuccess) + return qsTr("Payment Sent"); + if (s === FloweePay.TxRejected) + return qsTr("Failed"); + return ""; + } + } + + ProgressCheckIcon { + id: progressIcon + broadcastStatus: root.status + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: statusLabel.bottom + anchors.topMargin: 20 + opacity: root.status === FloweePay.TxRejected ? 0 : 1 + } Column { - spacing: 10 width: parent.width - Label { - id: errorLabel - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere + anchors.top: progressIcon.bottom + anchors.topMargin: 6 + spacing: 6 + Rectangle { + id: errorTextPane + visible: errorLabel.text !== "" + color: mainWindow.errorRedBg + radius: 10 width: parent.width - } + height: errorLabel.height + 20 - ProgressCheckIcon { - id: progressIcon - broadcastStatus: payment.broadcastStatus - anchors.horizontalCenter: parent.horizontalCenter + Label { + id: errorLabel + wrapMode: Text.Wrap + x: 10 + y: 10 + width: parent.width - 20 + horizontalAlignment: Qt.AlignHCenter + text: root.status === FloweePay.TxRejected ? qsTr("Transaction rejected by network") : "" + } } - Label { - id: fiatAmount + id: fiatLabel anchors.horizontalCenter: parent.horizontalCenter - font.pixelSize: paymentAddressLabel.font.pixelSize * 1.5 - text: Fiat.formattedPrice(payment.paymentAmountFiat) + color: statusLabel.color + font.pixelSize: statusLabel.font.pixelSize * 2.5 + text: { + var cents = root.bitcoinAmount * root.fiatPrice / 1000000; + cents += 0.5; + return Fiat.formattedPrice(cents / 100); + } visible: Fiat.price !== 0 } + BitcoinAmountLabel { id: cryptoAmount anchors.horizontalCenter: parent.horizontalCenter - fontPixelSize: paymentAddressLabel.font.pixelSize * 1.15 - value: payment.paymentAmount + value: root.bitcoinAmount colorize: false showFiat: false + color: statusLabel.color } + Item { width: 10; height: 10 } // spacer - Column { - // column to avoid spacing between these two labels. - width: parent.width - visible: addressLabel.visible - Label { - id: paymentAddressLabel - text: qsTr("Payment has been sent to:") - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - width: parent.width - } - Label { - id: addressLabel - text: { - var answer = ""; - for (let detail of payment.details) { - if (detail.isOutput) { - if (answer !== "") // then there are multiple outputs! - return ""; - answer = detail.niceAddress; - } - } - return answer; - } - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - width: parent.width - font.pixelSize: paymentAddressLabel.font.pixelSize * 0.8 - } + Label { + id: addressLabel + color: statusLabel.color + visible: root.targetAddress !== "" + width: parent.width - 20 + x: 10 + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.Wrap + text: qsTr("The payment has been sent to:", "Followed by the address") } - Item { width: 1; height: 10} // spacer - RowLayout { - id: txIdFeedback - spacing: 30 - visible: root.state === "waiting" || root.state === "success" - anchors.horizontalCenter: parent.horizontalCenter - ImageButton { - source: "qrc:/edit-copy.svg" - onClicked: Pay.copyToClipboard(payment.formattedTargetAddress); - responseText: qsTr("Copied address to clipboard") - } - ImageButton { - source: "qrc:/internet.svg" - onClicked: Pay.openInExplorer(payment.txid); - responseText: qsTr("Opening Website") - } - } - - Item { width: 1; height: 10} // spacer - - TextField { - id: transactionComment - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(400, parent.width - 40); - onEditingFinished: payment.userComment = text - placeholderText: qsTr("Add a personal note") - placeholderTextColor: "#505050" - text: payment.userComment + Label { + color: statusLabel.color + visible: root.targetAddress !== "" + width: parent.width - 20 + x: 10 + horizontalAlignment: Qt.AlignHCenter + text: root.targetAddress + fontSizeMode: Text.HorizontalFit } } - Button { + + BigCloseButton { + id: closeButtonBg + x: 10 anchors.bottom: parent.bottom anchors.bottomMargin: 30 - anchors.right: parent.right - anchors.rightMargin: 20 - text: qsTr("Close") - onClicked: { - payment.reset() - transactionComment.text = "" - background.opacity = 0; - background.y = background.height + 2; - root.closeButtonPressed(); - } + onClicked: root.closeButtonPressed() } Behavior on opacity { NumberAnimation { } } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 81b448e..f86692b 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -333,6 +333,7 @@ Page { enabled: payment.isValid && payment.txPrepared onActivated: { payment.markUserApproved() + root.hideHeader = true; broadcastPage.start(); } visible: payment.account.isDecrypted || !payment.account.needsPinToPay @@ -438,12 +439,10 @@ Page { Flowee.BroadcastFeedback { id: broadcastPage - anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen. - anchors.rightMargin: -10 - onStatusChanged: { - if (status !== "") - root.headerText = status; - } + bitcoinAmount: payment.paymentAmount + fiatPrice: payment.fiatPrice + targetAddress: payment.targetAddress + status: payment.broadcastStatus onCloseButtonPressed: { var mainView = thePile.get(0); diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index aab0e49..d04f427 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -156,6 +156,8 @@ Page { onActivated: { payment.markUserApproved() thePile.pop(); // the broadcast feedback is on the main screen. + thePile.currentItem.hideHeader = true; + broadcastPage.start(); } } } @@ -509,12 +511,12 @@ Page { } } Flowee.BroadcastFeedback { - anchors.leftMargin: -10 // go against the margins that Page gave us to show more fullscreen. - anchors.rightMargin: -10 - onStatusChanged: { - if (status !== "") - root.headerText = status; - } + id: broadcastPage + bitcoinAmount: payment.paymentAmount + fiatPrice: payment.fiatPrice + targetAddress: payment.targetAddress + status: payment.broadcastStatus + onCloseButtonPressed: { var mainView = thePile.get(0); mainView.currentIndex = 0; // go to the 'main' tab. diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 08ca894..d2fc871 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -175,8 +175,6 @@ void QMLSweepHandler::markUserApproved() FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const { - if (!m_txBroadcastStarted) - return FloweePay::NotStarted; #if 0 // This default-disabled code-snippet is fun to allow developing the UX/GUI by stepping through the steps. // Alter the 'i == 4' to another value to make it stop at the step you want to see longer. @@ -198,6 +196,9 @@ FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const case 4: return FloweePay::TxBroadcastSuccess; default: return FloweePay::TxWaiting; } +#else + if (!m_txBroadcastStarted) + return FloweePay::NotStarted; #endif auto infoObject = m_infoObject; if (infoObject.get() == nullptr) diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index cd9ce43..aeb5832 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -158,141 +158,23 @@ Mobile.Page { visible: !sweeper.account.needsPinToOpen onActivated: { root.hideHeader = true; - background.y = 0; - background.opacity = 1; + broadcastFeedback.start(); sweeper.markUserApproved(); } } - QQC2.Control { + Flowee.BroadcastFeedback { id: broadcastFeedback - anchors.leftMargin: -10 // go against the margins Page gave us, to show more fullscreen. - anchors.rightMargin: -10 - anchors.fill: parent - font.pixelSize: root.font.pixelSize * 1.2 - property int status: sweeper.broadcastStatus - property double bitcoinAmount: sweeper.sweepTotal - property int fiatAmount: bitcoinAmount / 100000000 * Fiat.price - property string targetAddress: sweeper.targetAddress + status: sweeper.broadcastStatus + bitcoinAmount: sweeper.sweepTotal + fiatPrice: Fiat.price + targetAddress: sweeper.targetAddress - Rectangle { - id: background - width: parent.width - height: parent.height - opacity: 0 - visible: opacity > 0 - color: broacastFeedback.status === FloweePay.TxRejected ? "#7f0000" : "#7bb688"; - y: height + 2 - MouseArea { - anchors.fill: parent // eat all mouse events. - } - Flowee.Label { - id: statusLabel - anchors.horizontalCenter: parent.horizontalCenter - font.bold: true - y: 30 - color: "#e8e8e8" - text: { - var s = broadcastFeedback.status; - if (s === FloweePay.TxSent1 || s === FloweePay.TxWaiting) - return qsTr("Sending Payment"); - if (s === FloweePay.TxBroadcastSuccess) - return qsTr("Payment Sent"); - if (s === FloweePay.TxRejected) - return qsTr("Failed"); - return ""; - } - } - - Flowee.ProgressCheckIcon { - id: progressIcon - broadcastStatus: broadcastFeedback.status - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: statusLabel.bottom - anchors.topMargin: 20 - opacity: broadcastFeedback.status === FloweePay.TxRejected ? 0 : 1 - } - - Column { - width: parent.width - anchors.top: progressIcon.bottom - anchors.topMargin: 6 - spacing: 6 - Rectangle { - id: errorTextPane - visible: errorLabel.text !== "" - color: mainWindow.errorRedBg - radius: 10 - width: parent.width - height: errorLabel.height + 20 - - Flowee.Label { - id: errorLabel - wrapMode: Text.Wrap - x: 10 - y: 10 - width: parent.width - 20 - horizontalAlignment: Qt.AlignHCenter - text: broadcastFeedback.status === FloweePay.TxRejected - ? qsTr("Transaction rejected by network") : "" - } - } - Flowee.Label { - id: fiatLabel - anchors.horizontalCenter: parent.horizontalCenter - color: statusLabel.color - font.pixelSize: statusLabel.font.pixelSize * 2.5 - text: Fiat.formattedPrice(broadcastFeedback.fiatAmount) - visible: Fiat.price !== 0 - } - - Flowee.BitcoinAmountLabel { - id: cryptoAmount - anchors.horizontalCenter: parent.horizontalCenter - value: broadcastFeedback.bitcoinAmount - colorize: false - showFiat: false - color: statusLabel.color - } - Item { width: 10; height: 10 } // spacer - - Flowee.Label { - id: addressLabel - color: statusLabel.color - visible: broadcastFeedback.targetAddress !== "" - width: parent.width - 20 - x: 10 - horizontalAlignment: Qt.AlignHCenter - wrapMode: Text.Wrap - text: qsTr("The payment has been sent to:", "Followed by the address") - } - Flowee.Label { - color: statusLabel.color - visible: broadcastFeedback.targetAddress !== "" - width: parent.width - 20 - x: 10 - horizontalAlignment: Qt.AlignHCenter - text: broadcastFeedback.targetAddress - fontSizeMode: Text.HorizontalFit - } - } - - Flowee.BigCloseButton { - id: closeButtonBg - x: 10 - anchors.bottom: parent.bottom - anchors.bottomMargin: 10 - onClicked: { - var mainView = thePile.get(0); - mainView.currentIndex = 0; // go to the 'main' tab. - thePile.pop(); - } - } - - Behavior on opacity { NumberAnimation { } } - Behavior on y { NumberAnimation { } } - Behavior on color { ColorAnimation { } } + onCloseButtonPresed: { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'main' tab. + thePile.pop(); } } } -- 2.54.0 From c4cea6ce614d13b59468671ac2cfc91215b9f8fb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 22:33:08 +0100 Subject: [PATCH 1175/1428] Improve the BroadcastFeedback page --- guis/Flowee/AddressLabel.qml | 8 +- guis/Flowee/BigCloseButton.qml | 3 +- guis/Flowee/BroadcastFeedback.qml | 93 ++++++++++++++++++----- guis/mobile/PayWithQR.qml | 4 +- modules/build-transaction/PayToOthers.qml | 4 +- modules/send-sweep/SendPage.qml | 3 +- 6 files changed, 86 insertions(+), 29 deletions(-) diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml index 76d1166..8cb6ae8 100644 --- a/guis/Flowee/AddressLabel.qml +++ b/guis/Flowee/AddressLabel.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +20,10 @@ import QtQuick.Controls as QQC2 QQC2.Control { id: root - required property QtObject txInfo; + required property var txInfo; property alias highlight: highlight.visible property alias showCopyIcon: copyIcon.visible - visible: txInfo !== null width: implicitWidth height: implicitHeight implicitHeight: Math.max(theLabel.implicitHeight, copyIcon.height) @@ -38,11 +37,12 @@ QQC2.Control { radius: height / 3 opacity: 0.2 } - width: parent.width - (copyIcon.visible ? (copyIcon.width + 10) : 0) + width: parent.width - (copyIcon.visible ? (copyIcon.width + 6) : 0) height: parent.height id: theLabel elide: wrapMode === Text.NoWrap ? Text.ElideMiddle : Text.ElideNone + horizontalAlignment: Text.AlignRight property bool allowCloak: true text: { diff --git a/guis/Flowee/BigCloseButton.qml b/guis/Flowee/BigCloseButton.qml index 1491b09..d910e5f 100644 --- a/guis/Flowee/BigCloseButton.qml +++ b/guis/Flowee/BigCloseButton.qml @@ -29,7 +29,8 @@ Rectangle { id: closeButtonLabel text: qsTr("Close") anchors.centerIn: parent - color: "#7bb688"; + color: "#42b670"; + font.bold: true } MouseArea { diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index bb77499..4a849ae 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -21,11 +21,9 @@ import QtQuick.Controls as QQC2 import QtQuick.Layouts import Flowee.org.pay -// screen to show the result of a Payment broadcast. -// Please notice you need to have a 'payment' object -// in the parents namespace. -// Also, this screen won't do anything until you actually -// call broadcast on the payment. +// screen to show the result of a (Payment) broadcast. +// This screen won't do anything until you call start(). +// hint, on mobile you may want to set rootPage.hideHeader = true; QQC2.Control { id: root anchors.fill: parent @@ -42,6 +40,8 @@ QQC2.Control { required property int fiatPrice required property string targetAddress// : payment.targetAddress required property int status; + property string personalNote: ""; + property bool showPersonalNote: true Rectangle { id: background @@ -49,16 +49,39 @@ QQC2.Control { height: parent.height opacity: 0 visible: opacity > 0 - color: root.status === FloweePay.TxRejected ? "#7f0000" : "#7bb688"; + color: root.status === FloweePay.TxRejected ? "#7f0000" : "#42b670"; y: height + 2 MouseArea { anchors.fill: parent // eat all mouse events. } + + Rectangle { + id: header + width: parent.width + height: 50 + color: mainWindow.floweeBlue + QQC2.Control { + clip: true + y: 17 + x: 20 + width: 122 + height: 21 + Image { + source: "qrc:/FloweePay.svg" + // ratio: 449 / 77 + width: 150 + height: 26 + x: -28 + y: -5 + } + } + } + Label { id: statusLabel anchors.horizontalCenter: parent.horizontalCenter font.bold: true - y: 30 + y: 70 color: "#e8e8e8" text: { var s = root.status; @@ -77,14 +100,13 @@ QQC2.Control { broadcastStatus: root.status anchors.horizontalCenter: parent.horizontalCenter anchors.top: statusLabel.bottom - anchors.topMargin: 20 opacity: root.status === FloweePay.TxRejected ? 0 : 1 + scale: 0.8 } Column { width: parent.width anchors.top: progressIcon.bottom - anchors.topMargin: 6 spacing: 6 Rectangle { id: errorTextPane @@ -117,13 +139,25 @@ QQC2.Control { visible: Fiat.price !== 0 } - BitcoinAmountLabel { - id: cryptoAmount + Item { + implicitHeight: cryptoAmount.height + implicitWidth: cryptoAmount.width + 32 anchors.horizontalCenter: parent.horizontalCenter - value: root.bitcoinAmount - colorize: false - showFiat: false - color: statusLabel.color + + Image { + source: "qrc:/bch.svg" + width: 22 + height: 22 + } + + BitcoinAmountLabel { + id: cryptoAmount + anchors.right: parent.right + value: root.bitcoinAmount + colorize: false + showFiat: false + color: statusLabel.color + } } Item { width: 10; height: 10 } // spacer @@ -137,14 +171,31 @@ QQC2.Control { wrapMode: Text.Wrap text: qsTr("The payment has been sent to:", "Followed by the address") } - Label { - color: statusLabel.color - visible: root.targetAddress !== "" + AddressLabel { width: parent.width - 20 x: 10 - horizontalAlignment: Qt.AlignHCenter - text: root.targetAddress - fontSizeMode: Text.HorizontalFit + highlight: false + visible: root.targetAddress !== "" + font.pixelSize: addressLabel.font.pixelSize * 0.8 + txInfo: { + "cloakedAddress": root.targetAddress, + "address": root.targetAddress + } + } + + Item { width: 1; height: 10} // spacer + + TextField { + id: transactionComment + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(400, parent.width - 40); + color: "#26282a" + palette.base: statusLabel.color + onEditingFinished: root.personalNote = totalText + placeholderText: qsTr("Add a personal note") + placeholderTextColor: "#505050" + text: root.personalNote + visible: root.showPersonalNote } } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index f86692b..53676e2 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -441,8 +441,10 @@ Page { id: broadcastPage bitcoinAmount: payment.paymentAmount fiatPrice: payment.fiatPrice - targetAddress: payment.targetAddress + targetAddress: payment.niceAddress status: payment.broadcastStatus + personalNote: payment.userComment + onPersonalNoteChanged: payment.userComment = personalNote onCloseButtonPressed: { var mainView = thePile.get(0); diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index d04f427..0036683 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -514,8 +514,10 @@ Page { id: broadcastPage bitcoinAmount: payment.paymentAmount fiatPrice: payment.fiatPrice - targetAddress: payment.targetAddress + targetAddress: payment.niceAddress status: payment.broadcastStatus + personalNote: payment.userComment + onPersonalNoteChanged: payment.userComment = personalNote onCloseButtonPressed: { var mainView = thePile.get(0); diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index aeb5832..bb640ab 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -170,8 +170,9 @@ Mobile.Page { bitcoinAmount: sweeper.sweepTotal fiatPrice: Fiat.price targetAddress: sweeper.targetAddress + showPersonalNote: false - onCloseButtonPresed: { + onCloseButtonPressed: { var mainView = thePile.get(0); mainView.currentIndex = 0; // go to the 'main' tab. thePile.pop(); -- 2.54.0 From 4ea93c24e14e04152b32c4999850689a84b54afb Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Feb 2025 22:54:05 +0100 Subject: [PATCH 1176/1428] Add the new broadcast screen to desktop too --- guis/Flowee/BigCloseButton.qml | 5 +++-- guis/Flowee/BroadcastFeedback.qml | 15 +++++++++++---- guis/desktop/SendTransactionPane.qml | 16 +++++++++++++++- guis/mobile.qrc | 1 - guis/widgets.qrc | 1 + 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/guis/Flowee/BigCloseButton.qml b/guis/Flowee/BigCloseButton.qml index d910e5f..fdd8079 100644 --- a/guis/Flowee/BigCloseButton.qml +++ b/guis/Flowee/BigCloseButton.qml @@ -21,15 +21,16 @@ Rectangle { id: root color: palette.windowText radius: 10 - width: parent.width - 20 + width: Math.min(parent.width - 20, 320) height: closeButtonLabel.height + 25 + anchors.horizontalCenter: parent.horizontalCenter signal clicked; Label { id: closeButtonLabel text: qsTr("Close") anchors.centerIn: parent - color: "#42b670"; + color: "#60b671"; font.bold: true } diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 4a849ae..af09d36 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -36,12 +36,18 @@ QQC2.Control { background.y = 0; background.opacity = 1; } + function reset() { + background.y = height + 2; + background.opacity = 0; + } + required property double bitcoinAmount required property int fiatPrice required property string targetAddress// : payment.targetAddress required property int status; property string personalNote: ""; property bool showPersonalNote: true + property bool hideHeader: false Rectangle { id: background @@ -49,7 +55,7 @@ QQC2.Control { height: parent.height opacity: 0 visible: opacity > 0 - color: root.status === FloweePay.TxRejected ? "#7f0000" : "#42b670"; + color: root.status === FloweePay.TxRejected ? "#7f0000" : "#60b671"; y: height + 2 MouseArea { anchors.fill: parent // eat all mouse events. @@ -60,6 +66,7 @@ QQC2.Control { width: parent.width height: 50 color: mainWindow.floweeBlue + visible: !root.hideHeader QQC2.Control { clip: true y: 17 @@ -81,7 +88,7 @@ QQC2.Control { id: statusLabel anchors.horizontalCenter: parent.horizontalCenter font.bold: true - y: 70 + y: root.hideHeader ? 20 : 70 color: "#e8e8e8" text: { var s = root.status; @@ -172,8 +179,8 @@ QQC2.Control { text: qsTr("The payment has been sent to:", "Followed by the address") } AddressLabel { - width: parent.width - 20 - x: 10 + width: Math.min(parent.width - 20, 360) + anchors.horizontalCenter: parent.horizontalCenter highlight: false visible: root.targetAddress !== "" font.pixelSize: addressLabel.font.pixelSize * 0.8 diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index fe4c80a..d70498d 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -285,7 +285,21 @@ Item { Flowee.BroadcastFeedback { id: broadcastFeedback - anchors.fill: parent + anchors.leftMargin: 0 + anchors.rightMargin: 0 + hideHeader: true + + bitcoinAmount: payment.paymentAmount + fiatPrice: payment.fiatPrice + targetAddress: payment.niceAddress + status: payment.broadcastStatus + personalNote: payment.userComment + onPersonalNoteChanged: payment.userComment = personalNote + + onCloseButtonPressed: { + payment.reset(); + reset(); + } } // ============= Payment components =============== diff --git a/guis/mobile.qrc b/guis/mobile.qrc index e52ef53..086337e 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -2,7 +2,6 @@ images/FloweePay-light.svg images/FloweePay.svg - images/bch.svg mobile/images/back-arrow.svg mobile/images/maslenica.svg mobile/images/moon.svg diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 933d4aa..105f4ee 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -1,5 +1,6 @@ + images/bch.svg images/eye-closed-light.png images/eye-closed.png images/eye-open-light.png -- 2.54.0 From 95ca9d02ce925f4828ba5a09ceaf3632cecab377 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Feb 2025 16:24:39 +0100 Subject: [PATCH 1177/1428] Add a small keyboard to unlock The default now is a "small" one at the bottom that users will be able to use with their thumbs. A common input way. For fat-fingered people we keep the current full screen input widget, easy to toggle by the button at the top which now is a 3-state and on top of that gets rememebered between instantiations. --- guis/mobile/NumericKeyboardWidget.qml | 66 +++++--- guis/mobile/UnlockWidget.qml | 217 ++++++++++++++++++-------- src/FloweePay.cpp | 17 ++ src/FloweePay.h | 14 ++ 4 files changed, 225 insertions(+), 89 deletions(-) diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 3a066a2..45895ac 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,6 +46,39 @@ Item { return Math.min(height, realPixelSize * 4) } + /** + * The background for a single button. + * Notice that we expect a property bool pressed and + * you will have access to the attached property 'index'. + */ + property Component buttonBackground: Item { + id: bg + property int index: 0 + property bool pressed: false + Rectangle { + width: Math.min(parent.width, parent.height) - 6 + height: width + anchors.centerIn: parent + radius: bg.pressed ? 20 : height + + color: { + if (bg.pressed || index === 11 || index === 9) + return palette.midlight + return palette.mid + } + opacity: enabled ? 1 : 0 + Behavior on opacity { NumberAnimation { } } + Behavior on color { ColorAnimation { } } + Behavior on radius { NumberAnimation { duration: 100 } } + + } + Timer { + interval: 200 + running: bg.pressed + onTriggered: bg.pressed = false + } + } + function flashErrorFeedback() { errorFeedback.opacity = 0.7 } @@ -93,30 +126,13 @@ Item { delegate: Item { width: root.width / 3 height: root.contentHeight / 4 - Rectangle { - id: backgroundCircle - width: Math.min(parent.width, parent.height) - 6 - height: width - anchors.centerIn: parent - radius: pressed ? 20 : height - - property bool pressed: false - color: { - if (pressed || index === 11 || index === 9) - return palette.midlight - return palette.mid - } - opacity: enabled ? 1 : 0 - Behavior on opacity { NumberAnimation { } } - Behavior on color { ColorAnimation { } } - Behavior on radius { NumberAnimation { duration: 100 } } - - Timer { - interval: 200 - running: parent.pressed - onTriggered: parent.pressed = false - } + Loader { + id: background + sourceComponent: root.buttonBackground + anchors.fill: parent + onLoaded: item.index = index } + QQC2.Label { id: textLabel anchors.centerIn: parent @@ -180,7 +196,7 @@ Item { dataInput.shake(); } else { - backgroundCircle.pressed = true; + background.item.pressed = true; } } onClicked: doSomething(); diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index dc76960..7f5e7b1 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,14 +39,52 @@ Item { shaker.start(); moveFocusTimer.start(); keyboard.flashErrorFeedback(); + smallKeyboard.flashErrorFeedback(); } /// clears the password typed, if needed. function acceptedPassword() { pwdField.text = ""; + lockIcon.open = true; } function takeFocus() { moveFocusTimer.start(); + lockIcon.open = false; + } + + Item { + id: passwordData + property QtObject editor: Item { + property string enteredString; + property int insertedIndex: -1 + function insertNumber(character) { + if (enteredString.length >= 10) + return false; + insertedIndex = enteredString.length; + enteredString = enteredString + character; + return true; + } + function addSeparator() { return false; } + function backspacePressed() { + if (enteredString == "") + return false; + insertedIndex = -1; + enteredString = enteredString.substring(0, enteredString.length - 1); + return true; + } + function reset() { + insertedIndex = -1; + enteredString = ""; + } + } + function finished() { + var pwd = editor.enteredString; + if (pwd !== "") { + root.password = pwd; + editor.reset(); + root.passwordEntered(); + } + } } // we can't move focus from things like the onClicked handler of a button @@ -55,40 +93,73 @@ Item { id: moveFocusTimer interval: 50 onTriggered: { - if (!switchButton.numericInput) { + if (Pay.unlockingKeyboard === FloweePay.FullKeyboard) { pwdField.selectAll(); pwdField.forceActiveFocus(); } } } - Image { + Item { id: lockIcon - source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property bool open: false + anchors.horizontalCenter: parent.horizontalCenter + y: parent.height > 700 ? 40 : 10 width: 60 height: 60 - anchors.horizontalCenter: parent.horizontalCenter - smooth: true - y: parent.height > 700 ? 40 : 10 + Item { + clip: true + width: 60 + height: 21 + rotation: lockIcon.open ? 20 : 0 + transformOrigin: Item.Bottom + Image { + id: lockIconOne + source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + width: 60 + height: 60 + } + } + Item { + clip: true + y: 21 + width: 60 + height: 40 + rotation: lockIcon.open ? -10 : 0 + transformOrigin: Item.TopRight + Image { + source: lockIconOne.source + y: -22 + width: 60 + height: 60 + } + } } Image { - id: switchButton - property bool numericInput: true - - width: numericInput ? 40 : 30 + width: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 30 : 40 height: width anchors.right: parent.right - source: "qrc:/" + (numericInput ? "keyboard" : "num-keyboard") - + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + source: { + var s = "qrc:/"; + if (Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard) + s += "keyboard" // next one + else + s += "num-keyboard" + return s + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + } MouseArea { anchors.fill: parent onClicked: { - keyboard.dataInput.editor.reset(); - var newValue = !switchButton.numericInput; - switchButton.numericInput = newValue; - if (newValue === false) + passwordData.editor.reset(); + var cur = Pay.unlockingKeyboard; + if (cur === FloweePay.BigNumbersKeyboard) + var newValue = FloweePay.FullKeyboard; + else + newValue = cur + 1; + Pay.unlockingKeyboard = newValue; + if (newValue === FloweePay.FullKeyoard) pwdField.forceActiveFocus(); } } @@ -112,12 +183,17 @@ Item { anchors.topMargin: root.height > 700 ? 20 : 6 spacing: 10 anchors.horizontalCenter: parent.horizontalCenter - visible: switchButton.numericInput + visible: Pay.unlockingKeyboard !== FloweePay.FullKeyboard + Flowee.Label { // for height + text: " "; + font.pixelSize: introText.font.pixelSize * 2 + visible: repeater.model == 0 // 2 equals only!! + } Repeater { id: repeater // take the number typed and turn it into an array of characters. model: { - var inputString = keyboard.dataInput.editor.enteredString + var inputString = passwordData.editor.enteredString var answer = []; for (let i = 0; i < inputString.length; ++i) { answer.push(inputString.substr(i, 1)); @@ -127,7 +203,7 @@ Item { Flowee.Label { text: { - if (index !== keyboard.dataInput.editor.insertedIndex) + if (index !== passwordData.editor.insertedIndex) return "∙" return modelData; } @@ -135,70 +211,50 @@ Item { Timer { interval: 1000 - running: index === keyboard.dataInput.editor.insertedIndex + running: index === passwordData.editor.insertedIndex onTriggered: text = "∙" } } } } + Rectangle { + anchors.bottom: pinPreview.top + width: parent.width / 10 * 8 + color: mainWindow.floweeGreen + height: 2 + x: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? parent.width + 20 : parent.width / 10 + } + Rectangle { + anchors.top: pinPreview.bottom + width: parent.width / 10 * 8 + color: mainWindow.floweeGreen + x: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? parent.width + 20 : parent.width / 10 + height: 2 + } NumericKeyboardWidget { id: keyboard - x: switchButton.numericInput ? 0 : 0 - parent.width + x: Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard ? 0 : 0 - parent.width - 20 hasSeparator: false width: parent.width anchors.top: pinPreview.top anchors.topMargin: introText.height * 2 // work around the fact that it takes less space when empty anchors.bottom: openButton.top anchors.bottomMargin: 10 - - onFinished: { - var pwd = keyboard.dataInput.editor.enteredString; - if (pwd !== "") { - root.password = pwd; - keyboard.dataInput.editor.reset(); - root.passwordEntered(); - } - } - - dataInput: Item { - property QtObject editor: Item { - property string enteredString; - property int insertedIndex: -1 - function insertNumber(character) { - if (enteredString.length >= 10) - return false; - insertedIndex = enteredString.length; - enteredString = enteredString + character; - return true; - } - function addSeparator() { return false; } - function backspacePressed() { - if (enteredString == "") - return false; - insertedIndex = -1; - enteredString = enteredString.substring(0, enteredString.length - 1); - return true; - } - function reset() { - insertedIndex = -1; - enteredString = ""; - } - } - function shake() { shaker.start(); } - } + dataInput: passwordData + onFinished: passwordData.finished(); Behavior on x { NumberAnimation { } } } Flowee.TextField { id: pwdField - x: switchButton.numericInput ? parent.width + 30 : 0 - visible: !switchButton.numericInput + x: !visible ? parent.width + 30 : 0 + visible: Pay.unlockingKeyboard === FloweePay.FullKeyboard anchors.top: introText.bottom anchors.topMargin: 20 width: parent.width - focus: switchButton.numericInput === false + focus: visible inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveDataTyped | (hideText ? Qt.ImhHiddenTextCharacters : Qt.ImhNone) echoMode: hideText ? TextInput.Password : TextInput.Normal @@ -231,10 +287,10 @@ Item { Flowee.Button { id: openButton - visible: !switchButton.numericInput + visible: Pay.unlockingKeyboard === FloweePay.FullKeyboard anchors.right: parent.right y: { - if (switchButton.numericInput) + if (!visible) return parent.height - height return pwdField.y + pwdField.height + 20; } @@ -245,9 +301,42 @@ Item { var pwd = pwdField.text; if (pwd !== "") { root.password = pwd; - keyboard.dataInput.editor.reset(); + passwordData.editor.reset(); root.passwordEntered(); } } } + + Rectangle { + width: parent.width + 20 + x: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? -10 : 0 - width - 20 + anchors.bottom: parent.bottom + anchors.bottomMargin: -10 + color: palette.base + height: 225 + } + + NumericKeyboardWidget { + id: smallKeyboard + + x: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 0 : 0 - parent.width - 20 + hasSeparator: false + width: parent.width + height: 200 + anchors.bottom: parent.bottom + dataInput: passwordData + onFinished: passwordData.finished(); + Behavior on x { NumberAnimation { } } + + buttonBackground: Item { + property int index: 0 + property bool pressed: false + Rectangle { + anchors.fill: parent + visible: parent.index === 11 + color: palette.midlight + opacity: 0.6 + } + } + } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 76288e2..81df496 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -61,6 +61,7 @@ constexpr const char *CREATE_START_WALLET = "create-start-wallet"; constexpr const char *WINDOW_WIDTH = "window/width"; constexpr const char *WINDOW_HEIGHT = "window/height"; constexpr const char *FONTSCALING = "window/font-scaling"; +constexpr const char *KEYBOARD4UNLOCK = "window/unlocking-keyboard"; constexpr const char *DARKSKIN = "darkSkin"; constexpr const char *DARKSKIN_FROM_PLATFORM = "darkSkin-from-platform"; constexpr const char *ACTIVITYSHOWBCH = "activity-show-bch"; @@ -234,6 +235,7 @@ FloweePay::FloweePay() m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); m_privateMode = appConfig.value(PRIVATE_MODE, false).toBool(); m_prices.reset(new PriceDataProvider(appConfig.value(CURRENCY_COUNTRY).toString())); + m_unlockingKeyboard = static_cast(appConfig.value(KEYBOARD4UNLOCK, m_unlockingKeyboard).toInt()); // Update expected chain-height every 5 minutes QTimer *timer = new QTimer(this); @@ -955,6 +957,21 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +FloweePay::UnlockingKeyboard FloweePay::unlockingKeyboard() const +{ + return m_unlockingKeyboard; +} + +void FloweePay::setUnlockingKeyboard(UnlockingKeyboard newUnlockingKeyboard) +{ + if (m_unlockingKeyboard == newUnlockingKeyboard) + return; + m_unlockingKeyboard = newUnlockingKeyboard; + QSettings appConfig; + appConfig.setValue(KEYBOARD4UNLOCK, m_unlockingKeyboard); + emit unlockingKeyboardChanged(); +} + QNetworkAccessManager* FloweePay::network() { assert(QThread::currentThread() == thread()); diff --git a/src/FloweePay.h b/src/FloweePay.h index 0951abc..1cbf2cf 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -71,6 +71,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(int unitAllowedDecimals READ unitAllowedDecimals NOTIFY unitChanged) Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) + Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) // notifications Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) @@ -87,6 +88,13 @@ public: }; Q_ENUM(UnitOfBitcoin) + enum UnlockingKeyboard { + FullKeyboard, + SmallNumbersKeyboard, + BigNumbersKeyboard + }; + Q_ENUM(UnlockingKeyboard) + /** * The protection the user has selected for his Flowee Pay. */ @@ -408,6 +416,9 @@ public: // return the app-wide network access manager. QNetworkAccessManager* network(); + UnlockingKeyboard unlockingKeyboard() const; + void setUnlockingKeyboard(UnlockingKeyboard newUnlockingKeyboard); + signals: void loadComplete(); /// \internal @@ -433,6 +444,8 @@ signals: void blockHeightCertaintyChanged(); void internal_heightCertaintyChanged(); // not thread-safe + void unlockingKeyboardChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -475,6 +488,7 @@ private: int m_windowWidth = 500; int m_windowHeight = 500; int m_fontScaling = 100; + UnlockingKeyboard m_unlockingKeyboard = SmallNumbersKeyboard; bool m_loadingCompleted = false; // 'init()' completed bool m_loadCompletEmitted = false; // ensure we only emit this once. bool m_appUnlocked = false; -- 2.54.0 From fbcfd0b4ab350e1c96bbcdbdd67ba2d9e9f708de Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Feb 2025 20:46:07 +0100 Subject: [PATCH 1178/1428] Remove unneeded tags --- guis/mobile/images/back-arrow.svg | 47 +++---------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/guis/mobile/images/back-arrow.svg b/guis/mobile/images/back-arrow.svg index 8b6f5d4..f02daed 100644 --- a/guis/mobile/images/back-arrow.svg +++ b/guis/mobile/images/back-arrow.svg @@ -1,45 +1,6 @@ - - - - - - + + + + -- 2.54.0 From b82bf5c753060499dea4bf60585617d89c55cbe3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 5 Feb 2025 20:52:38 +0100 Subject: [PATCH 1179/1428] Add quick recieve on lock screen. This moves the creation of the portfolio to happen the moment we finished loading. (wallets were loaded either way) The networking is the part that now waits for the user to unlock before it does anything. --- CMakeLists.txt | 1 - guis/desktop/main.qml | 2 +- guis/mobile.qrc | 1 + guis/mobile/UnlockApplication.qml | 85 +++++++++++++++++++++++++++++ guis/mobile/UnlockWidget.qml | 86 +++++++++++++++++------------- guis/mobile/images/background.jpg | Bin 0 -> 19889 bytes guis/mobile/main.qml | 2 +- src/FloweePay.cpp | 12 ++--- src/FloweePay.h | 2 +- src/main.cpp | 16 ++++-- src/main_utils.cpp | 4 -- src/main_utils_android.cpp | 3 -- 12 files changed, 157 insertions(+), 57 deletions(-) create mode 100644 guis/mobile/images/background.jpg diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b92903..ba839dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,7 +277,6 @@ if (ANDROID AND build_mobile_pay) qt6_add_executable(pay_mobile ${SOURCES_PAY_MOBILE}) qt_import_plugins(pay_mobile EXCLUDE_BY_TYPE - imageformats qmltooling networkinformation INCLUDE diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index b284151..07b18d0 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -36,7 +36,7 @@ ApplicationWindow { onHeightChanged: Pay.windowHeight = height onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow) - property bool isLoading: typeof portfolio === "undefined"; + property bool isLoading: typeof net === "undefined"; Component.onCompleted: updateFontSize(mainWindow); function updateFontSize(window) { diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 086337e..0fdfb8c 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -2,6 +2,7 @@ images/FloweePay-light.svg images/FloweePay.svg + mobile/images/background.jpg mobile/images/back-arrow.svg mobile/images/maslenica.svg mobile/images/moon.svg diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index d2486a5..a813e8a 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -16,6 +16,9 @@ * along with this program. If not, see . */ import QtQuick +import QtQuick.Controls as QQC2 +import "../Flowee" as Flowee +import Flowee.org.pay; FocusScope { Rectangle { @@ -26,14 +29,96 @@ FocusScope { anchors.fill: parent } + Image { + source: "qrc:/background.jpg" + width: parent.width + height: { + if (Pay.unlockingKeyboard === FloweePay.FullKeyboard) + return parent.height; + if (Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard) + return parent.height - 225; + return 0; + } + } + UnlockWidget { anchors.fill: parent anchors.margins: 10 onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); + assumeDarkBackground: true // the above background means the widget should prefer white text } Keys.onPressed: (event)=> { if (event.key !== Qt.Key_Back) { // exit app on 'back'. event.accepted = true; // at all other key events. } } + + Column { + id: quickReceive + x: 60 + y: { + var uk = Pay.unlockingKeyboard; + if (uk === FloweePay.BigNumbersKeyboard) + return parent.height + 10; + if (uk === FloweePay.FullKeyboard) + return 240; + if (uk === FloweePay.SmallNumbersKeyboard) + return parent.height - 240 - height + } + spacing: 6 + Image { + source: "qrc:/back-arrow" + rotation: 270 + width: 30 + height: 30 + } + Flowee.Label { + color: "white" + text: qsTr("Quick Receive") + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + horizontalAlignment: Qt.AlignHCenter + x: 15 - width / 2 + width: 90 + } + } + MouseArea { + anchors.fill: quickReceive + onClicked: myLittleStack.push(receive) + } + + FocusScope { + anchors.fill: parent + Rectangle { + anchors.fill: parent + color: palette.window + visible: myLittleStack.depth > 0 + + QQC2.StackView { + id: myLittleStack + anchors.fill: parent + onCurrentItemChanged: if (currentItem != null) currentItem.takeFocus(); + } + } + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { + if (myLittleStack.depth <= 1) + myLittleStack.clear(); + else + myLittleStack.pop(); + event.accepted = true; + } + } + } + + Component { + id: receive + Page { + function takeFocus() { receiveTab.forceActiveFocus(); } + backHandler: function handler() { myLittleStack.clear() } + ReceiveTab { + id: receiveTab + anchors.fill: parent + } + } + } } diff --git a/guis/mobile/UnlockWidget.qml b/guis/mobile/UnlockWidget.qml index 7f5e7b1..7c7ca38 100644 --- a/guis/mobile/UnlockWidget.qml +++ b/guis/mobile/UnlockWidget.qml @@ -28,6 +28,7 @@ Item { /// This will hold the password when the user is done typing it. property string password : ""; property alias buttonText: openButton.text + property bool assumeDarkBackground: false /// Emitted when the user submits the password signal passwordEntered; @@ -85,6 +86,7 @@ Item { root.passwordEntered(); } } + function shake() { } } // we can't move focus from things like the onClicked handler of a button @@ -115,7 +117,7 @@ Item { transformOrigin: Item.Bottom Image { id: lockIconOne - source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + source: "qrc:/lock" + (Pay.useDarkSkin || assumeDarkBackground ? "-light.svg" : ".svg"); width: 60 height: 60 } @@ -146,7 +148,7 @@ Item { s += "keyboard" // next one else s += "num-keyboard" - return s + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + return s + (Pay.useDarkSkin || assumeDarkBackground ? "-light.svg" : ".svg"); } MouseArea { @@ -171,8 +173,9 @@ Item { anchors.top: lockIcon.bottom anchors.topMargin: 20 horizontalAlignment: Text.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere width: parent.width + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + color: assumeDarkBackground ? "#fcfcfc" : palette.text Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes } @@ -181,7 +184,7 @@ Item { id: pinPreview anchors.top: introText.bottom anchors.topMargin: root.height > 700 ? 20 : 6 - spacing: 10 + spacing: 6 anchors.horizontalCenter: parent.horizontalCenter visible: Pay.unlockingKeyboard !== FloweePay.FullKeyboard Flowee.Label { // for height @@ -201,18 +204,25 @@ Item { return answer; } - Flowee.Label { - text: { - if (index !== passwordData.editor.insertedIndex) - return "∙" - return modelData; - } - font.pixelSize: introText.font.pixelSize * 2 + Rectangle { + width: 40 + height: parent.height + color: !Pay.useDarkSkin && assumeDarkBackground ? palette.base : "#00000000" + Flowee.Label { + id: dot + anchors.centerIn: parent + text: { + if (index !== passwordData.editor.insertedIndex) + return "∙" + return modelData; + } + font.pixelSize: introText.font.pixelSize * 2 - Timer { - interval: 1000 - running: index === passwordData.editor.insertedIndex - onTriggered: text = "∙" + Timer { + interval: 1000 + running: index === passwordData.editor.insertedIndex + onTriggered: dot.text = "∙" + } } } } @@ -222,35 +232,39 @@ Item { width: parent.width / 10 * 8 color: mainWindow.floweeGreen height: 2 - x: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? parent.width + 20 : parent.width / 10 + opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 0 : 1 + x: parent.width / 10 + Behavior on opacity { NumberAnimation {} } } Rectangle { anchors.top: pinPreview.bottom width: parent.width / 10 * 8 color: mainWindow.floweeGreen - x: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? parent.width + 20 : parent.width / 10 + x: parent.width / 10 height: 2 + opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 0 : 1 + Behavior on opacity { NumberAnimation {} } } NumericKeyboardWidget { id: keyboard - - x: Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard ? 0 : 0 - parent.width - 20 + opacity: Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard ? 1 : 0 + enabled: opacity > 0.1 hasSeparator: false width: parent.width anchors.top: pinPreview.top - anchors.topMargin: introText.height * 2 // work around the fact that it takes less space when empty - anchors.bottom: openButton.top - anchors.bottomMargin: 10 + anchors.topMargin: introText.height * 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: 40 dataInput: passwordData onFinished: passwordData.finished(); - Behavior on x { NumberAnimation { } } + Behavior on opacity { NumberAnimation {} } } Flowee.TextField { id: pwdField - x: !visible ? parent.width + 30 : 0 - visible: Pay.unlockingKeyboard === FloweePay.FullKeyboard + opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 1 : 0 + enabled: opacity > 0.1 anchors.top: introText.bottom anchors.topMargin: 20 width: parent.width @@ -282,19 +296,15 @@ Item { onClicked: pwdField.hideText = !pwdField.hideText } } - Behavior on x { NumberAnimation { } } + Behavior on opacity { NumberAnimation {} } } Flowee.Button { id: openButton - visible: Pay.unlockingKeyboard === FloweePay.FullKeyboard + opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 1 : 0 + enabled: opacity > 0.1 anchors.right: parent.right - y: { - if (!visible) - return parent.height - height - return pwdField.y + pwdField.height + 20; - } - + y: pwdField.y + pwdField.height + 20 text: qsTr("Open", "open wallet with PIN") onClicked: { @@ -305,28 +315,31 @@ Item { root.passwordEntered(); } } + Behavior on opacity { NumberAnimation {} } } Rectangle { width: parent.width + 20 - x: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? -10 : 0 - width - 20 + x: -10 + opacity: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 1 : 0 anchors.bottom: parent.bottom anchors.bottomMargin: -10 color: palette.base height: 225 + Behavior on opacity { NumberAnimation {} } } NumericKeyboardWidget { id: smallKeyboard - x: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 0 : 0 - parent.width - 20 + opacity: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 1 : 0 + enabled: opacity > 0.1 hasSeparator: false width: parent.width height: 200 anchors.bottom: parent.bottom dataInput: passwordData onFinished: passwordData.finished(); - Behavior on x { NumberAnimation { } } buttonBackground: Item { property int index: 0 @@ -338,5 +351,6 @@ Item { opacity: 0.6 } } + Behavior on opacity { NumberAnimation {} } } } diff --git a/guis/mobile/images/background.jpg b/guis/mobile/images/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1132cd60083da2dad3fb40cda794a4325a7fbafc GIT binary patch literal 19889 zcmex=$JtpjZw+&q1Jee^6tqJq65UA%ofL548$^70A^2q_B- zD|;Gh8hVlp{-0tHH17o2WxAYHb5 z>NG_usan;l3WG4OXOFj@5MtWntsCwCp!{)qLiLZ!ozMO=2&Bo(KeX0s=F~;G2d7_q z^}>8>XUZ4zh)+pVqgz>ypWPXFppWaNs2NR(2mLcbB>nrZ;6tQ zoW=uYCzzJ(-)(QVp?F$g%OBf&`VMm?<(6dnIQa`X3A`=w{(H-?A#k?kvz*3=)rqo%DsvP*8kwh)VV)4i`B?bFe|#T5Bs zW?PGR_HDm;55%U&6#4$!{Z*kf^T6wsBC7dwnpbcww!gXf*!yK0fA;Tnu)Of`sJxV? zgJ4Bcn%l9!Tg54hKE}PiENRKS<@w32{~6e#^A2n*chys#eBtk#t&++19yKTXZJYkq zZ&*I*&ex1DOXOH5Jo2&T5>4W}9lY<}{`Z-gQ@3)eZ=DeDI;Y|D=j65(zE8Q5pKJ6p zdS6)VzH!>ItPY{7EZMFSgLCl*9=wx0+N~;R`r2k%g{e!-Q`WN7oqjbDuB#9Kb71AD z*u39x^D?ndW^UHrDld8`ZrXYDZ67Ia zPr)%WiRc^s}s<gb+31TiPn*wSjQU+WXUinsH%AOof(8B-zgyM7 z@AN>fL}H8kq|1-(a84W!8s-H?Aw1#Cds^hM?`8Jw+jU6T9k< z%xKCEKKJbtyNqPTd-vMNyaOgDopg-Cq9*O&7kFFIm!GpIOYK_bi-SB*pJi>|l(S%E zRrQMt?;4tdK#+pCX{Pg zWN@oad0t$dsIhAMoDD425;+=%B_EH}%HNscax6--C7Ai?!_>1M{0e{ESS~P;y?o{C zRc_yH_|n7ITJwKg@trIF&9@6aNi*j0r8*wuIZ)EJc++0zU{O~V-*=x|5|^(3l@z4; zlP`AhWBC`%3%kt^SjHJF7E1pl%zW0N-s(>IZ_5KRPxox9P5EGz(R$I1+wbAzZX+S{ zi`-sb-b^}yW$VQLu>5A*H7DLY_Mt`QGo$+x`M4WT9JyB2J1uaM;`g}*SHk{S=(*@< zr@ZBz7xu3`^~HY%2bFUfH}1Z-RQU9!X21B|&b4(7f=aE4y^jOV)PyEa`_O3PnLhwy~}$Su@pxduv~qS9h4(DsvTUI}P52I*IBY=Z@M3K1^KKy7;-~aUX|l zOUqX*?PnRT&S0Bcd(zfr%SV3`0r^82|1bS#NR>RazG63vUvur1M+PD04~>Mn_b2!A zzqj7_`{@=DCDU8Ho?pak+0VDehpnF<#A>xs=kZ$CL@h@V)2fDzGtPbaP$RJC?2H4q z0xp`)T(iD3@;yV`9uHy5{qe~?O;&P#i+}V!V7p#byTQ+5k|V?UkXw5X)N53T+&%BP zX@9dR-!^**=?k?JDxW;DFn=Pqze3?g(Du8|8{LF#l%%e4=z3jDT>D$X?fCC&ihnFZ z`K9wzq&QzB70y$zUgxJ_lXZMWuN%u$T{*oE8FQZRbgmcPH@nN;SfNP0`ac72>Tkw5 z7tPXSUWRkVn6*6KD9y3Pz~K8mho5fUOJA&LWJ<_(`*p`!*)-$C_k;@b6uJKljH@Gz zXC}VgTe0z2nRZ3-zI`9hY0u!hHoH0_itWjxS!cZetzB4^l`Y=)`c}PQ`JDR^|1bY% zNX^Z+)Mr z>dlOT--l;*{+6G}|DU0MlK#{E4!-6*8>Pa@FPcdzb-#xqkzBK+U5Tl zBDU{(WT&2H%KD{x$=pwmRTy5!1oppq`B-6h#@Cyt_WWm$yfJ?x*t+v8VOA>?&fTKko2kc9J%jo0i;+%-ZSA27|`6Q6eB zr*xO(l0{iP;)&AlbsP^Y{?E{()*$^QUd!{#68oiFIwpQvTsSweAy0BvZBXW|;%6^q zw#h8`>uY-UY5~W4t%udu{&~K6XH@QCeztuF;|8`=m(ZUH%H_QCw4Ek+%`@<>+%5ll zwlC-I6jil{f34nceRj~z)+oeHTUD0j<;)FPAB^2p7Oae^Q{d>_cuImV)bH61&3igY z8#5=Kc2M%{U4GK!!}*q-a#rhpd{sVa7$R$ZAwzbT-hty$lg{eeA72yypJ7H^sOOBp z6(&v}G@FAgcdU}ptW0JrI=J%Rb)SyS?;>ONB)nV2<63%k{f6S3+jy`0)o7o8IwAD( zO2O|B{$KgeV6pD6X!d1WrF?M>_xB6Sr(6;|F!w)$;J1t0eddaEKA#$R>!<8vr$GHz zUyW=x&7KzW-RIb&J4~+10&a?j4f#4#zF(b@t+p&g_PoL?xitwHU45xR({Fx>&6&`) z;_l}j|GE<~FXBXP8E-$icJt1bof~h<&idha>%F{OoYs|TPqM{3@2y^XImGgi-R(vJ^l&Pn~~##)tRthZI#aNh>5{QTrFDwBn%5k6T!;&Yn&WgEg;6G&D zG<(jjhcmwPS?*qywsqk_hotUM8I!BBw&g}_g^%i2c*iAO>Cf92z^a=qCiDEfZ24t} zc>y;+Sc*?QIcKhi?wgq&YC&GMB@REI2EH*keo0LC;)6N0lEv*^yZGf>9Z~Ons9djHP@6FQ_l2*scTBkXMA>VV=7E?8i;dRwrqyl;{UDyV z>&A|Y&0P!LoBq80pP^&JwzE=d*E!OB>`e)qNgKwX?o~X==`l9^$==8?M{6ZcNz3XD!QtIi&KVlQqt~KAw zy#JrUy+<-sPKoQ)Op&t93y(Zbdm#Gz`+BxlaX0QO&9)MC-O{tKGPONVe}(mxn+q$p zbf|r+V_hfjqIl`g>Q86y2V8u3SMauJ`cAv@)BCpWzqZUqpPMaYn~|ueR_FHYsccD2 z|1VtZpTsb!;=rP~_s<*tGOPTPTC_&)ZlK|!jLZ24Y`^SX8GrA#{O|NnA%ZwIm!W%R1g5d+4z3 z3Yp)o3of18{7kpKJ7=R$)>L(D>s11Dl6EtY54b zR`$uro%xsY_Dc1J&iw2A>!PCK4m5QLcCzQy&rk0^uwbyIC4e4H6-RB+tp5%?Uh~ZldD1>8eEm( zQD;-@UzKqEoI(@#`NgL;c*uR5?#=c^%KvhnK_7R+=Una$hp%OS4Syd!>D@Ci^JlAC z_`Ft?J|D8!Z9nCsn2T$jmSF`>oY6V-i@7%L! z`m=l;jz9<9GKL2(y~h%M)~l?Z!NT+`R5O07Q@t#-%vKxXEwRHyUjPfX&oe!_k( z&i2ZVM_cZTh2MCcs9^n&-}N=$lcjT60-kP)>}iRPwC^=|x<*)T(^PBiXPH^Mik1Ig zkw5kO%C27C@?SH30+KV5^fG;uHt1zGCJU|2stjOeKl1lm#!JV5fYsI8ixvr8GMT|w zIxYT)m1A`5d}ix-bp@BJAr6j$!l#_wP6uBzs%5L?RNVeJ;l_65hVR)kmE;c{*s1k- zs%~(nr>Q49UrosJ-%tN_zmd(paO$he9#JKA`7LiBCCqtp=xp@68wLhnCNT!?J;Rr4 zo2_Z@;;b&bVnu))U)_)LH8m&BWna;A;=Rt6`THYF`uw?*&hu-&S!(s{sH2?q%VhWF zU%9FO8Ky3)wAS|fZYH&)rsT@1*9Eg@reE-$!8&Q4=%FJe(`y``Y*=<_L)oE@c~jHd zC+}m|w|HKhAUBEILrlt|HriY}ZtIp<*==t%LAhQKlIxin8SNQY1qv@HlmF=+zSYE2 zA!(KGdLD*aw-UB`DTB_ktY!NGtuCsyx?JPAA5(mC=JmAK>gin7D=)?>H6QqYVcGwS z%l=U%*;^i{1)7B5i&`Ds;=l z?Gc}MzH%42dO+4=+MhiS=05-%w^G<|J@=iv>!LOm{J*m7+Nl@^km2y6ip`#BMIB#> zbn>p*$GO*ezkRu=arwr)nj3S2lQzfyYxsX*xom#J|BEXxX=Kd}$m}&MWk~&H6KGki3J&uj;A%5Ya z#)e9MufJ69;mFs$AH!{#^~fva#E!(LiQ-|2o6`zizir@npsn5hUi78mnjLaMM;|<0 z?PB;K;mf@*+j}z+nBKLGH!hyhn3`8nk`>Du-lDnwzH;XWx6Lhlw$s zclm(;$)Db(-@ho97w*3v%J6$jz=aok{7k2vSpDexTYG1Z_j&x{n#@mQ=Txo^OM7u! zzvs7}X;KvLOZTO7ulRkonf-L_+y^!Kp$_kA#PgXM%RSDR9uu$ZXJuA zo@M-%Yo}No5os$h^WA7yu%P+s!xvkctc`uN&i-7mW1>&dv^mozy`1%EU82=2$vzOPRpSj;EY?&TD zz50MTCGD+`=&bsJsVkOyYHhgY^dMp)|JNn_m9An>6Wu@Toouy!#;Q{GQw=V>tNnjX zwn^J=d!>}bEitz zS$9s0y*;F>RiyMpdvl5CPPrp>KVsywid!7q0_u$foC^KEU69yxZ^2W3zsl*F=Eo94 z4$u0#+_(NR9}p?Rk+8T(^EZ@nDg(?@vbm3G54f z4#nwQ=w*G6A6Q)0Ewi7ye zZ>2*qU*5qVPX(jT?K}VR-iv>y99!Z|e=<9+h}iKUbJ~S8o+BCeI};8$+gDcR@a?IK z3iW5#6ycg^79P3dWI{3XbSv%4)k5~mx$l{VFR|ahSWfBIt6j^xI`U0+?~zE#XVVdy zQd?1a&FN<4i{&@le(m4+>E;r}MaR5mTyl0=lM^PhAyfWk?&*)0O>SC-@c9;||G2$U zY}R$2E{X4ESEGvbj^Eqj6x)$n+h(H|NuFQN?=EjEdU#+> z-Goy|Z-&jDar$IWlG%PhGv| z-lX9`17gNmZByouF6Bd+QApCPkB@`qkKXr{a&cXVv9Q}QOYQc2$y*Pzn~U~8y6?;6Safi2{rv*B zD2*u6DYv^Z#-~)vjX0%L`?nEi2_-a>sY! zw@)YDD4l!Zd**R!#0sWOFaC5d_h2k_+H7%S{^3P-;&1*l2*w+K(>#*?pP@=WtMkoK zKVxg1jLtW4(bs(Al5Xt$wf?mXkGZD#5$V5E6qGisGxc%`*}lTsWy`!*$|17tk7MJ4 z44%wyndF&$$m!;^e&c4&Nut8jlP0;!rbpa*lOSLfmGGWZjGysQ%hDZJWjJOEeE)s@ zxb_Y6^PjiG9%Pl8y;Wl6JF%9_+dCr9ojEaIMEO$Y&bm3B8w~g?zCO29kuhkc*!YN2c{0@HMSBNVdLxYsOx7;ZtWqRgQE&3Ku%N zs9&ww_tf`3)1wNvU4QIf`7w1}MTO;)U$?rF-n#CRs9v;O&)`w6*|#9OWA1Vn?@ZmR z?ziY~hHmNF{6s$Ygtxm_aqQZ%?ES%+*E4S|*!_H?KCc4v;_#qSmU&qzO-t<~&L;WC z_iX&i{#AT(S8edGUIW{L)U2e$wXF(qeq2|!WdtUJ&6v$!K@y!6^?s47YPE+v0LqY|(_BcE|rL zX_yog$uzrYYMW+?TVun;XWuKo-KqT+b=mOPRO`#~8q;I8L|-pIYnGp(&RxCGLYsAu zmtjnp@sX2Fx?&GrmEXT!{%B9b!Bg8~rcbNOX+7{lS>xs@eUUSFujfQ-I`7{p?rj0I{-9p;6yh)PcQF5thN?}F>!V0XvtqdFbA>UEw!T+1DvJ`FDHyYcvsxBQd8X^^iE$YV{$B|WZJpSg zP!NCI%DBtxpkuS}`Bj$l*i@c!8?WsVdS;>XUXlC3-DO+3*RDyf{{FNi=27Hh_ojz? zm%mQkBxFw^s>AML}?rrB> zI%xvq8ZUdcpILG}KVqYwNoM-CZqG6h?iKp5n{9bx%w5k$g_yO`&X?~ko#r32-C^~E zL%W~8xM}rfq4}*D;-Z$_FFXG3+SBV%QZIknUQf}eXiC4xX>r3j2Ug8^va;|}RO*dm zYj+pg9-YnogJC*bVXEtW$vc0p_HpQOUTBo@Q{y=ym>HG(JD0mM>+S&=|Lyb7sIItL z-ozhJ{9^5EJHu}?{xg_9^sLVfc=sS%Lsp1O;oGiHJpAb^O|vGveBycUcVq*9OzDJv zOTjX(JDpZu^$cCB+qnB3;`YWFM}P=veVtI>U=M%bd^l z?x_b6oiRGR&&6N8ygRErYI0zWN3nu+`O+n2s$E%USH4xOSBYf5>`*u1rP$Qd8Rw_> zORoQM(al;q@}u-*kH+bJ&!_3l)>6=Tva;&z`=guoIJg%!zL!7Q?c>3?{M_2|gNKad z<+n2Zxwk%B`A&_^j#*Bf-Qv$Ssq-F*%hh~4HQSy?#_q&M$6G58IdSB?=;I7#{3cXm zzg4$7_kl&T(;dgX6-g&f*L;{(>nzZ7+ev(*-}f1TN$+FX9<46yn)Yx(y4d3vUu!3e zzJ8Y8z5iE%;iKl@GZr$nIi1NeeEZxZx=dq38y{xutN$gEw@LouF@BG*j4*M>qG@Zw zeD|()&0cs_e zuXt{cozl7VDPLrQ3u4y&tkqR{r6XDW@4$cPR3!s50|SG=RPK34uFQ_{w2!~CZgcbD zHH|vwE2h;PIj7I4l$Wu7$FfrWE4#$`Ej1Q&)$vCaRb5%~Fg4|}Q_ty#JIv&dl?dm= z7hZ}JD9(;`Hu8L-So$OA%*$POm+h-mGg~|Ho}`{{#`{|<4v8hGHlJ_Jp7=iGNyie8 z#5HG%pB03cKWX^nd*@5;xg(bfSOhtnV=rzGdOz>1*y={fmDR#Ky_gTDy=8N9@mK1# zvJdKW`1gzN+LSG7x5du(@$hB8IP$BxJv??&*VZ>?b5eRGWmlYx$}xT2dq6(#dWV%( z*%ihhh388{p5G}emrv68^VTu?H1|E;{afcfaJ~Pe`0QzWR?QdM!j?i2^EK`BygH|B zf8?-_FNLx3&}-MCaL*8KmIrMtwXP0V<9{u^aB1Oh?Sqrq?S+as*!HykNeVrDDR<%n zmaiN~rwI2Rn$F$0f5Ca}*t@YiOmzk2KXKhm`XVkbr6-&*x8eVVTidjfQ@%-MF7Np> zdE#yRD$n^9X=z5blGnBb>aF=urL^k2_rbZRq7T~46z0#Gap%SE{|r`#v^Op+4%jRD zUP^x*|BMqAkG(3+?oK*6i-pyxFn`^XHb(V{d`XI19lxy(`y9JxfsBv957(JC@0T!2 zCuBZaaPaB1ue0||Uf%vK@QU$=`P!* zcYA#=R6pWeTBqc{Lc%b9hV)*qKirGIg_T#&5I<7hF4~m0Q2I;E#F({aJMPln}6sV<{MlUl_Wx*iPVN?X=CX<3xy&(8lBU+vlb z>);iymwYc{_|n5yRoRzl%$g&_e#2zNwofzH3i<+mjmn4me0uB$8-FT8p9{%>DNq)7+)-3yQeOHUe328z7YylK9b3O z_6BpLOm=QqvcJXX;Gr`$KgDi-$(K!9dFr%cmhF{A!XMV`Tdn?R?`^MZo6djxI9+^F z=W*8%i93(4vb*W$*F@h6*nFg5adC@-5YNQykGpj>0@&?-H#D@jWPV8f_@PSkfq30{ zX&c?8*Vd~RbO+Af&XOO6~(v^Y5p@pj{h0N7d>WPuNyNVm}fzd#muFu4@0)u zOj{(&%=4D86~42*`S|L5y(x~B zT6-K5il!-L@EIhWn}2P0-n0l--OCrVW*D>2(F+PYue>ZYI(KFrN7}8I`~KM7Vww=A_jg;KKsXDT&E!P$WPkylLP|=+nk2e)J&4Lzv`)TlSuFA)% z@V;QS?FH9dRWh6B9BcaFowTX>6qC}!f~E6VJ2$v#Ex3~QH`{T6Z_zXAqLbW9t*mG6f<=Xd_@77tM-u|RIBYlzL+5-y0HE%yXMrrcz@Jk-V}C+>i8Q!yFz~U zG#q()WRlFDYg0d1Ut0WB@9fu*>vul>$ax;&|NNtH;c_w7Q%VP>cJ46PC^N;#@O{h2 zeLE{z*q%RUx4Ip4SZ#XtXy`e)+;lgwg^Wmv%L6~ z`#7}YWSfKcwR2h5u6h}i{Mm5Yd(jF`rf+}x3q5M&!smXw+4dwgiqXTZW-`xhoriBX zD=M8c*#EQW=A>sA_RX1N@q21Th0gV$^BOHoDmht`a#n6$ZJK$PZxz=H{^VlMXFKHc z|E$eguG;t7nu%wd>Gmd;tudL;T<<+wHXAJOd#Y;d4$~Ip?1J&!GH5dEf1*wNu-qnEx|`?7LSG;k5bR z_x7JU0s{A=8Jt+$H>7cXlshlEFHPc$w)KAoDf!2NM-s|=UugF9t-f$@(U;}}(_Zh% zekd+`-6*=Dg^yj}So2~X<1<_(0}4o8{Wws&9T!KTx}HqPXN6 zFZ)k!p1b{o=7!lFZd`ui%&YvIe;3-{^Az{2v3Fc#Yp2bqEV2E4zff9Me(%XQdo0|3 zy*AA~5~sM?Z+f#Bs}*y%@E_N?@6F?n^7UO*@JTVZ$*U>te6-oD%=w^p*KU|@B?21fLmK%MxSG83GZj1xEj(*C*U| zY+L#8Y|1+AJD!IR8p^K~{%&?jo@vDt)#EeRG+w^lV`x+QYFYZRR=2A!MeqOIC3~mu z#)t36@(o$nE$oiZtvInU=|S(x%Zamh|5AJMLW5y%Xus3TZz5F-e)-0&Ijbb{;2z7O zMJ~F9QtPLxFsgP$@?h}n}W=38$i!eWv4S?SNC6*0#H>b&A28W((C zv5#AR^TWc^u53I?+vQKNI=)pg{(o`Pog*_2Z2bAx_3F=HdzMh+SaT+oo9sTbn!p32 z%TH+Ue|z-8>OCK=Ui~T+k6JkC!v8B9=PEStu?ZX6aOzkU?+pL2+DOJT?}7Tq0;I46 zjrIy`j@uC|u=i_Wr>k>lr1h%238GSR({3CQ3^{OWb-*&6oX>OEe}%2*UGMp9=Us*0 z&P9!@7N%r$NQd2denB~U_D(s^Q?tzqB#sGpzBoC9DL?-3jJv1r{b%65lHg;w%YL_` z+@aa)zGtTR=w9OZ9{%{(y8|f|cJqYgwHQPc`=ZV*o)Ko<;;bKh{!Xpe{OI5=&ai}t z{};}jmt=ZYnzCV~{*L^okF>K`;!9_&{m-y-YoOpEc^21di>V93($y9`GY!en@LBht z!BSW0=Zu)hx);aQ4`u)1d*$ELa)Rk{#VOU#D`XcY9W1`0FWP?e%M*!X<{ucJrF-s^ z%KD+2zN5C||HTd&L*uvl`)_;3O*p$|_3yt2tkaj&R)xG*pJ2WEig(drb?@qh`?K!) zoa%75xP7wr(IVyBx+|ipALz{6?~ok5ChGj_X}c$>iBIAvZd}cG;rbHQ*uAG;Hn9sc z35r}1+$F@Kx?SdmG0OwdV-5c=t=-z&vCY#(F1%3bLrrAN*0rU@&RPo05(oP(ezd(l zMb6>>xLax2U0v;pY89G*H=7rT*&I>i}I_J zrqArSrEpfI>-NdGByQfVA9So(to~oQoG9;^^LJTIW^cr1&%$um{JZ}C%gjE+F1@@s zk?*wK+&Q8ax=}JysxIl~$B6H~UmaPOqqHR0O1tmu#KNhsg>M~no_o64b%Xe)$(N5D;{HB=@llbsn;8@9BT^oTpD|hBv29k~ z=>&IGRISvVcxK*;Y5y5qTbRzDE1J@_A;49EdY6J4!o zx^eLvrAn`dsTfIC(06TBYcPAG-Y+E%S_b+paVceG>mhO8V3i zTPC|Adt<8$`xCy2U)bd>dpxx6{QAybolW8kd7sy=S9IE+W4CDQr z?~Nle?mKKM>sLvvlH+ZDkZ@3$nk|AEN0Q>QOIEWWB4XXU|>Js~6O<*W@SrW$*{ zNj}Th^H}HN+Yd`+8k zXob^BzutXq@Ai9~IHq$ftmd|dtL3y$E8=BPn~t$m+i4h zEc*7My3VOznSiJ7$StFdeGqKVm`%@sG5&hZ)twufmBSWQzlrmb zo4=NQ_BEvp zH}0;tKD5d)w16}Fk*Iatx$0Td4R2?4JwK~jn)7P1e_kNc6fb9c@xVIqZFAcz(Cx5B12FDct%C~2bJbTI5g$YL3W=dV1H;veh3eYC)io84V} z_ASwcZ$B%&ni6y9O2J*(9sEX;Upf9W)OdLY<}Ofos*Hs{dp) z9)+yG8L4kvtG@Pgfc?1>-OHVlzJ6-SG46lXH%Z2@<7&X^2jZ`zX0E-XP~4yLz4lO) zM0ugfH?CFQ9~&0f)VyN)(sF<^H9y6(Py*XF2On_s-*FJ0xX6}M!0!~Y9^mt1h1 zD&0D#>Bn*D1u}pBGi-ME{?BlXwJ=BE(Sdq*`|peP$T__D-nn1k(T*0j=*t{)pUPUM z?)42wusi$xl7ZzEF7~{8-;4AuMbvZoT{VQ~KCM3D*0@#d)yrZ1tGNEZk#*y=zKfTPqA{+T%H)q>)RPU zJWGtl+&=|vVSHo9{4^Og<%~^|5A+Vcx^wMg z_m!AE+EynMuS{-zy82m0!d2(1clLF?vx(VzM)^_fH1>(yCj|ES7&pG%C0|!{ifL)s zn_qYLSKhekQq!nmtbEq4(V%C8agg'qYhZ+Ar(8O{3s!gHaB?We}XpGq@5bi18y zEAz}-bkQ;DwB6Azkp~jR&d)bz#J`iV723q1E!-C$JySj5PME-%3MutfZjk z=EJ$;p{eJKe358A*FW2R|LyqQxoqj82@W|GCw;ADWnWy^tCsa>Z&|i^UIL4UmgCPR z8KonW)6N|DTVv|;;(?{Y6se5;9Ge-m(|gr~968M8;@jOyDl68M`{^j1_DqsBn#Xtf zhFOlutpdf)-Rvi)aYgd(zx+z@jqjD732%0GHt%T-Teh4rC|YNAbzjZW+s^y#)^6qK z5t_9tcyp{(=Y^`(lE0O9U#{Z_n`(Buc)O*b)T=frtt^S)6{|mp*PXks8o6q=8tt#0&vim&`G8G-+>G}#&Jof!w%jTA36)F>R z{iu4^1G^+$^{$il{WgyB=Vmrlc6?LXXtLJGEc}Hjug%U%laO7e{1>GDt!bN`y_KO- zYvJmpb*H!HJ}r5kcBr7JBl@$#4pL2WDy^mU7{xkGTPOUI| z9d*>k)S=el_>b70ukU)qnzT3h%=ox#hg--8?`*pppDN4OI5z!fuoZ91GXADicVg=q z$?aE{Rf~IZ9u$b#nwI~AD@;j&!KpO*eg}KiRE4u%U+>pHY+>Q}eLFuYpgxmn?Wav% z9zWGiiRBi|TrbSzuMcs_cd)l_@BWbF3N4PHKHt2?%d{-gznI*CM9sdi(p?y$@&mzi(S7UBl&V`M7tc z>do5?Vn;c*ut~*hr9Sz&zhlGuLbHH?jn-f8wLB7<&!elQRxWX< zjGMT5^-WdP{>dJb-Hj&C?fVMRmE~_M7b;MfT6W;W6=jxLmXIj%c^PHTA8< zFJskG=iS@qsa!NO`l>~&Cr|sd{VK*uB^4|?ReoS0qwB~w?Y_D2kYOOkFw5Y(Z(12OzSlb!OH%_~K za8vKs%z7HBB;8XN5|ytJw>jH!l}OT~ZOmMYojyNL{#;sps5$T+@0Cr` zleUSytvb&q($p2c=AcB$r|>YaD^v)_5MW(W)_Ou?QmwXytCY1{+$78 zK~wCfJ>3?l%9ph;P2$vlhOCW~o;?n7l6~A^B7VQQ`IF$9ziI{B*&KRHlXKk7Z?0+I z_^qZc=)^v)sxB2JPG*Vl1m7D%+y|z}3F|zs`ej|J9dn(IAllf*yw+Hb5mnzQZWQ}*6#`q1Zc`QbsHT^D`q3%JEMC0%~G zZ31guOaJ>Xj8lJh{b!hbDPm1q> zlr(+I-YfcxoykU5pP#)d;8N<%FYy7p+t2K}y+c?^a;u8yE3@c@eJ-9Mc{lTpvpCOi z7qzdu+``Uw!#AqR|MPP$pGiHwn^I;cdfRTl@bKXbQ+B;Ya{`U`6vT5EfB1Ws%hzI# z)U?f^p>_LD)bbsad0^VQyzKb-?fbmk?}Ui(hzN^3OAlMPs%lAv-=|*Lsss}K+cjx_jxwG)&buHn#jEig!bd0hRR)#*# zG?M#%K-qQa+iN`Y7aTl)h5LE>O%FH5%*CDO8)i&0Ty@>Jai(u?)S)e#bdU7a+|=sc zcZ%;;S>koGZ&5d=q&+>hSY7l>Q2+5Yrye^V{-UTJ7<8?{@6?Tpo^4w;Ul-V=#VO@1 zdFU;Ji&f=*QUbiz(W<`U)Dyi9+cd%*K#XX+Ie^9e+HYX+3hbvq`396UzJDa23)9o?Re<1 zb**UGe+J((Ch0PTqJ=Fti{F&9)aOgpKEGNyjYsAVuhW#$1o55A!)M&f|C?xV;^ezk zyazb#OU8c{UHO?`X{G;un&;?oP`4Anj3etaEPQ%(HG&6fY~aWj;9~;T3=EyO90ai~kuU zIr%4Qram$a6`I59zJtqVQ^#R}t8QsO751fRLS=N(`?{PUQ@=>YkN3bi;ArOrOe&=t12d6v$D-5@Rh@y&BA{5D+*Q> zZ{8Jh?nh;>HT&C{HNg^}d>>v9V!rV+xJuT&mUoZ6V*ZAw6Iu2@RafPWo*G>-`Jir6 zWsek_!v9OfN32y@K5Pv#)4m_GDNQ8hmLLajpy>7howSpT@e4j(=M8LTU65?I=e%Qe zc(v;3>I(Cwx+0*}>=O0h#ujHry}%ct5d~TQK#B71OkB%&%@-kGa-3X*<(2 ziNY1Heh9a0mM+g?%t`J)m%_a>w+839pk4 zDle%m{P3%DtA&EbzV#eOcI@LhIDPf`s!v&~6E16Qs)*~0+jrPcYt9vs=ErTq8fs>@ z`kH@ToO$(;`9{YI9nFHLJM>THzuu89zs0hz-K6pO6Mq@cDc#&B_GES5Gk9gRW98Mg zzg5=UpMCDT)aMIt9-e8hUANMDb?%14k`9KmH2ExQ8t+t3JMRB&(W~vxw^-@i_Y}IL z!RL9j;`P?3?L8qH39k+tE!xTQocr6$)xTyN)V5x|r%eu$rq;^T}aeE7+cu(GRs4F)BTD)Pb`1Wse7*S`u(N2 z?b{DN>M!24lQ;Fn`m6HIt0TVG%d|hAlp;SRsN9h6X84cdOa8GE+}_i~bEnM^Dkvy? zcr(3ag0o=fkIpY8b3!6Zl*6Xa0jlX~=}V*aL;zgM{bSaaRritnPoX9TOYW~@5- zO%UE8{uD-_h&{}<(6=QVB ziKOKxj@nOL=?(Y}q+Ba_$HWI?3zu?zq|BxQ`EyI7)Rq?PT(*+4Anw zEQSn|nwO7=Gtc2=Jx6munTdwB!S%`THUr%$nZ~8ML zG?VqVkmJd^ruFlu$<6LPwqwBsizVkD9!vk{H)FrY@fSZ>RiF8G`7RRpLb~7X}frCXJTj6+?IatzHo)++kw-*x~(;=3Gt8?mn^St zeChFqbz)YPm7(Rl;`LUBMjrn*A3X3PI-w`z&}Y{qOZ%GK;-#l|m1M2^^Po_1-t5aO z4=PMhnIcfG%X#f@#un+aTl2KGlIzs51EcO@nm)Tl5xoe9co_nIJ>{i(*l7FUgFnVAH1UZtH-BP1!A@WVNUBWRepRF@v2KmUXah* z??rFa{KZOV8|rRKZrS)zZc6F<>lZdlcIB%&SzKAx{dn#TJy&}>q1kr@i&PEXMIM@a z`b@;F{|xW@(r1KS<#mmgD3qK|w>EE7b zOYL5N#p!#%#QAfBroDPSZ~o>$fmk)6hX0puUJ~$`ePRCXJ+_suKU;3f^qTekWnEvh zIQoc)x57Rib>;{mu~XYGo7Twxs(twIq1|1*tIYYYZ5{|H-JO3j`H-B*j^uCNntB=fd+OjrR;j@i;v{tIG>mD}O z2D4vbUKir#Z4!Ao;feB{!y$?yUYzYWH7(4p_0+mldmzNr$m!^03X3O$POzKn4 znzt<9_&Ftran6|L;{H6dQ-&DVGq z9B+?1%68$0)Rn8@{OiqY_9;!aKQiv>TO!Ch~cv_#nS z!{$HDUz{v<{qk6%XY=A`>4}|D{}~MI*;i@*>p#Z$=G7U)H}?x4Kb&6tw(V?b;~kYp z+VX5KYX7|{EbqDSV|&iCj2O||*%ufU7G-_0VYpeSl=GjV?eo8?BV1cVQZ2wLkJ;Voz`^QTtfXw z3~}xxM@=OHz_>#dmIGRZdH*v6?EF~F=OHW$T;n9D57Wcr zn;gF`unkgW7Q3?U-UQi0lJgJRSBGX#-}B1ZZ|D0I#;1jIoxeXRa$-HOL&JHc%rakF z8SQ7UFE@1WzGeJ<$1%U%Z>GL~urTfMTl?ubn+gp4vh$b@ELLz$cI+*?>iz1dTgV*s zqwWq)CR<#$uan-e`p?7T;va3bIe)2lok=#{mjC|d(*n!rXX}_Z#fRMOxS%j~-wx?B z*)rEP`X-#dwYO%b!vv$f0-`0>Cz4ov4QKXjsa}^U?)mQ8mvxP+W;btpc<@O?TGfu! z8K(T#?UqF+=5e{(HvLSxeq~a<*X_txyO;2?U69G$;kd4dduCw5t>zEUf}J0ox3tMP zH9t5cWb&LNmvmF#Pqj$3Q!m>qy6N;UnG-YT>h?FTxFPJCZoKHy@Ba*&ZY@)rwI^0D zV`tyiYl}_hSm$!h+#C2=OgH}H3b*wMK6e5NKlvHPT&-MEzl42#mh7GX43}R$T;)?E zxhJi0u14kF*{6@EOImh*RIC@f=DE1js4F&SvHGF?6L+n@t;T-zsqm9TWl;%w&>!)bseqt8v?Jzb7iHfM*Mj*m#0DMd~eI_1%dC4ll`Qp-ufJ# zKL5v@sh6`H-JIs%+4h>LaHUx9)e(!G>vQ#<`CZ+K{LAMRum_)i zpv>pxIMJt1q^-Ro@Odz+u>UUWyboPO-tPIm`r(OH zYi5Yw)iM))!1O$;YGz3MkBAqgk1rQKw~nZboX(c1_1UK)e~s(AT_@Tdj=ZV;S#4^^ zY_;ac><#}I{P#M}Qr~prQQr6YhV%LQ#g9Gy{Cs)$ZK09|p6>njrJUC*qZ_9uHoREO ze@@!g`@5xzfrZ#f>-`EpI3`q^t;lt7@p`no-~R2yTesE!uHM<8?0G?{(bf8kvWdi( zRL!F=U)FypJuKs*=U21;gnY$Z=qr?^GO|0A}7ky^IVr}k6iOmML z!(v_6D$YAK^IN`w?(0=tCPMd*x5aq)@d+*Ry?2yF2a_x-ZT1zVHT9d^!AdO5Rr zLAdoN_W~YnVVysglfPE2*)^^0(@{ygx1YC^2bOfr?#o=4`uxa;_Z?fln{Y-X26*}m0>mLv++t>2OT`p-6j4wb80l*IX?H=el_Q~y@WF0$v(MRn$sABwwj z{lsRAZv6e&=82Z=41ptDiA(BaGBr=K{jTorS>blAqQ*^?KUz4G%KMqrIIWb zUypEIPS|mT#~}THjcdrQ`et2~3BCf7XL{Xp`3(0Pe3IH=c=2jOU0#it_=7s9`OhOP z+3XeSdpDcE3tq9sCd6Tb)Z^LnK2^r85%Th{{1AU7;GZ|Q(_#VLDGieXwVPu)KmOD5 zcvt9UzGDaPzjV)($KTiePU1Oz`N-l3bFK6z{&p)5Om4}x|6ca;M+V!fqQj9=6Yc7M zc1(}G*z!u|i@ohuDRI~J;^B*vg6Eob_111K%XqC?KGne{sQTiA|5p@UqBOUKl<4u? zGv4yAY5nF5?zvxDs^tzpw4D`@zWdU$bN5--2h_&>{P}+7o8OnDb6JlTo4?zANPAIf z$<*L&ayL%A4?Y^&*ZH48rr94LmoiMQ@^`4LLldeyb@?|&p#Qpnc-}kD8?abQia@(p{3^Yvk zJ6(`A54Qg6?_kpw5iWHTF`{%eQ3%#FX<)>&bcI_6^ zQN!HaxeSY0FI{=g647Tp`(}3mQ|uY*^ziz<{~6fg%=C?Bmqc#*v(2*E>(0ShjEfCl z?=^5P+qP}f$C-^qlRs|L$PRhX@V@2f>?+@6HIODCA@+^pW^9kg(n zTm!GQn9=f(PcIwGFI&KN0jxG_?{TsR~tdnJ*)CPC9BA*GN6BV{+ z6z{ITEPj01`DMj(ddv>3b8kNpeM|2Bj}(g;ryUX}t~&eljpl__n@zUcG_%$EOnC7+ zH0*KFmVl-e?S*p$7YASS)XX~a_}biSO6Odb7M-?Y;1GJmcIA0-`m&DAnMbevoqH-U z%ivaF&FNq3IfUBzZ{}Q=4g4`bDe3*$eu-C#?C!^3X)|ALvfmdyZ`pqau4ccJ{TrlX zFa5dfp~LozM@O|~|E?RZoBPzy{p+54gRLo7M}+Ot1g^WQ)gphZ%PCxaXExzP|F&Iw zzF7W!o!%AhtP}QC{$$gm+0lP(`@B{j>st3k?#*ef?-O!30y9Is+RRL{WpwNqT6+S1 z9;ll$b=$rppZLnBiS0EkFRGAN+Q;AH*8Kj3f|JU$&C2($f0}qt&S>{jZ+_pi&h1Z% z_~%5JwVR3RMf{s^$~fS`!Ur)HX`8psNROK8Sn~6-Zh65W7q_W3rOW%DUdu2qWnMQ~ zWAPbPk;4-XU3L;=^>LcLXzr1x7cPAKFx~i(^j`)}zaXJSjs7h~oOAi49&-xs`rN;% z`P6ld_+$2;oMo?QGPGePhYEB!O$ws9XjVlqYaU&NlHkx8bNA(g!gegEhOhOX4> z=h+;h^J?4ms$jdB+uvN|@^?NQ$yvVVX!-J2ES(S^1%Q@y^$;w_b8i zDULg&sp2_RbaDHvP2pLVC-&}(&neXs@btXZoa?%ER^>Xj^4l>BPrYD|dvbw0YVHy% z+r6isdgM)ds(M$pH>}|RM^rD<$0HufOM>R#o2IhQ@S^?H%bt_7FHO6BBaP)rY(;Qg zLE0Qu-X9aV_sBb(dHZp#a5%D4^vmwn&~Cr}^YI&sR;o|d@AALg_Q4}2oK14`8xHTB-9K|=Lzx~Xn)Ptj zGfKAISgXkvyS;dS?WO9eoDCJyy9Gjd(x*HwUmUH={MzYiX5in*)nP?@^8C-tc@bGA z&ARgSos>%wt2Y>Rzt>LRqsf1KYShFxRlgRS-WPt^d+h7QpnnbtPO~Q1d^~s3BJ#D; zjphFiANJiMwExqjsH1I}42`g}gevO;#huFAHXuOtmbT~cKAcJc{buwBwECd)Cu@^tdk zZ8n0UQ&lddGC$g}(&&WckNEY=)}|g+cB{U1KsK=8-52J2U+p~C-P@mhu$$$jq~XP@ zEEnHgnm5%rrGw!Ie`mMh{zTn1jk_jwG9=Buf3#zjv3Qtse(kPr0?9%O_g7!start(); } m_loadingCompleted = true; - if (m_appProtection != AppPassword) { // in that case, wait for password. - assert(!m_loadCompletEmitted); - m_loadCompletEmitted = true; - emit loadComplete(); - } + emit loadComplete(); } void FloweePay::saveData() @@ -1242,8 +1238,10 @@ bool FloweePay::checkAppPassword(const QString &password) const bool ok = (hash == m_appProtectionHash); if (ok) { setAppProtection(AppUnlocked); - if (m_loadingCompleted && !m_loadCompletEmitted) { - m_loadCompletEmitted = true; + // notice that the app auto-locks after a timeout. + // so we could be here multiple times in one lifetime. + if (m_loadingCompleted && !m_loadCompleteEmitted) { + m_loadCompleteEmitted = true; emit loadComplete(); } } diff --git a/src/FloweePay.h b/src/FloweePay.h index 1cbf2cf..994c9a3 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -490,7 +490,7 @@ private: int m_fontScaling = 100; UnlockingKeyboard m_unlockingKeyboard = SmallNumbersKeyboard; bool m_loadingCompleted = false; // 'init()' completed - bool m_loadCompletEmitted = false; // ensure we only emit this once. + bool m_loadCompleteEmitted = false; // avoid emitting this on every unlock bool m_appUnlocked = false; bool m_darkSkin = true; bool m_skinFollowsPlatform = false; diff --git a/src/main.cpp b/src/main.cpp index 7978999..fceeec5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,9 +19,10 @@ #include "FloweePay.h" #include "NewWalletConfig.h" #include "NewIndicatorProvider.h" -#include "PriceDataProvider.h" #include "Payment.h" +#include "PriceDataProvider.h" #include "TransactionInfo.h" +#include "PortfolioDataProvider.h" #include "PaymentRequest.h" #include "PaymentBackend.h" #include "QRCreator.h" @@ -165,12 +166,21 @@ int main(int argc, char *argv[]) #endif "/main.qml"); + PortfolioDataProvider *portfolio = nullptr; + auto blockheaders = handleStaticChain(cld); - QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld]() { - loadCompleteHandler(engine, cld); + QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld, &portfolio]() { + if (portfolio == nullptr) { + // the portfolio can only be properly constructed when the init completed + portfolio = new PortfolioDataProvider(&engine); + engine.rootContext()->setContextProperty("portfolio", portfolio); + } + if (FloweePay::instance()->appProtection() != FloweePay::AppPassword) + loadCompleteHandler(engine, cld); }); FloweePay::instance()->startP2PInit(); + // We ship our own font to not have to depend on the host system's installed fonts // for 'special' characters like arrows and stars. int fontId = QFontDatabase::addApplicationFont(":/Flowee-Symbols.otf"); diff --git a/src/main_utils.cpp b/src/main_utils.cpp index 6b60582..d1e59ec 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -15,11 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "ModuleManager.h" #include "FloweePay.h" #include "NetDataProvider.h" #include "UserIntent.h" -#include "PortfolioDataProvider.h" #include #include @@ -196,9 +194,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c if (!isOffline) netData->startTimer(); - PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("net", netData); - engine.rootContext()->setContextProperty("portfolio", portfolio); if (!isOffline && cld->parser.isSet(cld->connect)) { const int port = cld->parser.isSet(cld->testnet4) ? 28333 : 8333; // add it to the DB, making sure there is at least one. diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 4470886..defa1db 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "FloweePay.h" -#include "PortfolioDataProvider.h" #include "NetDataProvider.h" #include "ModuleManager.h" #include "UserIntent.h" @@ -144,9 +143,7 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c app->p2pNet()->addP2PNetListener(netData); netData->startTimer(); - PortfolioDataProvider *portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("net", netData); - engine.rootContext()->setContextProperty("portfolio", portfolio); if (g_userIntent) { auto myActivity = QJniObject(QNativeInterface::QAndroidApplication::context()); -- 2.54.0 From b89570f0b5e87cdc7d0ca4664f27ce28ff19094d Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Feb 2025 12:21:19 +0100 Subject: [PATCH 1180/1428] The Ubuntu long term support ships too old Qt. When Ubuntu released its 2024.04 release late April, they included Qt 6.4 which was by then already old and out of support. For some reason they did not include the Qt LTS 6.5 which at that point was already out for a year. So, people are stuck with a really old Qt that has been out of support for a very long time already on ubuntu and derivatives like mint. (re) add a backwards compatible define to make stuff compile on this old version of Qt. --- src/CameraController.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index 83bf913..a2d5da3 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -32,7 +32,9 @@ #include #include #include +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) #include +#endif enum AskingState { NotAsked, @@ -512,6 +514,7 @@ void CameraController::startRequest(QRScanner *request) } if (d->state == NotAsked) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) switch (QCoreApplication::instance()->checkPermission(QCameraPermission {})) { case Qt::PermissionStatus::Granted: d->state = Authorized; break; case Qt::PermissionStatus::Denied: d->state = Denied; break; @@ -532,6 +535,9 @@ void CameraController::startRequest(QRScanner *request) }); break; } +#else + d->state = Authorized; +#endif } // give the overlay screen time to appear before // activating the camera. -- 2.54.0 From 9e145539a69787b56a702f265851c41eb477dbcc Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Feb 2025 00:18:44 +0100 Subject: [PATCH 1181/1428] Fixes --- guis/mobile/UnlockApplication.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index a813e8a..72bb7f7 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -45,7 +45,8 @@ FocusScope { anchors.fill: parent anchors.margins: 10 onPasswordEntered: if (!Pay.checkAppPassword(password)) passwordIncorrect(); - assumeDarkBackground: true // the above background means the widget should prefer white text + // the above background means the widget should prefer white text + assumeDarkBackground: Pay.unlockingKeyboard !== FloweePay.BigNumbersKeyboard } Keys.onPressed: (event)=> { if (event.key !== Qt.Key_Back) { // exit app on 'back'. -- 2.54.0 From 63385c8a8c4aa9b96f5d383f3aa3db9060cde1f7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Feb 2025 21:16:12 +0100 Subject: [PATCH 1182/1428] Follow upstream; port asio usage --- modules/blocks/BlockHeadersChecker.cpp | 2 +- src/FloweePay.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 6228660..7153ee4 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -231,7 +231,7 @@ void BlockHeadersChecker::downloadFinished() // keep the file object from getting garbage collected. // the map() requires the QFile to stay alive. m_newHeaders->setParent(FloweePay::instance()); - FloweePay::instance()->ioService().post(std::bind(&BlockHeadersChecker::processNewHeaders, this)); + FloweePay::instance()->ioContext().post(std::bind(&BlockHeadersChecker::processNewHeaders, this)); } void BlockHeadersChecker::processNewHeaders() diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1ab9c63..40a76ac 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -142,7 +142,7 @@ QString joinWords(const QList &words, bool lowercaseFirstWord) FloweePay::FloweePay() : m_chain(s_chain), m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), - m_indexerServices(new IndexerServices(m_basedir, ioService(), this)) + m_indexerServices(new IndexerServices(m_basedir, ioContext(), this)) { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); @@ -576,7 +576,7 @@ QString FloweePay::basedir() const void FloweePay::startP2PInit() { // start creation of downloadmanager and loading of data in a different thread - ioService().post(std::bind(&FloweePay::init, this)); + ioContext().post(std::bind(&FloweePay::init, this)); } QString FloweePay::amountToStringPretty(double price) const @@ -1563,7 +1563,7 @@ QList FloweePay::wallets() const DownloadManager *FloweePay::p2pNet() { if (m_downloadManager == nullptr) { - m_downloadManager.reset(new DownloadManager(ioService(), m_basedir.toStdString(), m_chain)); + m_downloadManager.reset(new DownloadManager(ioContext(), m_basedir.toStdString(), m_chain)); m_downloadManager->addHeaderListener(this); m_downloadManager->addP2PNetListener(this); m_downloadManager->notifications().addListener(&m_notifications); -- 2.54.0 From b7ebe1ad7af29399134dce22d5d6bf1e84c5c5ac Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Feb 2025 22:56:59 +0100 Subject: [PATCH 1183/1428] Fix radius Radius should visually be the same on the inner and outer circles. --- guis/mobile/PriceInputWidget.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index c2a765d..7f0ddc2 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -106,7 +106,7 @@ FocusScope { border.color: palette.midlight color: palette.light width: inputs.width + 20 - radius: 15 + radius: 9 Rectangle { color: palette.highlight -- 2.54.0 From 2d7cfde4df2ed26c7400f6a33832aa460a15145a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Feb 2025 22:57:14 +0100 Subject: [PATCH 1184/1428] Redo ReceiveTab --- guis/mobile/ReceiveTab.qml | 208 +++++++++++++++++++++++++++++- guis/mobile/UnlockApplication.qml | 4 + 2 files changed, 207 insertions(+), 5 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 4a97ba6..740620b 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -16,15 +16,17 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Shapes -import QtQuick.Layouts +import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay FocusScope { - id: receiveTab + id: root property string icon: "qrc:/receive.svg" - property string title: qsTr("Receive") + property string title: qsTr("Receive") + + property bool showInstructions: true + focus: true property QtObject account: portfolio.current @@ -51,7 +53,202 @@ FocusScope { if (activeFocus) request.start(); } + Column { + id: verticalTabs + y: 50 + x: -10 + Repeater { + model: ["qr-code", "edit-money", "edit-pen"] + MouseArea { + property bool active: index === swipeView.currentIndex + width: 50 + height: 50 + onClicked: swipeView.currentIndex = index + Rectangle { + x: 46 + y: 5 + width: 4 + height: 40 + color: palette.highlight + visible: parent.active + } + Rectangle { + anchors.fill: parent + color: palette.highlight + visible: parent.active + opacity: 0.15 + } + Image { + anchors.fill: parent + anchors.margins: 10 + source: "qrc:/" + modelData + (Pay.useDarkSkin ? "-light" : "") + ".svg" + smooth: true + } + } + + } + } + + Flowee.Label { + id: instructions + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Share this QR to receive") + opacity: editViewActive ? 0 : 0.5 + Behavior on opacity { OpacityAnimator { } } + visible: root.showInstructions + } + Flowee.QRWidget { + id: qr + width: parent.width + y: root.showInstructions ? instructions.height : 0 + qrSize: { + var avail = root.height - y - swipeView.height; + return Math.min(256, avail); + } + textVisible: false + qrText: request.qr + + Flowee.Label { + visible: request.failReason !== PaymentRequest.NoFailure + text: { + var f = request.failReason; + if (f === PaymentRequest.AccountEncrypted) + return qsTr("Encrypted Wallet"); + if (f === PaymentRequest.AccountImporting) + return qsTr("Import Running..."); + if (f === PaymentRequest.NoAccountSet) + return "No Account Set"; // not translated b/c cause is bug in QML + return ""; + } + anchors.centerIn: parent + width: parent.width - 40 + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 18 + } + } + QQC2.SwipeView { + id: swipeView + width: parent.width + 20 // to ensure swiped pages are wide enough + x: -10 + height: { + var have = parent.height - (showInstructions ? instructions.height : 0) - 256; // qr is ideally 256 + if (currentIndex === 1) + return Math.max(have, priceInput.implicitHeight + 215); + return have - 6; // spacing of 6 + } + + anchors.bottom: parent.bottom + + Item { + Column { + width: parent.width - 20 + x: 10 + + Flowee.BitcoinAmountLabel { + visible: value > 0 + colorize: false + value: request.amount + font.pixelSize: instructions.font.pixelSize * 1.4 + anchors.horizontalCenter: parent.horizontalCenter + } + + PageTitledBox { + width: parent.width + title: qsTr("Address", "Bitcoin Cash address") + Flowee.LabelWithClipboard { + width: parent.width + text: request.addressShort + fontSizeMode: Text.HorizontalFit + } + } + + Flowee.Button { + anchors.right: parent.right + text: qsTr("Clear") + enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; + onClicked: { + request.clear(); + request.account = portfolio.current; + description.text = ""; + priceInput.paymentBackend.paymentAmount = 0; + request.start(); + } + } + } + } + + Item { + PriceInputWidget { + id: priceInput + fiatFollowsSats: false + width: parent.width + + Connections { + target: Fiat + /* + * The price may change simply because the market moved and we re-cheked the server, + * but more important is the case where the user changed which currency they use. + */ + function onPriceChanged() { + // set the value again to trigger an updated exchange-rate calculation to happen. + var price = priceInput.editor.value; + if (priceInput.fiatFollowsSats) + priceInput.paymentBackend.paymentAmount = price; + else + priceInput.paymentBackend.paymentAmountFiat = price; + } + } + } + + Rectangle { + width: parent.width + anchors.bottom: parent.bottom + color: palette.base + height: 215 + } + NumericKeyboardWidget { + width: parent.width + height: 200 + anchors.bottom: parent.bottom + anchors.bottomMargin: 10 + onFinished: passwordData.finished(); + dataInput: priceInput + + buttonBackground: Item { + property int index: 0 + property bool pressed: false + } + } + } + + Item { + Column { + width: parent.width - 20 + x: 10 + spacing: 10 + Flowee.BitcoinAmountLabel { + anchors.horizontalCenter: parent.horizontalCenter + visible: value > 0 + colorize: false + value: request.amount + font.pixelSize: instructions.font.pixelSize * 1.4 + } + PageTitledBox { + width: parent.width + title: qsTr("Description") + Flowee.TextField { + id: description + width: parent.width + // focus: true + onTotalTextChanged: request.message = totalText + } + } + } + } + } + + /* Column { id: verticalTabs y: 50 @@ -247,7 +444,7 @@ FocusScope { /* * The price may change simply because the market moved and we re-cheked the server, * but more important is the case where the user changed which currency they use. - */ + * / function onPriceChanged() { // set the value again to trigger an updated exchange-rate calculation to happen. var price = priceInput.editor.value; @@ -432,4 +629,5 @@ FocusScope { } } } + */ } diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 72bb7f7..8befdde 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -90,9 +90,11 @@ FocusScope { FocusScope { anchors.fill: parent Rectangle { + id: background anchors.fill: parent color: palette.window visible: myLittleStack.depth > 0 + MouseArea { anchors.fill: parent; acceptedButtons: Qt.AllButtons } // catch all QQC2.StackView { id: myLittleStack @@ -116,9 +118,11 @@ FocusScope { Page { function takeFocus() { receiveTab.forceActiveFocus(); } backHandler: function handler() { myLittleStack.clear() } + headerText: receiveTab.title ReceiveTab { id: receiveTab anchors.fill: parent + showInstructions: false } } } -- 2.54.0 From b4a5b480de2f675884911bd8841e84185447ea55 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Feb 2025 23:31:57 +0100 Subject: [PATCH 1185/1428] Add proper namespaces --- guis/mobile/main.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 3588b9e..31c9a53 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -24,7 +24,8 @@ import Flowee.org.pay; import QtQuick.Controls.Basic -ApplicationWindow { + +QQC2.ApplicationWindow { id: mainWindow title: "Flowee Pay" width: 360 @@ -214,7 +215,7 @@ ApplicationWindow { border.width: 1 radius: 5 } - Overlay.modeless: Rectangle { + QQC2.Overlay.modeless: Rectangle { color: Pay.useDarkSkin ? "#33000000" : "#33ffffff" } -- 2.54.0 From ede60931183e531c019c2b0c3eb419fc218f3f84 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 12:32:27 +0100 Subject: [PATCH 1186/1428] Ignore more txs for PaymentRequest Transactions submitted that are older than an hour won't be used for the PaymentRequest fulfillment. This is relevant when the user syncs the wallet after being offline for a longer time. Also ignore incoming transactions for a request when the request has no price set. For those transactions we should really just have a generic popup. --- src/PaymentRequest.cpp | 6 +++++- src/WalletKeyView.cpp | 8 ++++++-- src/WalletKeyView.h | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/PaymentRequest.cpp b/src/PaymentRequest.cpp index f2d2ff5..30d0e47 100644 --- a/src/PaymentRequest.cpp +++ b/src/PaymentRequest.cpp @@ -149,11 +149,15 @@ void PaymentRequest::setAccount(QObject *account) }); connect (m_view, &WalletKeyView::transactionMatch, this, [=]() { assert(QThread::currentThread() == thread()); + if (m_amountRequested == 0) // no point in giving feedback when it is just a general transaction. + return; int64_t seen = 0; auto view = qobject_cast(sender()); assert(view); for (const auto &tx : view->transactions()) { - if (tx.state != WalletKeyView::UTXORejected) { + // ignore old transactions because we don't actually persist this request and the + // value proposition of this class is actively waiting for an incoming tx. + if (tx.state != WalletKeyView::UTXORejected && tx.state != WalletKeyView::OldUTXO) { seen += tx.amount; } // copy the user typed payment request to the wallet diff --git a/src/WalletKeyView.cpp b/src/WalletKeyView.cpp index 77a6ce2..f737ec8 100644 --- a/src/WalletKeyView.cpp +++ b/src/WalletKeyView.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ #include "WalletKeyView.h" #include "Wallet.h" +#include "FloweePay.h" #include @@ -57,6 +58,7 @@ void WalletKeyView::appendedTransactions(const int firstNew, const int count) assert(QThread::currentThread() == thread()); QMutexLocker locker(&m_wallet->m_lock); logInfo() << " getting new tx. Index:" << firstNew << "count:" << count; + const auto currentHeight = FloweePay::instance()->chainHeight(); for (int txIndex = firstNew; txIndex < firstNew + count; ++txIndex) { auto itemIter = m_wallet->m_walletTransactions.find(txIndex); if (itemIter == m_wallet->m_walletTransactions.end()) // the wallet changed, we're probably waiting for a queued connection @@ -67,8 +69,10 @@ void WalletKeyView::appendedTransactions(const int firstNew, const int count) tx.ref = Wallet::OutputRef(txIndex, output.first).encoded(); tx.state = UTXOSeen; const auto blockHeight = itemIter->second.minedBlockHeight; - if (blockHeight > 0) + if (currentHeight - blockHeight < 7) tx.state = UTXOConfirmed; + else if (blockHeight > 0) + tx.state = OldUTXO; else if (blockHeight == -2) tx.state = UTXORejected; tx.amount = output.second.value; diff --git a/src/WalletKeyView.h b/src/WalletKeyView.h index 9950b0f..ea17dcd 100644 --- a/src/WalletKeyView.h +++ b/src/WalletKeyView.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,6 +51,7 @@ public: enum UTXOState { UTXOSeen, UTXOConfirmed, + OldUTXO, UTXORejected }; -- 2.54.0 From ca194f1e395b8f4a699ce84acd411d64d0f806f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 16:04:45 +0100 Subject: [PATCH 1187/1428] Re-add the "payment accepted" screen. It is a bit simpler, only covering the lower half and it is better for when the payment made was too low. --- guis/mobile/ReceiveTab.qml | 365 ++++-------------------------- guis/mobile/UnlockApplication.qml | 3 + 2 files changed, 44 insertions(+), 324 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 740620b..8c689d8 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -24,18 +24,16 @@ FocusScope { id: root property string icon: "qrc:/receive.svg" property string title: qsTr("Receive") - property bool showInstructions: true - - - focus: true property QtObject account: portfolio.current property bool qrViewActive: true; property bool editViewActive: false + focus: true PaymentRequest { id: request amount: priceInput.paymentBackend.paymentAmount + onAmountSeenChanged: feedback.hide = false; } onAccountChanged: { @@ -57,6 +55,7 @@ FocusScope { id: verticalTabs y: 50 x: -10 + enabled: feedback.opacity < 0.1 Repeater { model: ["qr-code", "edit-money", "edit-pen"] MouseArea { @@ -84,6 +83,7 @@ FocusScope { anchors.margins: 10 source: "qrc:/" + modelData + (Pay.useDarkSkin ? "-light" : "") + ".svg" smooth: true + opacity: enabled ? 1 : 0.4 } } @@ -248,231 +248,12 @@ FocusScope { } } - /* - Column { - id: verticalTabs - y: 50 - x: -10 - Repeater { - model: ["qr-code", "edit-pen"] - - delegate: MouseArea { - property bool active: index === 0 - onActiveChanged: { - let a = active; - if (a) // disable the other one - verticalTabs.children[(index + 1) % 2].active = false // yes, this warns during construction ツ)_/¯ - // update page-scope state variables - if (index === 1) - a = !a; - qrViewActive = a; - editViewActive = !a; - if (a) - feedbackScope.forceActiveFocus(); - else - editScope.forceActiveFocus(); - } - width: 50 - height: 50 - onClicked: active = true; - - Rectangle { - anchors.fill: parent - color: palette.window - } - Rectangle { - x: 46 - y: 5 - width: 4 - height: 40 - color: palette.highlight - visible: active - } - Rectangle { - anchors.fill: parent - color: palette.highlight - visible: active - opacity: 0.15 - } - Image { - anchors.fill: parent - anchors.margins: 10 - source: "qrc:/" + modelData + (Pay.useDarkSkin ? "-light" : "") + ".svg" - smooth: true - } - } - } - } - - Flowee.Label { - id: instructions - y: 35 - height - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Share this QR to receive") - opacity: editViewActive ? 0 : 0.5 - Behavior on opacity { OpacityAnimator { } } - } - - Flowee.QRWidget { - id: qr - width: parent.width - y: qrViewActive ? 40 : editBox.height - x: qrViewActive || editBox.editingTextField ? 0 : 0 - width - qrSize: qrViewActive ? 256 : 160 - textVisible: false - qrText: request.qr - - Flowee.Label { - visible: request.failReason !== PaymentRequest.NoFailure - text: { - var f = request.failReason; - if (f === PaymentRequest.AccountEncrypted) - return qsTr("Encrypted Wallet"); - if (f === PaymentRequest.AccountImporting) - return qsTr("Import Running..."); - if (f === PaymentRequest.NoAccountSet) - return "No Account Set"; // not translated b/c cause is bug in QML - return ""; - } - anchors.centerIn: parent - width: parent.width - 40 - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pointSize: 18 - } - Behavior on y { NumberAnimation { } } - Behavior on x { NumberAnimation { } } - } - - // feedback-fields - FocusScope { - id: feedbackScope - width: parent.width - y: instructions.height + qr.height + 30 - height: receiveTab.height - y - clip: true - - Flickable { - anchors.fill: parent - contentHeight: column.height - contentWidth: parent.width - boundsMovement: Flickable.StopAtBounds - - Column { - id: column - width: parent.width - spacing: 10 - opacity: qrViewActive ? 1 : 0 - - PageTitledBox { - width: parent.width - visible: request.message !== "" - title: qsTr("Description") - Flowee.Label { - text: request.message - } - } - PageTitledBox { - width: parent.width - visible: request.amount > 0 - title: qsTr("Amount", "requested amount of coin") - Flowee.BitcoinAmountLabel { - colorize: false - value: request.amount - } - } - PageTitledBox { - width: parent.width - title: qsTr("Address", "Bitcoin Cash address") - Flowee.LabelWithClipboard { - width: parent.width - text: request.addressShort - font.pixelSize: instructions.font.pixelSize * 0.9 - } - } - Flowee.Button { - anchors.right: parent.right - text: qsTr("Clear") - enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; - onClicked: { - request.clear(); - request.account = portfolio.current; - description.text = ""; - priceInput.paymentBackend.paymentAmount = 0; - request.start(); - } - } - Behavior on opacity { OpacityAnimator { } } - } - } - } - - // edit fields - FocusScope { - id: editScope - width: parent.width - height: editBox.height - enabled: editViewActive - PageTitledBox { - id: editBox - width: parent.width - 50 - x: 50 - title: qsTr("Description") - opacity: editViewActive ? 1 : 0 - - // little state-machine to switch between the two - // input options - property bool editingTextField: true - - Flowee.TextField { - id: description - width: parent.width - focus: true - onTotalTextChanged: request.message = totalText - onActiveFocusChanged: if (activeFocus) editBox.editingTextField = true - } - - Behavior on opacity { OpacityAnimator { } } - - PriceInputWidget { - id: priceInput - fiatFollowsSats: false - width: parent.width - onActiveFocusChanged: if (activeFocus) editBox.editingTextField = false - - Connections { - target: Fiat - /* - * The price may change simply because the market moved and we re-cheked the server, - * but more important is the case where the user changed which currency they use. - * / - function onPriceChanged() { - // set the value again to trigger an updated exchange-rate calculation to happen. - var price = priceInput.editor.value; - if (priceInput.fiatFollowsSats) - priceInput.paymentBackend.paymentAmount = price; - else - priceInput.paymentBackend.paymentAmountFiat = price; - } - } - } - } - } - - NumericKeyboardWidget { - id: numericKeyboard - width: parent.width - x: qrViewActive || editBox.editingTextField ? width + 10 : 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 15 - dataInput: priceInput - } - // the "payment received" screen. Rectangle { - anchors.fill: parent - anchors.leftMargin: -10 // be actually edge to edge - anchors.rightMargin: -10 - radius: 10 + id: feedback + property bool hide: false // hide a partially paid popup + + anchors.fill: swipeView gradient: Gradient { GradientStop { position: 0.6 @@ -482,110 +263,48 @@ FocusScope { return palette.base if (state === PaymentRequest.DoubleSpentSeen) return mainWindow.errorRedBg - return "#3e8b4e" // in all other cases: green + return "#60b671" // in all other cases: green } Behavior on color { ColorAnimation {} } } GradientStop { - position: 0.1 + position: 0.01 color: palette.base } } - opacity: request.state === PaymentRequest.Unpaid ? 0: 1 + opacity: hide || request.state === PaymentRequest.Unpaid ? 0: 1 enabled: opacity > 0 visible: opacity > 0 // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) - Item { - id: feedback - width: 160 - height: 160 - - y: 60 + Flowee.Label { + color: "green" + text: "✔" + // y: 60 + y: -30 // basically avoid the xheight x: 30 - visible: request.state !== PaymentRequest.DoubleSpentSeen - Shape { - id: circleShape - anchors.fill: parent - opacity: request.state === PaymentRequest.Unpaid ? 0: 1 - x: 40 - ShapePath { - strokeWidth: 20 - strokeColor: request.state === PaymentRequest.PaymentSeenOk - ? "green" : "#9ea0b0" - fillColor: "transparent" - capStyle: ShapePath.RoundCap - startX: 100; startY: 10 - - PathAngleArc { - id: progressCircle - centerX: 80 - centerY: 80 - radiusX: 70; radiusY: 70 - startAngle: -20 - sweepAngle: request.state === PaymentRequest.Unpaid ? 0: 340 - - Behavior on sweepAngle { NumberAnimation { duration: Pay.dspTimeout } } - } - } - - Behavior on opacity { OpacityAnimator {} } - } - Flowee.Label { - color: "green" - text: "✔" - opacity: { - if (request.state === PaymentRequest.PaymentSeenOk - || request.state === PaymentRequest.Confirmed) - return 1; - return 0; - } - font.pixelSize: 130 - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - } - } - // textual feedback - Item { - clip: true - anchors.top: feedback.top - anchors.left: feedback.right - anchors.right: parent.right - height: 160 opacity: { - var s = request.state - if (s == PaymentRequest.PartiallyPaid - || s === PaymentRequest.PaymentSeen) + if (request.state === PaymentRequest.PaymentSeenOk || request.state === PaymentRequest.Confirmed) return 1; return 0; } - Flowee.Label { - id: label1 - text: qsTr("Payment Seen") - x: request.state === PaymentRequest.Unpaid ? (0 - width - 10) : 0 - y: 30 - Behavior on x { NumberAnimation { } } - } - Flowee.Label { - id: label2 - text: qsTr("Checking...") // checking security - x: (label1.x > (0 - label1.width) + 20) ? 0 : 0 - width - 10 - y: label1.height + 8 + 30 - Behavior on x { NumberAnimation { } } - } - Behavior on opacity { OpacityAnimator { duration: 1000 } } + font.pixelSize: 130 + } + // textual feedback + Flowee.Label { + text: qsTr("Payment Seen") + " (" + ((request.amountSeen / request.amount) * 100).toFixed(2) + " %)" + y: 70 + x: 20 + opacity: request.state === PaymentRequest.PartiallyPaid ? 1 : 0 } - Flowee.Label { id: feedbackLabel text: { var s = request.state; - if (s === PaymentRequest.DoubleSpentSeen) { - // double-spent-proof received + if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received return qsTr("High risk transaction"); - } if (s === PaymentRequest.PartiallyPaid) return qsTr("Partially Paid"); if (s === PaymentRequest.PaymentSeen) @@ -598,36 +317,34 @@ FocusScope { } width: parent.width - 40 anchors.horizontalCenter: parent.horizontalCenter - anchors.top: feedback.bottom + y: 100 wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 20 } - Flowee.Label { - visible: request.state === PaymentRequest.DoubleSpentSeen - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: feedbackLabel.bottom - anchors.topMargin: 20 - width: parent.width - 40 - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - text: qsTr("Instant payment failed. Wait for confirmation. (double spent proof received)") - } - Behavior on opacity { OpacityAnimator {} } - Flowee.Button { - anchors.right: parent.right - anchors.rightMargin: 10 - y: 400 - text: qsTr("Continue") + Flowee.BigCloseButton { + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 onClicked: { request.clear(); description.text = ""; priceInput.paymentBackend.paymentAmount = 0; request.account = portfolio.current; request.start(); + feedback.hide = false; switchToTab(0); // go to the 'main' tab. } + visible: request.state !== PaymentRequest.PartiallyPaid + } + + Flowee.Button { + anchors.right: parent.right + anchors.rightMargin: 10 + y: 160 + text: qsTr("Continue") + visible: request.state === PaymentRequest.PartiallyPaid + onClicked: feedback.hide = true; } } - */ } diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 8befdde..7b19433 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -119,6 +119,9 @@ FocusScope { function takeFocus() { receiveTab.forceActiveFocus(); } backHandler: function handler() { myLittleStack.clear() } headerText: receiveTab.title + function switchToTab(tab) { + myLittleStack.clear(); + } ReceiveTab { id: receiveTab anchors.fill: parent -- 2.54.0 From 39cf1a979de590f26afd135b54228eda80f95249 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 16:33:23 +0100 Subject: [PATCH 1188/1428] Add icon --- guis/mobile.qrc | 2 ++ guis/mobile/images/edit-money-light.svg | 6 ++++++ guis/mobile/images/edit-money.svg | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 guis/mobile/images/edit-money-light.svg create mode 100644 guis/mobile/images/edit-money.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 0fdfb8c..291f622 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -23,6 +23,8 @@ mobile/images/settingsIcon.svg mobile/images/edit-pen.svg mobile/images/edit-pen-light.svg + mobile/images/edit-money-light.svg + mobile/images/edit-money.svg mobile/images/tx-receiving.svg mobile/images/tx-receiving-light.svg mobile/images/tx-send.svg diff --git a/guis/mobile/images/edit-money-light.svg b/guis/mobile/images/edit-money-light.svg new file mode 100644 index 0000000..e70eada --- /dev/null +++ b/guis/mobile/images/edit-money-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/guis/mobile/images/edit-money.svg b/guis/mobile/images/edit-money.svg new file mode 100644 index 0000000..85fe78f --- /dev/null +++ b/guis/mobile/images/edit-money.svg @@ -0,0 +1,6 @@ + + + + + + -- 2.54.0 From 5315fbcde85fd9cf3def9d3e238d02ef6844c4aa Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 16:37:20 +0100 Subject: [PATCH 1189/1428] Avoid loss of privacy; don't show used address The quick receive does not appear when your default wallet is a single address wallet because in that case the address that will be shown will have history. --- guis/mobile/UnlockApplication.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 7b19433..4fde50c 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -67,6 +67,8 @@ FocusScope { return parent.height - 240 - height } spacing: 6 + visible: !portfolio.current.isSingleAddressAccount + Image { source: "qrc:/back-arrow" rotation: 270 @@ -83,6 +85,7 @@ FocusScope { } } MouseArea { + enabled: quickReceive.visible anchors.fill: quickReceive onClicked: myLittleStack.push(receive) } -- 2.54.0 From ddd6266cbf507299fa4c3b4a61573143ca3ab4aa Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 20:17:31 +0100 Subject: [PATCH 1190/1428] Add in-app notifications --- guis/mobile/main.qml | 52 +++++++++++++--- src/CMakeLists.txt | 2 +- src/FloweePay.cpp | 20 +++++++ src/FloweePay.h | 11 +++- src/NotificationManager.cpp | 39 ++++++++++++ src/NotificationManager.h | 2 + src/NotificationManager_android.cpp | 93 ++++++++++++++++++++++++++--- src/NotificationManager_dbus.cpp | 52 +++------------- src/NotificationManager_p_android.h | 74 +++++++++++++++++++++++ 9 files changed, 284 insertions(+), 61 deletions(-) create mode 100644 src/NotificationManager_p_android.h diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 31c9a53..6797ea8 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -181,14 +181,24 @@ QQC2.ApplicationWindow { x: 25 width: mainWindow.contentItem.width - 50 height: label.implicitHeight * 3 - dim: true property alias text: label.text + property QtObject notification: Pay.notification + property int timeout: 600 + + onVisibleChanged: { + if (!visible) { + label.text = ""; + Pay.notification = null; + } + } + onNotificationChanged: if (notification) show(notification.message) function show(message) { if (message === "") return; text = message; visible = true; + timeout = 600; } Flowee.Label { id: label @@ -200,13 +210,16 @@ QQC2.ApplicationWindow { Rectangle { id: timeoutBar width: 5 - height: visible ? parent.height - 12 : 1 + height: { + var max = parent.height - 12; + return max - (notificationPopup.timeout / 600 * max); + } anchors.right: parent.right anchors.top: parent.top anchors.topMargin: 6 color: palette.highlight - Behavior on height { NumberAnimation { duration: notificationPopupTimer.interval } } + Behavior on height { NumberAnimation { duration: 100 } } } background: Rectangle { @@ -221,10 +234,35 @@ QQC2.ApplicationWindow { Timer { id: notificationPopupTimer - running: parent.visible; - interval: 6000 - onTriggered: notificationPopup.visible = false; + running: parent.visible && notificationPopup.timeout > 0; + interval: 100 + onTriggered: { + var t = notificationPopup.timeout; + t -= 10; + notificationPopup.timeout = t; + if (t <= 0) + notificationPopup.visible = false; + } + } + } + Item { // slide to hide for the notification popup + width: notificationPopup.width + height: notificationPopup.height + x: notificationPopup.x + y: notificationPopup.y + visible: notificationPopup.visible + onXChanged: { + notificationPopup.x = x; + if (x < -175 || x > 225) + notificationPopup.visible = false; + } + + DragHandler { + yAxis.enabled: false + xAxis.enabled: true + margin: 15 + onActiveChanged: if (!active) parent.x = 25; } } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00a00ae..9837a1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -63,7 +63,7 @@ set (PAY_SOURCES ModuleSection.cpp ) if (ANDROID) - list(APPEND PAY_SOURCES NotificationManager_android.cpp) + list(APPEND PAY_SOURCES NotificationManager_p_android.h NotificationManager_android.cpp) elseif (${Qt6DBus_FOUND}) list(APPEND PAY_SOURCES NotificationManager_p_dbus.h NotificationManager_dbus.cpp) else () diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 40a76ac..26841ed 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -953,6 +953,26 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +QObject *FloweePay::notification() const +{ + return m_notification; +} + +void FloweePay::setNotification(QObject *n) +{ + if (m_notification == n) + return; + if (m_notification) + m_notification->deleteLater(); + m_notification = n; + emit notificationChanged(); +} + +bool FloweePay::headless() const +{ + return m_headless; +} + FloweePay::UnlockingKeyboard FloweePay::unlockingKeyboard() const { return m_unlockingKeyboard; diff --git a/src/FloweePay.h b/src/FloweePay.h index 994c9a3..c189941 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -73,6 +73,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) // notifications + Q_PROPERTY(QObject *notification READ notification WRITE setNotification NOTIFY notificationChanged FINAL) Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) Q_PROPERTY(bool offline READ isOffline CONSTANT) @@ -419,6 +420,11 @@ public: UnlockingKeyboard unlockingKeyboard() const; void setUnlockingKeyboard(UnlockingKeyboard newUnlockingKeyboard); + bool headless() const; + + QObject *notification() const; + void setNotification(QObject *newNotification); + signals: void loadComplete(); /// \internal @@ -443,9 +449,10 @@ signals: void skinFollowsPlatformChanged(); void blockHeightCertaintyChanged(); void internal_heightCertaintyChanged(); // not thread-safe - void unlockingKeyboardChanged(); + void notificationChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -480,6 +487,7 @@ private: std::unique_ptr m_prices; std::unique_ptr m_network; NotificationManager m_notifications; + QObject *m_notification = nullptr; CameraController* m_cameraController; IndexerServices *m_indexerServices; // the Electrum indexer index. QList m_wallets; @@ -492,6 +500,7 @@ private: bool m_loadingCompleted = false; // 'init()' completed bool m_loadCompleteEmitted = false; // avoid emitting this on every unlock bool m_appUnlocked = false; + bool m_headless = false; bool m_darkSkin = true; bool m_skinFollowsPlatform = false; bool m_createStartWallet = false; diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 1184f4a..6488327 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -15,7 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "FloweePay.h" #include "NotificationManager.h" +#include "PriceDataProvider.h" #include #include @@ -39,3 +41,40 @@ void NotificationManager::headerSyncComplete() { m_headerSyncComplete = true; } + +QString NotificationManager::describeCollated(int &txCount) const +{ + const auto data = collatedData(); + txCount = 0; + if (data.empty()) + return QString(); + + int64_t deposited = 0; + int64_t spent = 0; + for (const auto &item : data) { + deposited += item.deposited; + spent += item.spent; + txCount += item.txCount; + } + + const auto gained = deposited - spent; + auto pricesOracle = FloweePay::instance()->prices(); + QString gainedStr; + if (pricesOracle->price()) + gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price()); + if (gainedStr.isEmpty()) { + // no price data available (yet). Display crypto units + gainedStr = QString("%1 %2") + .arg(FloweePay::instance()->amountToStringPretty((double) gained), + FloweePay::instance()->unitName()); + } + if (gained > 0) // since we indicate adding, we always want the plus there + gainedStr = QString("+%1").arg(gainedStr); + + // body-text + if (data.size() > 1) + return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); + if (gained < 0 && txCount == 1) + return tr("A payment of %1 has been sent").arg(gainedStr.mid(1)); + return tr("%1 new transactions found (%2)", "", txCount).arg(txCount).arg(gainedStr); +} diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 1f17552..6052416 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -47,6 +47,8 @@ private: bool m_headerSyncComplete = false; bool m_newBlockMuted = false; + QString describeCollated(int &txCount) const; + static constexpr const char *KEY_MUTE = "notificationNewblockMute"; friend class NotificationManagerPrivate; NotificationManagerPrivate *d; diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 634ecb6..52c06a8 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -16,19 +16,20 @@ * along with this program. If not, see . */ #include "NotificationManager.h" +#include "NotificationManager_p_android.h" #include "FloweePay.h" -#include "PriceDataProvider.h" #include #include -#include #include - +#if TARGET_OS_Android +# include constexpr const char *ClassName = "org/flowee/pay/PayNotifications"; +#endif NotificationManager::NotificationManager(QObject *parent) : QObject(parent), - d(nullptr) + d(new NotificationManagerPrivate(this)) { setCollation(true); QSettings appConfig; @@ -42,12 +43,45 @@ void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ificatio if (!m_headerSyncComplete) return; + d->newBlockSeen_fromNetwork(notification.blockHeight); +} + +void NotificationManager::segmentUpdated(const P2PNet::Notification&) +{ + d->segmentUpdated_fromNetwork(); +} + + +// --------------------------------------------------------- +NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) + : q(qq) +{ + connect (this, &NotificationManagerPrivate::newBlockSeenSignal, + this, &NotificationManagerPrivate::newBlockSeen, Qt::QueuedConnection); + connect (this, &NotificationManagerPrivate::segmentUpdatedSignal, + this, &NotificationManagerPrivate::segmentUpdated, Qt::QueuedConnection); +} + +void NotificationManagerPrivate::newBlockSeen_fromNetwork(int height) +{ + emit newBlockSeenSignal(height); +} + +void NotificationManagerPrivate::segmentUpdated_fromNetwork() +{ + emit segmentUpdatedSignal(); +} + +// on the local thread. +void NotificationManagerPrivate::newBlockSeen(int blockHeight) +{ QString message; if (FloweePay::instance()->chain() == P2PNet::MainChain) - message = tr("Bitcoin Cash block mined. Height: %1").arg(notification.blockHeight); + message = tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); else - message = tr("tBCH (testnet4) block mined: %1").arg(notification.blockHeight); + message = tr("tBCH (testnet4) block mined: %1").arg(blockHeight); +#if TARGET_OS_Android auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { QJniEnvironment env; auto jmessage = QJniObject::fromString(message); @@ -57,9 +91,54 @@ void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ificatio }); // The java side will handle everything from here, so we just ignore the task. Q_UNUSED(task); +#endif } -void NotificationManager::segmentUpdated(const P2PNet::Notification&) +// on the local thread. +void NotificationManagerPrivate::segmentUpdated() +{ + if (FloweePay::instance()->headless()) + createAndroidTxNotification(); + else + createQmlNotification(); +} + +void NotificationManagerPrivate::createAndroidTxNotification() { // TODO } + +void NotificationManagerPrivate::createQmlNotification() +{ + int txCount; + QString message = q->describeCollated(txCount); + if (message.isEmpty()) + return; + + Notification *n = new Notification(q); + connect (n, &QObject::destroyed, this, [=]{ + q->flushCollate(); + }); + // TODO does this not need a title? + n->setMessage(message); + FloweePay::instance()->setNotification(n); +} + + +Notification::Notification(QObject *parent) + : QObject(parent) +{ +} + +QString Notification::message() const +{ + return m_message; +} + +void Notification::setMessage(const QString &newMessage) +{ + if (m_message == newMessage) + return; + m_message = newMessage; + emit messageChanged(); +} diff --git a/src/NotificationManager_dbus.cpp b/src/NotificationManager_dbus.cpp index dad90fa..8db0c5e 100644 --- a/src/NotificationManager_dbus.cpp +++ b/src/NotificationManager_dbus.cpp @@ -18,7 +18,6 @@ #include "NotificationManager.h" #include "NotificationManager_p_dbus.h" #include "FloweePay.h" -#include "PriceDataProvider.h" #include @@ -119,63 +118,26 @@ void NotificationManagerPrivate::newBlockNotificationShown(uint id) void NotificationManagerPrivate::segmentUpdated() { - const auto data = q->collatedData(); - if (data.empty()) - return; - if (m_openingNewFundsNotification) { // we are currently (async) opening a notification, wait until its actually open. QTimer::singleShot(50, this, SLOT(segmentUpdated())); return; } - int64_t deposited = 0; - int64_t spent = 0; - int txCount = 0; - for (const auto &item : data) { - deposited += item.deposited; - spent += item.spent; - txCount += item.txCount; - } - auto iface = remote(); if (!iface->isValid()) return; + + int txCount; + QString message = q->describeCollated(txCount); + if (message.isEmpty()) + return; + QVariantList args; args << QVariant("Flowee Pay"); // app-name args << QVariant(m_newFundsNotificationId); // replaces-id args << QString(); // app_icon (not needed since we say which desktop file we are) args << tr("New Transactions", "dialog-title", txCount); - - const auto gained = deposited - spent; - auto pricesOracle = FloweePay::instance()->prices(); - QString gainedStr; - if (pricesOracle->price()) - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price()); - if (gainedStr.isEmpty()) { - // no price data available (yet). Display crypto units - gainedStr = QString("%1 %2") - .arg(FloweePay::instance()->amountToStringPretty((double) gained), - FloweePay::instance()->unitName()); - } - if (gained > 0) // since we indicate adding, we always want the plus there - gainedStr = QString("+%1").arg(gainedStr); - - // body-text - if (data.size() > 1) { - args << tr("%1 new transactions across %2 wallets found (%3)") - .arg(txCount).arg(data.size()) - .arg(gainedStr); - } - else if (gained < 0 && txCount == 1) { - args << tr("A payment of %1 has been sent") - .arg(gainedStr.mid(1)); - } - else { - args << tr("%1 new transactions found (%2)", "", txCount) - .arg(txCount) - .arg(gainedStr); - } - + args << message; args << QStringList(); args << m_walletUpdateHints; args << -1; // timeout (ms) -1 means to let the server decide diff --git a/src/NotificationManager_p_android.h b/src/NotificationManager_p_android.h new file mode 100644 index 0000000..921be59 --- /dev/null +++ b/src/NotificationManager_p_android.h @@ -0,0 +1,74 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2021-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef NOTIFICATIONMANAGER_P_ANDROID_H +#define NOTIFICATIONMANAGER_P_ANDROID_H + +#include + +class NotificationManager; + +class NotificationManagerPrivate : public QObject +{ + Q_OBJECT +public: + explicit NotificationManagerPrivate(NotificationManager *qq); + + void newBlockSeen_fromNetwork(int height); + void segmentUpdated_fromNetwork(); + + /* transaction notifications are either routed to the Android + * notification system, or when the application is visible they + * will create an in-app (aka QML) popup. + * Never both in one instance. + */ + void createAndroidTxNotification(); + void createQmlNotification(); + +signals: + // emitted by notifyNewBlock_fromNetwork, in order to move the call to the Qt thread. + void newBlockSeenSignal(int blockHeight); + void segmentUpdatedSignal(); + +private slots: + void newBlockSeen(int blockHeight); + // the wallet has updated. + void segmentUpdated(); + +private: + NotificationManager * const q; +}; + + +class Notification : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString message READ message NOTIFY messageChanged FINAL) +public: + Notification(QObject *parent = nullptr); + + QString message() const; + void setMessage(const QString &newMessage); + +signals: + void messageChanged(); + +private: + QString m_message; +}; + +#endif -- 2.54.0 From eeba43969de8a071fa6a78cf2edfce9d8f38c90e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 20:20:39 +0100 Subject: [PATCH 1191/1428] new month, new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 18f5e8d..3ef1206 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="35" android:versionName="2025.02.0"> diff --git a/src/main.cpp b/src/main.cpp index fceeec5..f4d0027 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.01.1"); + qapp.setApplicationVersion("2025.02.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 973a694ae44ea7d92495803706ddde92b210ec71 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 20:41:03 +0100 Subject: [PATCH 1192/1428] Make focus move as we move through tabs. --- guis/mobile/ReceiveTab.qml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 8c689d8..3761768 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -62,7 +62,7 @@ FocusScope { property bool active: index === swipeView.currentIndex width: 50 height: 50 - onClicked: swipeView.currentIndex = index + onClicked: swipeView.moveTo(index); Rectangle { x: 46 @@ -137,10 +137,18 @@ FocusScope { return Math.max(have, priceInput.implicitHeight + 215); return have - 6; // spacing of 6 } - anchors.bottom: parent.bottom + onCurrentIndexChanged: currentItem.doFocus(); + function moveTo(index) { + forceActiveFocus(); + currentIndex = index; + currentItem.doFocus(); + } + + Item { + function doFocus() { } Column { width: parent.width - 20 x: 10 @@ -179,6 +187,8 @@ FocusScope { } Item { + function doFocus() { priceInput.takeFocus(); } + PriceInputWidget { id: priceInput fiatFollowsSats: false @@ -223,6 +233,7 @@ FocusScope { } Item { + function doFocus() { description.forceActiveFocus(); } Column { width: parent.width - 20 x: 10 @@ -240,7 +251,6 @@ FocusScope { Flowee.TextField { id: description width: parent.width - // focus: true onTotalTextChanged: request.message = totalText } } -- 2.54.0 From 76fbe0863363f530f97fce543cf167b4e047e61a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 20:46:35 +0100 Subject: [PATCH 1193/1428] Minor movement. --- guis/mobile/UnlockApplication.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 4fde50c..bcefd81 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -62,9 +62,9 @@ FocusScope { if (uk === FloweePay.BigNumbersKeyboard) return parent.height + 10; if (uk === FloweePay.FullKeyboard) - return 240; + return 300; if (uk === FloweePay.SmallNumbersKeyboard) - return parent.height - 240 - height + return parent.height - 270 - height } spacing: 6 visible: !portfolio.current.isSingleAddressAccount -- 2.54.0 From 8ecc1ebc27f7795b742e5cb0d6d1615e52875ffd Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Feb 2025 20:58:54 +0100 Subject: [PATCH 1194/1428] Delay checking of portfolio until loaded. --- guis/mobile/UnlockApplication.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index bcefd81..46ca660 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -67,7 +67,10 @@ FocusScope { return parent.height - 270 - height } spacing: 6 - visible: !portfolio.current.isSingleAddressAccount + opacity: 0 + property bool havePortfolio: typeof portfolio !== "undefined"; + onHavePortfolioChanged: opacity = portfolio.current.isSingleAddressAccount ? 0 : 1 + Behavior on opacity { NumberAnimation { } } Image { source: "qrc:/back-arrow" -- 2.54.0 From 642052384797827278c294cdd317d76842aa7654 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 11 Feb 2025 19:52:03 +0100 Subject: [PATCH 1195/1428] Port asio to stop using deprecated methods. --- modules/blocks/BlockHeadersChecker.cpp | 2 +- src/FloweePay.cpp | 3 ++- src/IndexerServices.cpp | 19 ++++++++----------- src/IndexerServices.h | 6 +++--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/modules/blocks/BlockHeadersChecker.cpp b/modules/blocks/BlockHeadersChecker.cpp index 7153ee4..327dd76 100644 --- a/modules/blocks/BlockHeadersChecker.cpp +++ b/modules/blocks/BlockHeadersChecker.cpp @@ -231,7 +231,7 @@ void BlockHeadersChecker::downloadFinished() // keep the file object from getting garbage collected. // the map() requires the QFile to stay alive. m_newHeaders->setParent(FloweePay::instance()); - FloweePay::instance()->ioContext().post(std::bind(&BlockHeadersChecker::processNewHeaders, this)); + boost::asio::post(FloweePay::instance()->ioContext(), std::bind(&BlockHeadersChecker::processNewHeaders, this)); } void BlockHeadersChecker::processNewHeaders() diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 26841ed..121b350 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -55,6 +55,7 @@ #include #include #include +#include constexpr const char *UNIT_TYPE = "unit"; constexpr const char *CREATE_START_WALLET = "create-start-wallet"; @@ -576,7 +577,7 @@ QString FloweePay::basedir() const void FloweePay::startP2PInit() { // start creation of downloadmanager and loading of data in a different thread - ioContext().post(std::bind(&FloweePay::init, this)); + boost::asio::post(ioContext(), std::bind(&FloweePay::init, this)); } QString FloweePay::amountToStringPretty(double price) const diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index d6c50db..5984c3f 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,9 +51,9 @@ constexpr const char * FILENAME = "/electrum.dat"; } -IndexerServices::IndexerServices(const QString &basedir, boost::asio::io_service &ioService, QObject *parent) +IndexerServices::IndexerServices(const QString &basedir, boost::asio::io_context &ioContext, QObject *parent) : QObject{parent}, - m_resolver(ioService), + m_resolver(ioContext), m_basedir(basedir) { connect (this, SIGNAL(startMaybeFindServices()), this, SLOT(maybeFindServices()), Qt::QueuedConnection); @@ -63,8 +63,7 @@ IndexerServices::IndexerServices(const QString &basedir, boost::asio::io_service void IndexerServices::populate() { - tcp::resolver::query query("ec-seed.flowee.cash", "http"); - m_resolver.async_resolve(query, std::bind(&IndexerServices::onSeedLookupComplete, + m_resolver.async_resolve("ec-seed.flowee.cash", "http", std::bind(&IndexerServices::onSeedLookupComplete, this, std::placeholders::_1, std::placeholders::_2)); } @@ -202,16 +201,14 @@ void IndexerServices::punish(const EndPoint &ep, int score) } } -void IndexerServices::onSeedLookupComplete(const boost::system::error_code &error, tcp::resolver::iterator iterator) +void IndexerServices::onSeedLookupComplete(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results) { if (error) return; QMutexLocker locker(&m_mutex); - tcp::resolver::iterator end; - while (iterator != end) { - const QString ip = QString::fromStdString(iterator->endpoint().address().to_string()); - ++iterator; + for (auto dnsIter = results.begin(); dnsIter != results.end(); ++dnsIter) { + const QString ip = QString::fromStdString(dnsIter->endpoint().address().to_string()); bool found = false; for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { @@ -423,7 +420,7 @@ void FetchIndexerServicePeers::socketDataAvailable() } } try { - auto ip = boost::asio::ip::address::from_string(indexer.ip.toStdString()); + auto ip = boost::asio::ip::make_address(indexer.ip.toStdString()); Q_UNUSED(ip); // the above throws if not a real IP (but an onion for instance) so we filter our // input based on that simple metric. diff --git a/src/IndexerServices.h b/src/IndexerServices.h index 57503ee..a3beb9b 100644 --- a/src/IndexerServices.h +++ b/src/IndexerServices.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ class IndexerServices : public QObject { Q_OBJECT public: - explicit IndexerServices(const QString &basedir, boost::asio::io_service &ioService, QObject *parent = nullptr); + explicit IndexerServices(const QString &basedir, boost::asio::io_context &ioContext, QObject *parent = nullptr); void populate(); @@ -64,7 +64,7 @@ private slots: private: void load(); - void onSeedLookupComplete(const boost::system::error_code &error, tcp::resolver::iterator iterator); + void onSeedLookupComplete(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results); std::deque m_knownServices; tcp::resolver m_resolver; -- 2.54.0 From 3e2c247801d927095f01e92d90d773321cf4a755 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Feb 2025 22:13:13 +0100 Subject: [PATCH 1196/1428] Remove the reindex idea again The people that needed this have been able to use it, new wallets really have no need for something as destructive as this feature. --- guis/mobile/AccountPageListItem.qml | 26 ------------------- src/AccountInfo.cpp | 40 ----------------------------- src/AccountInfo.h | 3 --- src/FloweePay.cpp | 15 ----------- src/FloweePay.h | 1 - 5 files changed, 85 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index c901089..1fc05b4 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -390,32 +390,6 @@ QQC2.Control { Item { width: 1; height: 30 } // spacer. - Rectangle { - id: reindexButton - height: archiveButtonText.height + 20 - color: mainWindow.errorRedBg - width: reindexLabel.width + 30 - radius: 3 - - QQC2.Label { - id: reindexLabel - text: qsTr("Re-scan Chain"); - anchors.centerIn: parent - color: "#fcfcfc" - } - MouseArea { - anchors.fill: parent - cursorShape: Qt.ArrowCursor - onClicked: { - thePile.pop(); - portfolio.current = root.account; - root.account.rescanBlockchain(); - } - } - } - - Item { width: 1; height: 30 } // spacer. - Rectangle { id: removeWallet height: archiveButtonText.height + 20 diff --git a/src/AccountInfo.cpp b/src/AccountInfo.cpp index 6838974..def667f 100644 --- a/src/AccountInfo.cpp +++ b/src/AccountInfo.cpp @@ -441,46 +441,6 @@ void AccountInfo::closeWallet() setHasFreshTransactions(false); } -void AccountInfo::rescanBlockchain() -{ - assert(m_wallet); - auto prio = m_wallet->segment()->priority(); - const auto segmentId = m_wallet->segment()->segmentId(); - const auto seed = m_wallet->encryptionSeed(); - QDir dir(QString::fromStdString(m_wallet->walletDir().string())); - m_wallet->saveWallet(); // ensure wallet.dat changes are saved here, not on destructor. - m_wallet->deleteLater(); - auto *oldWallet = m_wallet; - - // the only thing we need to do is remove 'wallet.dat' - dir.remove("wallet.dat"); - - // now we start a new wallet object that will be without the history. - dir.cdUp(); - m_wallet = new Wallet(dir.absolutePath().toStdString(), segmentId, seed); - m_wallet->moveToThread(thread()); - if (prio == PrivacySegment::OnlyManual) // un-archive it. - prio = PrivacySegment::Normal; - m_wallet->segment()->setPriority(prio); - auto *f = FloweePay::instance(); - f->addWallet(m_wallet); - f->removeWallet(oldWallet); - - // initiate the download - if (!f->isOffline()) - f->p2pNet()->addAction(); - - auto m = m_model.release(); - if (m) - m->deleteLater(); - emit modelsChanged(); - emit lastBlockSynchedChanged(); - emit balanceChanged(); - emit utxosChanged(); - emit timeBehindChanged(); - emit isArchivedChanged(); -} - void AccountInfo::removeAccount() { assert(m_wallet); diff --git a/src/AccountInfo.h b/src/AccountInfo.h index c1ac50d..e8d3557 100644 --- a/src/AccountInfo.h +++ b/src/AccountInfo.h @@ -130,9 +130,6 @@ public: /// Remove all secrets from the encrypted wallet. Q_INVOKABLE void closeWallet(); - /// Remove history and re-scan the chain for all transactions. - Q_INVOKABLE void rescanBlockchain(); - // Remove, delete, throw in a black-hole, the wallet and all files. Q_INVOKABLE void removeAccount(); diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 121b350..6e85d75 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1016,21 +1016,6 @@ void FloweePay::removeWallet(Wallet *wallet) emit walletsChanged(); } -void FloweePay::addWallet(Wallet *wallet) -{ - assert(wallet); - assert(wallet->segment()); - assert(!m_wallets.contains(wallet)); - auto *dl = m_downloadManager.get(); - assert(dl); // should be initialized by now. - dl->addDataListener(wallet); - dl->addHeaderListener(wallet); - dl->connectionManager().addPrivacySegment(wallet->segment()); - - m_wallets.append(wallet); - emit walletsChanged(); -} - bool FloweePay::skinFollowsPlatform() const { return m_skinFollowsPlatform; diff --git a/src/FloweePay.h b/src/FloweePay.h index c189941..ded76a2 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -412,7 +412,6 @@ public: /// removes wallet from the list of wallets this app knows about. void removeWallet(Wallet *wallet); - void addWallet(Wallet *wallet); // return the app-wide network access manager. QNetworkAccessManager* network(); -- 2.54.0 From 07c7344e453404fd991a9f02869d7c0f4b18a3d0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 14 Feb 2025 18:18:00 +0100 Subject: [PATCH 1197/1428] Improve layout of "waiting for block" text. --- guis/mobile/TransactionInfoSmall.qml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 6c4f37c..6298cab 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -54,10 +54,8 @@ ColumnLayout { id: minedLabel visible: { var h = root.minedHeight; - if (h == -2) - return false; if (h <= 0 ) - return true; + return false; var boringTime = Pay.formatDateTime(model.date); return boringTime !== minedDateLabel.text } @@ -69,10 +67,8 @@ ColumnLayout { visible: minedLabel.visible text: { var h = root.minedHeight; - if (h <= 0) { - if (root.minedHeight !== -2) // is not rejected - return qsTr("Waiting for block"); - } + if (h <= 0) + return ""; var rc = Pay.formatBlockTime(h); var confirmations = Pay.headerChainHeight - h + 1; if (confirmations > 0 && confirmations < 20) @@ -80,6 +76,16 @@ ColumnLayout { return rc; } } + Flowee.Label { + visible: text !== "" + Layout.columnSpan: 2 + text: { + var h = root.minedHeight; + if (h <= 0 && h !== -2) + return qsTr("Waiting for block"); + return ""; + } + } Flowee.Label { id: paymentTypeLabel Layout.columnSpan: isMoved ? 2 : 1 -- 2.54.0 From 55449ffe75d3d9bad38902701cf528bd4b699d75 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 14 Feb 2025 18:44:09 +0100 Subject: [PATCH 1198/1428] Fix positioning of popups The popup component adds hidden margins and insets, which we have to fight to not end up with arbitary positioning. This fixes the positioning and sizes much better. --- guis/mobile/PopupOverlay.qml | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index aae09e3..37461e2 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -57,8 +57,8 @@ FocusScope { id: thePopup width: parent.width height: 100 - leftInset: 0 - rightInset: 0 + leftInset: -2 + rightInset: -2 topInset: 0 bottomInset: 0 modal: true @@ -72,9 +72,18 @@ FocusScope { root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } background: Rectangle { - color: palette.light - radius: 5 + color: palette.base } + + Rectangle { + color: palette.light + border.color: palette.midlight + border.width: 1 + radius: 5 + anchors.fill: loader + anchors.margins: -10 // the popup imposes this border on us, we take it back + } + Loader { id: loader width: parent.width @@ -87,16 +96,16 @@ FocusScope { if (item == null) return; var h = loader.item.implicitHeight - thePopup.height = h + 20; // 20 is the top and bottom inset of the popup + thePopup.height = h + 12; // 12 is for top and bottom inset of the popup var rect = thePopup.sourceRect; if (overlayLoader.item) { // the overlay is supposed to be at the same position as the sourceRect var h2 = overlayLoader.height thePopup.height += h2 + 10; if (root.height - rect.bottom >= h) { // fits below - thePopup.y = rect.bottom - h2 - 12; - overlayLoader.y = 0 - loader.y = h2 + 10; + thePopup.y = rect.bottom - h2; + overlayLoader.y = -12 + loader.y = h2; } else if (h < rect.y) { // fits above thePopup.y = rect.y - h - 22; @@ -120,15 +129,8 @@ FocusScope { else thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps } - Rectangle { - color: "#00000000" - border.color: palette.midlight - border.width: 1 - radius: 5 - anchors.fill: parent - anchors.margins: -10 - } } + Loader { // use offsets to counter the popup insets width: parent.width + 40 -- 2.54.0 From 9605f2556c42ec3e05ba03e6404ea847c7c51d3c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:05:45 +0100 Subject: [PATCH 1199/1428] Cleanup startup screen This reflects that it is a full screen page and has no 'back' button. We also remove lots of small hacks that have seens been made standard in reusable components. --- guis/Flowee/QRWidget.qml | 5 +- guis/mobile/StartupScreen.qml | 117 ++++++++++++++-------------------- guis/mobile/TextButton.qml | 10 ++- 3 files changed, 58 insertions(+), 74 deletions(-) diff --git a/guis/Flowee/QRWidget.qml b/guis/Flowee/QRWidget.qml index 51417c0..c991cda 100644 --- a/guis/Flowee/QRWidget.qml +++ b/guis/Flowee/QRWidget.qml @@ -32,7 +32,7 @@ Item { function handleOnClicked() { Pay.copyToClipboard(qrText); // invert the feedback so a second tap removes the feedback again. - clipboardFeedback.opacity = clipboardFeedback.opacity == 0 ? 1 : 0 + clipboardFeedback.opacity = clipboardFeedback.opacity === 0 ? 1 : 0 } Image { @@ -60,13 +60,12 @@ Item { id: addressLine width: parent.width height: addressLabel.height + 10 - anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom radius: 6 color: palette.base Label { id: addressLabel - anchors.centerIn: parent + y: 5 width: parent.width text: { var address = root.qrText diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index fd4b68f..a6da20e 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -22,19 +22,7 @@ import Flowee.org.pay Page { id: root - headerText: qsTr("Welcome!") - headerButtonVisible: true - headerButtonText: qsTr("Continue") - onHeaderButtonClicked: { - if (!portfolio.current.isUserOwned) { - request.clear(); // to make the QR here the same as there - var mainView = thePile.get(0); - mainView.currentIndex = 2 // go to the receive-tab - } - - thePile.pop(); - } - + hideHeader: true Item { // data Connections { @@ -52,33 +40,38 @@ Page { } onActiveFocusChanged: request.start(); - Flickable { + Rectangle { + id: logo width: parent.width + 20 x: -10 - height: parent.height + 20 + height: 80 + color: mainWindow.floweeBlue + + Image { + id: img + source: "qrc:/FloweePay-light.svg" + // ratio: 449 / 77 + width: parent.width - 20 + height: width / 449 * 77 + x: 10 + y: 8 + } + } + + Flickable { + id: bla + y: logo.height + width: parent.width + height: parent.height - y contentWidth: width contentHeight: column.height + 20 clip: true Column { id: column + y: 10 width: parent.width spacing: 10 - Rectangle { - width: parent.width - height: 80 - color: mainWindow.floweeBlue - - Image { - id: img - source: "qrc:/FloweePay-light.svg" - // ratio: 449 / 77 - width: parent.width - 20 - height: width / 449 * 77 - x: 10 - y: 8 - } - } Flowee.Label { text: qsTr("Moving the world towards a Bitcoin\u00a0Cash economy") wrapMode: Text.WordWrap @@ -89,6 +82,21 @@ Page { font.pixelSize: root.font.pixelSize * 1.2 } + TextButton { + text: qsTr("Continue") + horizontalAlignment: Text.AlignRight + pageButton: true + onClicked: { + if (!portfolio.current.isUserOwned) { + request.clear(); // to make the QR here the same as there + var mainView = thePile.get(0); + mainView.currentIndex = 2 // go to the receive-tab + } + + thePile.pop(); + } + } + Item { width: 1; height: 10 } // spacer GridLayout { @@ -103,7 +111,6 @@ Page { anchors.horizontalCenter: parent.horizontalCenter Rectangle { - id: logo height: 120 width: 120 radius: 26 @@ -149,6 +156,7 @@ Page { TextButton { text: qsTr("All Videos") visible: parent.infoObject !== null + horizontalAlignment: Text.AlignHCenter Layout.maximumWidth: 140 pageButton: true onClicked: thePile.push(parent.infoObject.qml); @@ -157,27 +165,13 @@ Page { Item { width: 1; height: 10 } // spacer - Rectangle { - radius: 20 - height:button2Text.height + 20 - width: button2Text.width + 40 - color: "#178b3a" + Flowee.BigButton { anchors.horizontalCenter: parent.horizontalCenter property QtObject infoObject: ModuleManager.sectionOnPlugin("sendSweepModule", "main"); - - visible: infoObject !== null - - Flowee.Label { - id: button2Text - color: "white" - text: qsTr("Claim a Cash Stamp") - anchors.centerIn: parent - } - MouseArea { - anchors.fill: parent - onClicked: thePile.replace(parent.infoObject.qml); - } + text: qsTr("Claim a Cash Stamp") + onClicked: thePile.replace(parent.infoObject.qml); + isMainButton: true } Item { width: 1; height: 5 } // spacer @@ -211,8 +205,7 @@ Page { x: column.width / 10 } Flowee.QRWidget { - width: parent.width - 20 - x: 10 + width: parent.width qrText: request.qr } @@ -238,26 +231,12 @@ Page { Item { width: 1; height: 5 } // spacer - Rectangle { - radius: 20 - height:buttonText.height + 20 - width: buttonText.width + 40 - color: "#178b3a" + Flowee.BigButton { anchors.horizontalCenter: parent.horizontalCenter - - Flowee.Label { - id: buttonText - color: "white" - text: qsTr("Add a different wallet") - anchors.centerIn: parent - } - MouseArea { - anchors.fill: parent - onClicked: thePile.replace("./NewAccount.qml"); - } + text: qsTr("Add a different wallet") + onClicked: thePile.replace("./NewAccount.qml"); + isMainButton: true } - - Item { width: 1; height: 40 } // spacer } } } diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index eeb719e..573a7bf 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -30,6 +30,7 @@ Item { signal clicked property alias text: label.text + property alias horizontalAlignment: label.horizontalAlignment property alias subtext: smallLabel.text /// When true, show an arrow indicating we open a new page on click property bool pageButton: false @@ -56,8 +57,13 @@ Item { id: label y: 10 x: icon.visible ? 38 : 0 - width: parent.width - x - wrapMode: Text.WordWrap + width: { + var w = parent.width - x + if (horizontalAlignment === Text.AlignRight) + w = w - rightIcon.width - 10 + return w; + } + elide: Text.ElideRight color: enabled ? palette.windowText : palette.brightText } Rectangle { -- 2.54.0 From 018df80441bb01fbe6f65f54d22cfafae2b3d8c0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:06:19 +0100 Subject: [PATCH 1200/1428] Add top spacing. --- guis/mobile/ImportWalletPage.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index f305e8d..654aae3 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -147,6 +147,7 @@ Page { Column { id: entryPage width: parent.width + y: 10 spacing: 20 property var typedData: Pay.identifyString(secretText.totalText) @@ -281,7 +282,8 @@ Page { id: privKeydetailsPage x: width + 10 width: parent.width - height: parent.height + height: parent.height - 20 + y: 10 function takeFocus() { singleAddress.forceActiveFocus(); @@ -425,8 +427,9 @@ Page { Column { id: seedDetailsPage x: width + 10 + y: 10 width: parent.width - height: parent.height + height: parent.height - 20 spacing: 10 function takeFocus() { -- 2.54.0 From ca50e3978cd2197c6bf14936a8ecd2dba5019ecb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:09:44 +0100 Subject: [PATCH 1201/1428] Plugin into the right module section for sweeping. --- guis/mobile/StartupScreen.qml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/guis/mobile/StartupScreen.qml b/guis/mobile/StartupScreen.qml index a6da20e..4f91077 100644 --- a/guis/mobile/StartupScreen.qml +++ b/guis/mobile/StartupScreen.qml @@ -167,10 +167,9 @@ Page { Flowee.BigButton { anchors.horizontalCenter: parent.horizontalCenter - property QtObject infoObject: - ModuleManager.sectionOnPlugin("sendSweepModule", "main"); + property QtObject infoObject: ModuleManager.moduleSection("sendSweepModule"); text: qsTr("Claim a Cash Stamp") - onClicked: thePile.replace(parent.infoObject.qml); + onClicked: thePile.replace(infoObject.qml); isMainButton: true } -- 2.54.0 From 99c0b20171040285e6f2efee356e845f1f915592 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:17:03 +0100 Subject: [PATCH 1202/1428] Add even/odd background colors to the social feed. --- guis/ControlColors.js | 2 +- modules/social-feed/Listing.qml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index 33840ce..1430d3f 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -56,7 +56,7 @@ function applyDarkSkin(item) { } function applyLightSkin(item) { item.palette.window = "#e0dfde"; - item.palette.base = "#e8e7e6"; + item.palette.base = "#f0efee"; item.palette.alternateBase = "#e2e1e0"; item.palette.light = "#dddcdb"; item.palette.dark = "#353637"; diff --git a/modules/social-feed/Listing.qml b/modules/social-feed/Listing.qml index a09f0e2..82ac88e 100644 --- a/modules/social-feed/Listing.qml +++ b/modules/social-feed/Listing.qml @@ -39,6 +39,13 @@ Page { width: ListView.view.width height: content.height + 10 + Rectangle { + color: (index % 2 === 0) ? palette.base : palette.alternateBase + width: parent.width + 20 + x: -10 + height: parent.height + } + Column { id: content width: parent.width -- 2.54.0 From 43917e57b86a2b4c970c23f04bad7745fdfe40e3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:28:19 +0100 Subject: [PATCH 1203/1428] Make list items more listy --- guis/Flowee/CardTypeSelector.qml | 39 ++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index a5caaeb..0821071 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -54,7 +54,8 @@ Item { Label { id: name - width: root.width + width: root.width - 20 + x: 10 fontSizeMode: Text.HorizontalFit horizontalAlignment: Text.AlignHCenter font.pixelSize: mainWindow.font.pixelSize * 1.3 @@ -70,12 +71,36 @@ Item { Repeater { model: root.features - Label { - text: modelData - anchors.horizontalCenter: parent.horizontalCenter - width: Math.min(implicitWidth, contentArea.width - 50) - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - horizontalAlignment: Text.AlignHCenter + Item { + width: contentArea.width + height: listItemLabel.height + Label { + id: listItemLabel + text: modelData + width: Math.min(contentWidth, parent.width - 50) + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + // horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + } + Rectangle { + id: left + anchors.verticalCenter: parent.verticalCenter + anchors.right: listItemLabel.left + anchors.rightMargin: 10 + width: 6 + height: 6 + rotation: 45 + color: mainWindow.floweeSalmon + } + Rectangle { + anchors.top: left.top + anchors.left: listItemLabel.right + anchors.leftMargin: 10 + width: 6 + height: 6 + rotation: 45 + color: mainWindow.floweeSalmon + } } } } -- 2.54.0 From ad93f235f8983f2c4eec9024deb99701d062535f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:34:02 +0100 Subject: [PATCH 1204/1428] Minor UX improvements. This reuses the same text for the same button here as in the 'send' tab, and fixes the spacing a litte. --- guis/mobile/ImportWalletPage.qml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 654aae3..ef2f750 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -160,7 +160,7 @@ Page { width: parent.width Item { width: parent.width - height: scanButton.height + height: scanButton.height + 20 Flowee.ImageButton { id: scanButton source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); @@ -168,7 +168,8 @@ Page { iconSize: Math.min(entryPage.width / 4, 100) x: (parent.width - width) / 2 // while NFC is not enabled.. // x: (parent.width - width * 2 - 20) / 2 - text: qsTr("Camera") + y: 10 + text: qsTr("Scan QR") } QRScanner { id: scanner @@ -189,6 +190,7 @@ Page { border.color: "yellow" border.width: 2 x: (parent.width - width * 2 - 20) / 2 + width + 20 + y: 10 Flowee.Label { anchors.centerIn: parent -- 2.54.0 From 23c5f77a4805ef76d310f1ed26819e8ff1541018 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:37:44 +0100 Subject: [PATCH 1205/1428] Properly center the percentage text vertically --- guis/Flowee/Progressbar.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/Progressbar.qml b/guis/Flowee/Progressbar.qml index 178874e..84f593a 100644 --- a/guis/Flowee/Progressbar.qml +++ b/guis/Flowee/Progressbar.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,7 +30,7 @@ Item { Label { id: percentLabel text: (100 * root.progress).toFixed(0) + "%" - y: 5 + y: (parent.height - contentHeight) / 2 x: root.width / 2 - width / 2 } @@ -60,7 +60,7 @@ Item { } Label { text: percentLabel.text - y: 5 + y: (parent.height - contentHeight) / 2 x: root.width / 2 - width / 2 color: palette.highlightedText } -- 2.54.0 From bd977e9ad184ed212bbe9125c8d6550d20fc92bb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 12:44:23 +0100 Subject: [PATCH 1206/1428] Handle long comments better. --- guis/mobile/EditableLabel.qml | 3 +-- guis/mobile/TransactionListItem.qml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/mobile/EditableLabel.qml b/guis/mobile/EditableLabel.qml index c8d53c9..8adb86e 100644 --- a/guis/mobile/EditableLabel.qml +++ b/guis/mobile/EditableLabel.qml @@ -20,8 +20,7 @@ import "../Flowee" as Flowee Item { id: root - implicitHeight: editWidget.implicitHeight - height: editWidget.height + implicitHeight: Math.max(editWidget.implicitHeight, ourLabel.contentHeight) property alias text: ourLabel.text property bool editable: true baselineOffset: editWidget.baselineOffset diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 1be6980..a6cad0f 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -80,6 +80,7 @@ Item { return price.y + price.baselineOffset - baselineOffset return parent.height / 2 - height } + elide: Text.ElideRight Component.onCompleted: fetchText(); function fetchText() { -- 2.54.0 From e80eea83f96432d3b1974993b33dc68a9ae5a95c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 13:02:01 +0100 Subject: [PATCH 1207/1428] Allow user to archive the initial wallet. An often heard request. --- guis/mobile/AccountPageListItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 1fc05b4..4c40258 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -372,7 +372,7 @@ QQC2.Control { height: archiveButtonText.height + 20 color: Pay.useDarkSkin ? "#b39554" : "#e5be6b" width: archiveButtonText.width + 30 - visible: !singleAccountSetup && root.account.isUserOwned + visible: !singleAccountSetup radius: 3 Flowee.Label { -- 2.54.0 From 6c3af0f3c1d9dfefc333f36d0d277fd3ed152cac Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 15:05:41 +0100 Subject: [PATCH 1208/1428] Avoid number overflow issues The backend still used an int for the fiat price, this is now a qint64. Additionally the BitcoinValue now optimistically (pessimistically?) converts the input with the current price in order to avoid a second order overflow. --- guis/mobile/ReceiveTab.qml | 2 ++ src/BitcoinValue.cpp | 9 ++++++++- src/PaymentBackend.cpp | 10 +++++----- src/PaymentBackend.h | 10 +++++----- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 3761768..b191dd0 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -149,6 +149,7 @@ FocusScope { Item { function doFocus() { } + clip: true Column { width: parent.width - 20 x: 10 @@ -233,6 +234,7 @@ FocusScope { } Item { + clip: true function doFocus() { description.forceActiveFocus(); } Column { width: parent.width - 20 diff --git a/src/BitcoinValue.cpp b/src/BitcoinValue.cpp index a31d5fd..5d30dcd 100644 --- a/src/BitcoinValue.cpp +++ b/src/BitcoinValue.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -195,6 +195,13 @@ bool BitcoinValue::setStringValue(const QString &value) if (newVal > MaxMoney) return false; } + else { // assume the value will be converted to bch which should also not overflow + qint64 sats = newVal * FloweePay::instance()->prices()->price(); + test = sats; + test2 = test; // convert back to int + if (test2 != static_cast(sats)) // we lose on conversion, then the value is too big. + return false; + } setValue(newVal, UserInput); return true; } diff --git a/src/PaymentBackend.cpp b/src/PaymentBackend.cpp index 572e903..345596d 100644 --- a/src/PaymentBackend.cpp +++ b/src/PaymentBackend.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ void PaymentBackend::setPaymentAmount(double amount) { if (amount < 0) return; - auto priceInFiat = std::round(FloweePay::instance()->prices()->price() * amount / 1E8); + qint64 priceInFiat = std::round(FloweePay::instance()->prices()->price() * amount / 1E8); if (qFuzzyCompare(m_paymentAmount, amount) && m_paymentAmountFiat == priceInFiat) return; m_paymentAmount = amount; @@ -42,16 +42,16 @@ void PaymentBackend::setPaymentAmount(double amount) emit amountChanged(); } -int PaymentBackend::paymentAmountFiat() const +qint64 PaymentBackend::paymentAmountFiat() const { return m_paymentAmountFiat; } -void PaymentBackend::setPaymentAmountFiat(int amount) +void PaymentBackend::setPaymentAmountFiat(qint64 amount) { if (amount < 0) return; - auto satsAmount = amount * 1E8 / FloweePay::instance()->prices()->price(); + double satsAmount = amount * 1E8 / FloweePay::instance()->prices()->price(); if (m_paymentAmountFiat == amount && m_paymentAmount == satsAmount) return; m_paymentAmountFiat = amount; diff --git a/src/PaymentBackend.h b/src/PaymentBackend.h index d3d1682..d494f93 100644 --- a/src/PaymentBackend.h +++ b/src/PaymentBackend.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,22 +26,22 @@ class PaymentBackend : public QObject /// The payment-wide amount of funds. Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY amountChanged) /// The payment-wide amount of funds in globally set fiat. - Q_PROPERTY(int paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) + Q_PROPERTY(qint64 paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY amountChanged) public: explicit PaymentBackend(QObject *parent = nullptr); double paymentAmount() const; void setPaymentAmount(double amount); - int paymentAmountFiat() const; - void setPaymentAmountFiat(int amount); + qint64 paymentAmountFiat() const; + void setPaymentAmountFiat(qint64 amount); signals: void amountChanged(); private: double m_paymentAmount = 0; - int m_paymentAmountFiat = 0; + qint64 m_paymentAmountFiat = 0; }; #endif -- 2.54.0 From 3ee266324179697214abd1e5d7ff68e5d5cafa57 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 16:23:24 +0100 Subject: [PATCH 1209/1428] Make broadcastfeedback work on instaPay payments too. --- guis/mobile/PayWithQR.qml | 16 +++++----------- modules/build-transaction/PayToOthers.qml | 7 +++++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 53676e2..3b7373c 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -88,12 +88,10 @@ Page { fiatPrice: Fiat.price autoPrepare: true instaPay: true - - // easier testing values (for when you don't have a camera) - // ps. in case of testing, you want above instaPay: property => false!! - // paymentAmount: 100000000 - // targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45" - // userComment: "bla bla bla" + onApprovedByUser: { + root.hideHeader = true; + broadcastPage.start(); + } } Flowee.Dialog { id: scannedUrlFaultyDialog @@ -331,11 +329,7 @@ Page { anchors.bottomMargin: 10 width: parent.width enabled: payment.isValid && payment.txPrepared - onActivated: { - payment.markUserApproved() - root.hideHeader = true; - broadcastPage.start(); - } + onActivated: payment.markUserApproved() visible: payment.account.isDecrypted || !payment.account.needsPinToPay } diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 0036683..10d52a2 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -52,6 +52,11 @@ Page { id: payment account: portfolio.current fiatPrice: Fiat.price + + onApprovedByUser: { + thePile.currentItem.hideHeader = true; + broadcastPage.start(); + } } Flowee.Dialog { id: errorDialog @@ -156,8 +161,6 @@ Page { onActivated: { payment.markUserApproved() thePile.pop(); // the broadcast feedback is on the main screen. - thePile.currentItem.hideHeader = true; - broadcastPage.start(); } } } -- 2.54.0 From 745199f3b9ad7cfbb750e08d3c53a56606b9f102 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 17 Feb 2025 22:34:53 +0100 Subject: [PATCH 1210/1428] Update to the latest Qt: 6.8.2 --- android/build-pay.sh | 2 +- android/docker/Dockerfile | 4 ++-- android/docker/build-docker.sh | 2 +- android/docker/scripts/buildBoost.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index b080564..f4e2a0e 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="codeberg.org/flowee/buildenv-android:v6.8.1" + _docker_name_="bitcoincashcode.org/flowee/buildenv-android:v6.8.2" fi mkdir -p imports diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 58ee0b9..dbb6607 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,8 +1,8 @@ -ARG QtVersion=v6.8.1 +ARG QtVersion=v6.8.2 FROM archlinux:latest ARG QtVersion -LABEL maintainer="Tom Zander " +LABEL maintainer="Tom Flowee " LABEL qtversion="${QtVersion}" ENTRYPOINT ["su", "-", "builduser", "-c"] diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index dc7023f..309ae62 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.8.1 +QtVersion=v6.8.2 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index 5f5babe..d8dea5e 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -8,7 +8,7 @@ VER2=`echo $VERSION|sed -e 's#\.#_#g'` cd /usr/local/cache if ! test -f boost_$VER2.tar.bz2; then - wget https://boostorg.jfrog.io/artifactory/main/release/$VERSION/source/boost_$VER2.tar.bz2 + wget https://archives.boost.io/release/$VERSION/source/boost_$VER2.tar.bz2 fi cd ~builduser -- 2.54.0 From aa0810faeb3954cc5eeea6b72714d43ef316ddb0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 19 Feb 2025 20:01:44 +0100 Subject: [PATCH 1211/1428] A new wallet doesn't need updating. --- guis/desktop/AccountListItem.qml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 29be615..2025b88 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -50,8 +50,16 @@ Item { Timer { id: startTimer property int wavy: 0 - running: !root.account.isArchived && !Pay.offline - && root.account.lastBlockSynched === root.startPos; + running: { + if (root.account.isArchived || Pay.offline) + return false; + if (root.account.lastBlockSynched === root.startPos) { + if (Pay.chainHeight === root.startPos) // fresh new wallet + return false; + return true; + } + return false; + } interval: 300 repeat: true onTriggered: { -- 2.54.0 From af4fd25810c129b145b035b5401ceba559adfc95 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 22 Feb 2025 17:01:40 +0100 Subject: [PATCH 1212/1428] import translations from crowdin --- translations/floweepay-common_de.ts | 123 +++++---- translations/floweepay-common_nl.ts | 119 ++++----- translations/floweepay-desktop_de.ts | 263 ++++++++++--------- translations/floweepay-desktop_nl.ts | 239 +++++++++-------- translations/floweepay-mobile_de.ts | 268 ++++++++++---------- translations/floweepay-mobile_nl.ts | 264 ++++++++++--------- translations/module-build-transaction_de.ts | 95 +++---- translations/module-build-transaction_nl.ts | 89 ++++--- translations/module-send-sweep_de.ts | 31 --- translations/module-send-sweep_nl.ts | 31 --- 10 files changed, 765 insertions(+), 757 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 00178f9..9e435f7 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -76,53 +76,47 @@ selbst + + BigCloseButton + + + Close + Schließen + + BroadcastFeedback - + Sending Payment Sende Zahlung - + Payment Sent Zahlung gesendet - + Failed Fehlgeschlagen - + Transaction rejected by network Transaktion wurde vom Netzwerk abgelehnt - - Payment has been sent to: - Zahlung wurde gesendet an: + + The payment has been sent to: + Followed by the address + Die Zahlung wurde gesendet an: - - Copied address to clipboard - Adresse in Zwischenablage kopiert - - - - Opening Website - Öffne Website - - - + Add a personal note Eine persönliche Notiz hinzufügen - - - Close - Schließen - CFIcon @@ -148,30 +142,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -180,13 +174,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -234,35 +228,45 @@ NotificationManager - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash Block abgebaut. Höhe: %1 + + %1 new transactions across %2 wallets found (%3) + %1 new transactions across %2 wallets found (%3) - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) Block abgebaut: %1 + + A payment of %1 has been sent + A payment of %1 has been sent + + + + %1 new transactions found (%2) + + %1 neue Transaktion gefunden (%2) + %1 neue Transaktionen gefunden (%2) + NotificationManagerPrivate - + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash Block abgebaut. Höhe: %1 - + + tBCH (testnet4) block mined: %1 - tBCH (testnet4) Block abgebaut: %1 + tBCH (testnet4) Block erstellt: %1 - + Mute Stummschalten - + New Transactions dialog-title @@ -270,24 +274,6 @@ Neue Transaktionen - - - %1 new transactions across %2 wallets found (%3) - %1 neue Transaktionen über %2 Wallets gefunden (%3) - - - - A payment of %1 has been sent - Eine Zahlung von %1 wurde gesendet - - - - %1 new transactions found (%2) - - %1 neue Transaktionen gefunden (%2) - %1 neue Transaktionen gefunden (%2) - - Payment @@ -366,6 +352,11 @@ TextField + Copy + Kopieren + + + Paste Einfügen @@ -445,27 +436,27 @@ WalletHistoryModel - + Today Heute - + Yesterday Gestern - + Earlier this week Früher in dieser Woche - + This week Diese Woche - + Earlier this month Früher in diesem Monat diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index ee0a5f9..118bf4f 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -76,53 +76,47 @@ zelf + + BigCloseButton + + + Close + Sluiten + + BroadcastFeedback - + Sending Payment Betaling wordt verzonden - + Payment Sent Betaling Verzonden - + Failed Mislukt - + Transaction rejected by network Transactie afgewezen door het netwerk - - Payment has been sent to: + + The payment has been sent to: + Followed by the address Betaling is verzonden naar: - - Copied address to clipboard - Adres gekopieerd naar klembord - - - - Opening Website - Open Website - - - + Add a personal note Voeg een persoonlijke notitie toe - - - Close - Sluiten - CFIcon @@ -148,30 +142,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -180,13 +174,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -234,35 +228,45 @@ NotificationManager - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash-blok gemijnd. Hoogte: %1 + + %1 new transactions across %2 wallets found (%3) + %1 nieuwe transacties in %2 portemonnees gevonden (%3) - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) blok gemijnd: %1 + + A payment of %1 has been sent + Een betaling van %1 is verzonden + + + + %1 new transactions found (%2) + + %1 nieuwe transactie gevonden (%2) + %1 nieuwe transacties gevonden (%2) + NotificationManagerPrivate - + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash-blok gemijnd. Hoogte: %1 - + + tBCH (testnet4) block mined: %1 tBCH (testnet4) blok gemijnd: %1 - + Mute Negeren - + New Transactions dialog-title @@ -270,24 +274,6 @@ Nieuwe transacties - - - %1 new transactions across %2 wallets found (%3) - %1 nieuwe transacties in %2 portemonnees gevonden (%3) - - - - A payment of %1 has been sent - Een betaling van %1 is verzonden - - - - %1 new transactions found (%2) - - %1 nieuwe transactie gevonden (%2) - %1 nieuwe transacties gevonden (%2) - - Payment @@ -366,6 +352,11 @@ TextField + Copy + Kopieer + + + Paste Plak @@ -445,27 +436,27 @@ WalletHistoryModel - + Today Vandaag - + Yesterday Gisteren - + Earlier this week Eerder deze week - + This week Deze week - + Earlier this month Eerder deze maand diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index c7113cd..932df36 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -49,108 +49,113 @@ AccountDetails - + Wallet Details Geldbörsen Details - + Name Name - + Sync Status Sync Status - + Encryption Verschlüsselung - - + + Password Passwort - + Open Öffnen - + Invalid PIN Ungültige PIN - + Include balance in total Inkludiere Guthaben in Gesamtsumme - + Hide in private mode Im privaten Modus ausblenden - + Address List Adressliste - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. + Wechselt zwischen Adressen, auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen, die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses - Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen + Wechselt zwischen ungenutzten und genutzten Bitcoin-Adressen - + Backup details Backup-Details - + Seed-phrase Seed-Phrase - + + Copy + Kopieren + + + Seed format Seed-Format - + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -158,11 +163,31 @@ AccountListItem - + Last Transaction Letzte Transaktion + + ActivityConfigBar + + + Filter + Filter + + + + + Received + Erhalten + + + + + Sent + Gesendet + + AddressDbStats @@ -331,7 +356,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss OR - OR + ODER @@ -340,81 +365,81 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussGeheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Address to import Zu importierende Adresse - - + + New Wallet Name - Neuer Geldbörsen Name + Name der neuen Geldbörse - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + Start Start - + Discover Details online check for wallet details - Entdecke Details + Finde Details - + Derivation Ableitung - + Nothing found for seed. Does it have a password? Nichts für Seed gefunden. Hat es ein Passwort? - + Password Passwort - + imported wallet password - importiertes Geldbörsen-Passwort + Passwort der zu importierenden Geldbörse @@ -445,7 +470,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Import Existing Wallet - Import einer vorhandenen Geldbörse + Vorhandene Geldbörse importieren @@ -466,7 +491,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Private keys based Property of a wallet - Private Schlüssel basiert + Basiert auf privaten Schlüsseln @@ -491,7 +516,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Coin Selector - Coin Selektor + Coin Auswahl @@ -514,7 +539,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Share your QR code or copy address to receive - Teilen Sie Ihren QR-Code oder kopieren Sie Ihre Adresse zum Empfang + Teilen Sie Ihren QR-Code oder kopieren Sie Ihre Empfangsadresse @@ -592,7 +617,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Add Destination - Ziel hinzufügen + Empfänger hinzufügen @@ -606,14 +631,14 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Warning Warnung Payment request warnings: - Warnungen für Zahlungsanforderungen: + Warnungen für Zahlungsanforderung: @@ -662,65 +687,65 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Senden - + Destination Ziel - + Max available The maximum balance available Max. verfügbar - + %1 to %2 summary text to pay X-euro to address M %1 zu %2 - + Enter Bitcoin Cash Address Geben Sie eine Bitcoin Cash Adresse ein - - + + Copy Address Adresse kopieren - + Amount Betrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + Continue Fortfahren - + Cancel Abbrechen - + Coin Selector Coin Selektor - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -729,78 +754,78 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Total Number of coins Gesamt - + Needed Benötigt - + Selected Ausgewählt - + Value Wert - + Locked coins will never be used for payments. Right-click for menu. Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin Coin entsperren - + Lock coin Coin sperren - + Public-comment Öffentlicher Kommentar - + Add a comment you want to include in the transaction, visible for everyone. Fügen Sie einen Kommentar hinzu, den Sie in die Transaktion einfügen möchten, sichtbar für alle. - + Custom message, to be included in the transaction. Benutzerdefinierte Nachricht, die in die Transaktion aufgenommen werden soll. - + Text Text - + Size Größe @@ -813,67 +838,67 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Einstellungen - + Unit Einheit - + Show Bitcoin Cash value on Activity page Bitcoin Cash Wert auf der Aktivitätsseite anzeigen - + Show Block Notifications Zeige Blockbenachrichtigungen - + When a new block is mined, Flowee Pay shows a desktop notification Wenn ein neuer Block erzeugt wird, zeigt Flowee Pay eine Desktop-Benachrichtigung - + Night Mode Nachtmodus - + Private Mode Privater Modus - + Hides private wallets while enabled Versteckt private Geldbörsen, wenn aktiviert - + Font sizing Schriftgröße - + Version Version - + Library Version Version der Bibliothek - + Synchronization Synchronisation - + Network Status Netzwerkstatus - + Address Stats Adressstatistik @@ -881,32 +906,32 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Transaction - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + rejected abgelehnt @@ -1211,90 +1236,90 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1303,12 +1328,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 6e5fc32..cdae812 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -49,108 +49,113 @@ AccountDetails - + Wallet Details Portemonnee Details - + Name Naam - + Sync Status Synchronisatie status - + Encryption Encryptie - - + + Password Wachtwoord - + Open Open - + Invalid PIN Ongeldige PIN - + Include balance in total Saldo opnemen in totaal - + Hide in private mode Verbergen in privémodus - + Address List Adreslijst - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Backup details Back-up details - + Seed-phrase Herstelzin - + + Copy + Kopieer + + + Seed format Herstelzin formaat - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -158,11 +163,31 @@ AccountListItem - + Last Transaction Laatste Transactie + + ActivityConfigBar + + + Filter + Filter + + + + + Received + Ontvangen + + + + + Sent + Verzonden + + AddressDbStats @@ -340,79 +365,79 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Address to import Te importeren adres - - + + New Wallet Name Nieuwe naam Portemonnee - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + Start Start - + Discover Details online check for wallet details Vind de details - + Derivation Derivatie - + Nothing found for seed. Does it have a password? Niets gevonden voor herstelzin. Behoeft het een wachtwoord? - + Password Wachtwoord - + imported wallet password Wachtwoord geïmporteerde portemonnee @@ -606,7 +631,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Warning Waarschuwing @@ -662,65 +687,65 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Verstuur - + Destination Bestemming - + Max available The maximum balance available Max. beschikbaar - + %1 to %2 summary text to pay X-euro to address M %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - - + + Copy Address Kopieer adres - + Amount Bedrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Cancel Afbreken - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -729,78 +754,78 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt - + Public-comment Publiek commentaar - + Add a comment you want to include in the transaction, visible for everyone. Voeg een opmerking toe die u wilt opnemen in de transactie, zichtbaar voor iedereen. - + Custom message, to be included in the transaction. Speciaal bericht dat wordt opgenomen in de transactie. - + Text Tekst - + Size Grootte @@ -813,67 +838,67 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Instellingen - + Unit Eenheid - + Show Bitcoin Cash value on Activity page Toon Bitcoin Cash waarde op de activiteitenpagina - + Show Block Notifications Toon blok notificaties - + When a new block is mined, Flowee Pay shows a desktop notification Wanneer een nieuw blok is gedolven, toont Flowee Pay een desktop notificatie - + Night Mode Nachtmodus - + Private Mode Privé modus - + Hides private wallets while enabled Verbergt privé portemonnees - + Font sizing Lettertypegrootte - + Version Versie - + Library Version Bibliotheek versie - + Synchronization Synchronisatie - + Network Status Netwerk Status - + Address Stats Adres statistieken @@ -881,32 +906,32 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Transaction - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + rejected afgewezen @@ -1211,90 +1236,90 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1303,12 +1328,12 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... - + QR-Scan QR-Scannen diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 24efafe..08fe5d3 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -26,8 +26,8 @@ - © 2020-2024 Tom Zander and contributors - ©️ 2020-2024 Tom Zander und Mitwirkende + © 2020-2025 Tom Zander and contributors + ©️ 2020-2025 Tom Zander und Mitwirkende @@ -48,17 +48,17 @@ AccountHistory - + Home Startseite - + Pay Bezahlen - + Receive Empfange @@ -81,120 +81,126 @@ Backup-Informationen - + Backup Details Backup-Details - + Wallet seed-phrase Geldbörsen-Seed-Phrase - + Password Passwort - + Seed format Seed-Format - - + + Starting Height height refers to block-height Starthöhe - + Derivation Path Ableitungspfad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + Wallet keys Geldbörsen-Schlüssel - + Show Index toggle to show numbers Index anzeigen - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses - Wechselt zwischen ungenutzten und benutzten Bitcoin-Adressen + Wechselt zwischen ungenutzten und genutzten Bitcoin-Adressen - + Addresses and keys Adressen und Schlüssel - + Sync Status Synchronisierungsstatus - + Hide balance in overviews Guthaben in Übersichten ausblenden - + Hide in private mode Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren - - Re-scan Chain - Chain neu scannen + + Really Delete? + Wirklich löschen? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Die Löschung der Geldbörse "%1" kann nicht rückgängig gemacht werden. + + + Remove Wallet Geldbörse entfernen @@ -361,7 +367,7 @@ OR - OR + ODER @@ -370,84 +376,84 @@ Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Next Weiter - + Address to import Zu importierende Adresse - - + + New Wallet Name Neuer Geldbörsen Name - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + Start Start - + Discover Details online check for wallet details Entdecke Details - + Derivation Path Ableitungspfad - + Nothing found for seed. Does it have a password? Nichts für Seed gefunden. Hat es ein Passwort? - + Password Passwort - + imported wallet password importiertes Geldbörsen-Passwort @@ -577,7 +583,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. MenuOverlay - + Add Wallet Geldbörse hinzufügen @@ -756,7 +762,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussZieladresse - + Unlock Wallet Geldbörse entsperren @@ -799,86 +805,69 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss ReceiveTab - + Receive Empfange - + Share this QR to receive Diesen QR teilen um zu empfangen - + Encrypted Wallet Verschlüsselte Geldbörse - + Import Running... Import läuft... - - + Description Beschreibung - - Amount - requested amount of coin - Betrag - - - + Address Bitcoin Cash address Adresse - + Clear Leeren - - + + Payment Seen Zahlung gesichtet - - Checking... - Überprüfe... - - - + High risk transaction Hochriskante Transaktion - + Partially Paid Teilweise bezahlt - + Payment Accepted Zahlung akzeptiert - + Payment Settled Zahlung abgeschlossen - - Instant payment failed. Wait for confirmation. (double spent proof received) - Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) - - - + Continue Fortfahren @@ -978,23 +967,33 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBewege die Welt in Richtung einer Bitcoin Cash Wirtschaft - + + Getting Started + Erste Schritte + + + + All Videos + Alle Videos + + + Claim a Cash Stamp Cash Stamp beanspruchen - - + + OR OR - + Scan to send to your wallet Scannen um an Ihre Geldbörse zu senden - + Add a different wallet Eine andere Geldbörse hinzufügen @@ -1002,42 +1001,42 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss TransactionDetails - + Transaction Details Transaktionsdetails - + Open in Explorer In Explorer öffnen - + Transaction Hash Transaktions-Hash - + First Seen Erstsichtung - + Rejected Abgelehnt - - Unconfirmed - Unbestätigt + + Waiting for block + Warte auf Block - + Mined at Abgebaut am - + %1 blocks ago %1 Block her @@ -1045,57 +1044,57 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - + Transaction comment Transaktionskommentar - + Size: %1 bytes Größe: %1 Bytes - + Coinbase Coinbase - + Fees paid Bezahlte Gebühren - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 Bytes - + Fused from my addresses Fusioniert von meinen Adressen - + Sent from my addresses Gesendet von meinen Adressen - + Sent from addresses Gesendet von den Adressen - + Fused into my addresses Fusioniert in meine Adressen - + Received at addresses Empfangen auf den Adressen - + Received at my addresses Empfangen auf meinen Adressen @@ -1113,12 +1112,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussVerarbeite - + Mined Gemined - + %1 blocks ago Confirmations @@ -1127,42 +1126,47 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss - + + Waiting for block + Warte auf Block + + + Miner Reward Miner Belohnung - + Fees Gebühren - + Received Erhalten - + Payment to self Zahlung an sich selbst - + Sent Gesendet - + Holds a token Hält ein Token - + Sent to Gesendet an - + Transaction Details Transaktionsdetails @@ -1170,45 +1174,53 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss TransactionListItem - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + Rejected Abgelehnt + + UnlockApplication + + + Quick Receive + Schnell empfangen + + UnlockWidget - + Enter your wallet passcode Geben Sie Ihren Geldbörsen-Zugangscode ein - + Open open wallet with PIN Öffnen diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 9441a5d..0a42bf8 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -26,8 +26,8 @@ - © 2020-2024 Tom Zander and contributors - © 2020-2024 Tom Zander en bijdragers + © 2020-2025 Tom Zander and contributors + © 2020-2025 Tom Zander en bijdragers @@ -48,17 +48,17 @@ AccountHistory - + Home Start - + Pay Betaal - + Receive Ontvang @@ -81,120 +81,126 @@ Back-up Informatie - + Backup Details Back-up gegevens - + Wallet seed-phrase Herstelzin opslaan - + Password Wachtwoord - + Seed format Herstelzin formaat - - + + Starting Height height refers to block-height Beginhoogte - + Derivation Path Derivatie pad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren - - Re-scan Chain - Keten herscannen + + Really Delete? + Echt verwijderen? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Verwijderen van portemonnee "%1" kan niet ongedaan worden gemaakt. + + + Remove Wallet Portemonnee verwijderen @@ -370,84 +376,84 @@ Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Next Volgende - + Address to import Te importeren adres - - + + New Wallet Name Nieuwe naam Portemonnee - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + Start Start - + Discover Details online check for wallet details Vind de details - + Derivation Path Derivatie pad - + Nothing found for seed. Does it have a password? Niets gevonden voor herstelzin. Behoeft het een wachtwoord? - + Password Wachtwoord - + imported wallet password Wachtwoord geïmporteerde portemonnee @@ -577,7 +583,7 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MenuOverlay - + Add Wallet Portemonnee toevoegen @@ -756,7 +762,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -799,86 +805,69 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt ReceiveTab - + Receive Ontvangen - + Share this QR to receive Betaler moet deze QR lezen - + Encrypted Wallet Versleutelde portemonnee - + Import Running... Bezig met importeren... - - + Description Omschrijving - - Amount - requested amount of coin - Bedrag - - - + Address Bitcoin Cash address Adres - + Clear Wissen - - + + Payment Seen Betaling gezien - - Checking... - Controleren... - - - + High risk transaction Transactie met hoog risico - + Partially Paid Deels betaald - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - - Instant payment failed. Wait for confirmation. (double spent proof received) - Directe betaling is mislukt. Wacht op bevestiging. (Double Spent Proof ontvangen) - - - + Continue Doorgaan @@ -978,23 +967,33 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt We lopen het pad naar een Bitcoin Cash economie - + + Getting Started + Om te beginnen + + + + All Videos + Alle video's + + + Claim a Cash Stamp Claim een Cash Stamp - - + + OR OF - + Scan to send to your wallet Scan om naar uw portemonnee te verzenden - + Add a different wallet Een andere portemonnee toevoegen @@ -1002,42 +1001,42 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionDetails - + Transaction Details Transactiedetails - + Open in Explorer Open in Verkenner - + Transaction Hash Transactie hash - + First Seen Eerst gezien - + Rejected Afgewezen - - Unconfirmed - Onbevestigd + + Waiting for block + Wachten op blok - + Mined at Gedolven in - + %1 blocks ago Meest recente blok @@ -1045,57 +1044,57 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + Transaction comment Transactie omschrijving - + Size: %1 bytes Grootte: %1 bytes - + Coinbase Coinbase - + Fees paid Betaalde kosten - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Mijn gefuseerde adressen - + Sent from my addresses Verzonden vanaf mijn adressen - + Sent from addresses Verzonden vanaf adressen - + Fused into my addresses Gefuseerd naar mijn adressen - + Received at addresses Ontvangen op adressen - + Received at my addresses Ontvangen op mijn adressen @@ -1113,12 +1112,12 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt In behandeling - + Mined Gedolven - + %1 blocks ago Confirmations @@ -1127,42 +1126,47 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt - + + Waiting for block + Wachten op blok + + + Miner Reward Miner Beloning - + Fees Kosten - + Received Ontvangen - + Payment to self Betaling aan uzelf - + Sent Verzonden - + Holds a token Heeft een token - + Sent to Verstuurd naar - + Transaction Details Transactiedetails @@ -1170,45 +1174,53 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionListItem - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + Rejected Afgewezen + + UnlockApplication + + + Quick Receive + Snel Ontvangen + + UnlockWidget - + Enter your wallet passcode Voer pincode voor portemonnee in - + Open open wallet with PIN Open diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index e7de5dd..6ae78d5 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -16,7 +16,7 @@ Build Transaction - Baue Transaktion + Transaktion erstellen @@ -24,6 +24,45 @@ Templates manuell auswählen + + DestinationEditPage + + + Edit Destination + Empfänger definieren + + + + Send All + all money in wallet + Alles senden + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Copy Address + Adresse kopieren + + + + Warning + Warnung + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? + + + + I am certain + Ich bin mir sicher + + PayToOthers @@ -35,7 +74,7 @@ Building Error error during build - Fehler beim Bauen + Fehler beim Erstellen @@ -45,9 +84,9 @@ - + Add Destination - Ziel hinzufügen + Empfänger hinzufügen @@ -97,76 +136,44 @@ %1 Sat/Byte - + Destination Ziel - + unset indication of desination not being set nicht eingestellt - + invalid address is not correct ungültig - - + Copy Address Adresse kopieren - - Edit Destination - Ziel bearbeiten - - - - Send All - all money in wallet - Alles senden - - - - Bitcoin Cash Address - Bitcoin Cash Adresse - - - - Warning - Warnung - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - - - - I am certain - Ich bin mir sicher - - - + Drag to Edit Ziehen zum Bearbeiten - + Drag to Delete Ziehen zum Löschen - + Unlock Wallet Geldbörse entsperren - + Prepare Payment... Zahlung wird vorbereitet... diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index 2d3be16..448f25f 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -24,6 +24,45 @@ Selecteer templates handmatig + + DestinationEditPage + + + Edit Destination + Bewerk Bestemming + + + + Send All + all money in wallet + Alles verzenden + + + + Bitcoin Cash Address + Bitcoin Cash-adres + + + + Copy Address + Kopieer adres + + + + Warning + Waarschuwing + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? + + + + I am certain + Ik weet het zeker + + PayToOthers @@ -45,7 +84,7 @@ - + Add Destination Voeg bestemming toe @@ -97,76 +136,44 @@ %1 sat/byte - + Destination Bestemming - + unset indication of desination not being set niet ingesteld - + invalid address is not correct ongeldig - - + Copy Address Kopieer adres - - Edit Destination - Bewerk Bestemming - - - - Send All - all money in wallet - Betaal alles - - - - Bitcoin Cash Address - Bitcoin Cash-adres - - - - Warning - Waarschuwing - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - - - - I am certain - Ik ben zeker - - - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - + Unlock Wallet Portemonnee ontgrendelen - + Prepare Payment... Betaling voorbereiden... diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index ab38e84..9e12444 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -46,37 +46,6 @@ Transfer to: Transferieren nach: - - - Sending Payment - Sende Zahlung - - - - Payment Sent - Zahlung gesendet - - - - Failed - Fehlgeschlagen - - - - Transaction rejected by network - Transaktion wurde vom Netzwerk abgelehnt - - - - The payment has been sent to: - Followed by the address - Die Zahlung wurde gesendet an: - - - - Close - Schließen - SendSweepModuleInfo diff --git a/translations/module-send-sweep_nl.ts b/translations/module-send-sweep_nl.ts index d5682da..e54d17a 100644 --- a/translations/module-send-sweep_nl.ts +++ b/translations/module-send-sweep_nl.ts @@ -46,37 +46,6 @@ Transfer to: Overmaken naar: - - - Sending Payment - Betaling wordt verzonden - - - - Payment Sent - Betaling Verzonden - - - - Failed - Mislukt - - - - Transaction rejected by network - Transactie afgewezen door het netwerk - - - - The payment has been sent to: - Followed by the address - Betaling is verzonden naar: - - - - Close - Sluiten - SendSweepModuleInfo -- 2.54.0 From e12fdc9035a1347ccc01ef3c059e2ab94b4b4a88 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 22 Feb 2025 17:10:00 +0100 Subject: [PATCH 1213/1428] Fixes in german translations --- translations/floweepay-common_de.ts | 4 ++-- translations/module-build-transaction_de.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 9e435f7..ddb4975 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -230,12 +230,12 @@ %1 new transactions across %2 wallets found (%3) - %1 new transactions across %2 wallets found (%3) + %1 neue Transaktionen über %2 Wallets gefunden (%3) A payment of %1 has been sent - A payment of %1 has been sent + Eine Zahlung von %1 wurde gesendet diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index 6ae78d5..d723178 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -40,7 +40,7 @@ Bitcoin Cash Address - Bitcoin Cash Address + Bitcoin Cash Adresse @@ -138,7 +138,7 @@ Destination - Ziel + Empfänger @@ -175,7 +175,7 @@ Prepare Payment... - Zahlung wird vorbereitet... + Zahlung vorbereiten... -- 2.54.0 From 8c8fbe226373157525fd520351819225e9a944c8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 18:22:22 +0100 Subject: [PATCH 1214/1428] Check for 'already running'. This adds a lock to ensure that the app is not started twice, which would be bad for data consistency. The second version will just show a simple window stating it is already running. While it would be nice to make it more advanced, I don't think that is useful to spent time and code on. --- guis/desktop.qrc | 1 + guis/desktop/locked.qml | 56 +++++++++++++++++++++++++++++++++++++++++ src/FloweePay.cpp | 17 ++++++++++--- src/FloweePay.h | 11 ++++++++ src/main.cpp | 27 +++++++++++--------- 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 guis/desktop/locked.qml diff --git a/guis/desktop.qrc b/guis/desktop.qrc index 84e5645..980097b 100644 --- a/guis/desktop.qrc +++ b/guis/desktop.qrc @@ -18,6 +18,7 @@ desktop/ConfigItem.qml ControlColors.js Utils.js + desktop/locked.qml desktop/main.qml desktop/AccountConfigMenu.qml desktop/AccountDetails.qml diff --git a/guis/desktop/locked.qml b/guis/desktop/locked.qml new file mode 100644 index 0000000..7d4d2dd --- /dev/null +++ b/guis/desktop/locked.qml @@ -0,0 +1,56 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../ControlColors.js" as ControlColors + +import QtQuick.Controls.Basic + +ApplicationWindow { + id: mainWindow + visible: true + width: Pay.windowWidth + height: Pay.windowHeight + minimumWidth: 240 + minimumHeight: 300 + title: "Flowee Pay" + + onVisibleChanged: if (visible) ControlColors.applySkin(mainWindow) + + Component.onCompleted: updateFontSize(mainWindow); + function updateFontSize(window) { + // 75% = > 14.25, 100% => 19, 200% => 28 + window.font.pixelSize = 17 + (11 * (Pay.fontScaling-100) / 100) + } + + Item { + id: mainScreen + anchors.fill: parent + focus: true + + Label { + x: 10 + y: 20 + width: Math.min(parent.width - 20, 200); + text: qsTr("Already running?") + } + + } +} + diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 6e85d75..2c28334 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -143,7 +143,8 @@ QString joinWords(const QList &words, bool lowercaseFirstWord) FloweePay::FloweePay() : m_chain(s_chain), m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), - m_indexerServices(new IndexerServices(m_basedir, ioContext(), this)) + m_indexerServices(new IndexerServices(m_basedir, ioContext(), this)), + m_lockFile(m_basedir + "/.lock") { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); @@ -301,6 +302,9 @@ FloweePay::FloweePay() in.close(); } + m_lockFile.setStaleLockTime(0); + m_lockFailed = !m_lockFile.tryLock(1); + // forward signals connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); connect (this, &FloweePay::startSaveData_priv, this, [=]() { @@ -326,12 +330,14 @@ FloweePay::FloweePay() // a global destructor the ordering is not locally controlled. void FloweePay::shutdown() { - p2pNet()->shutdown(); + if (m_lockFailed) // Another process owns those files. + return; saveData(); m_indexerServices->save(); auto *dl = m_downloadManager.get(); if (dl) { // p2pNet follows lazy initialization. + dl->shutdown(); dl->removeHeaderListener(this); dl->removeP2PNetListener(this); } @@ -493,7 +499,7 @@ void FloweePay::loadingCompleted() void FloweePay::saveData() { - auto data = std::make_shared(m_wallets.size() * 100); + auto data = std::make_shared(m_wallets.size() * 100 + 50); Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { if (wallet->encryptionSeed() != 0) @@ -954,6 +960,11 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +bool FloweePay::lockFailed() const +{ + return m_lockFailed; +} + QObject *FloweePay::notification() const { return m_notification; diff --git a/src/FloweePay.h b/src/FloweePay.h index ded76a2..1c622ac 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -424,6 +425,14 @@ public: QObject *notification() const; void setNotification(QObject *newNotification); + /** + * FloweePay will attempt to lock the datadirectory at startup in order + * to avoid multiple instances running at the same time. This is not a hard + * check, we simply set this bool in the constuctor of this class to true + * if some other process is still holding a lock. + */ + bool lockFailed() const; + signals: void loadComplete(); /// \internal @@ -491,6 +500,7 @@ private: IndexerServices *m_indexerServices; // the Electrum indexer index. QList m_wallets; QHash m_accountConfigs; // key is wallet-segment-id + QLockFile m_lockFile; int m_dspTimeout = 5000; int m_windowWidth = 500; int m_windowHeight = 500; @@ -509,6 +519,7 @@ private: bool m_offline = false; bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true + bool m_lockFailed = false; #ifdef TARGET_OS_Android // when the app is no longer the front app we record the time in order to diff --git a/src/main.cpp b/src/main.cpp index f4d0027..9eecafb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,6 +88,16 @@ int main(int argc, char *argv[]) qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); + // Clean shutdown on SIGTERM + struct sigaction sa; + sa.sa_handler = HandleSigTerm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGTERM, &sa, nullptr); + sigaction(SIGINT, &sa, nullptr); + // Ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + UserIntent paymentIntent; setupCallbacks(&paymentIntent); @@ -141,10 +151,14 @@ int main(int argc, char *argv[]) #endif auto app = FloweePay::instance(); + engine.rootContext()->setContextProperty("Pay", app); + if (app->lockFailed()) { + engine.load("qrc:///desktop/locked.qml"); + return qapp.exec(); + } engine.addImageProvider(QLatin1String("qr"), new QRCreator(QRCreator::URLEncoded)); engine.addImageProvider(QLatin1String("qr-raw"), new QRCreator(QRCreator::RawString)); engine.rootContext()->setContextProperty("Intent", &paymentIntent); - engine.rootContext()->setContextProperty("Pay", app); engine.rootContext()->setContextProperty("Fiat", app->prices()); engine.rootContext()->setContextProperty("MenuModel", &menuModel); engine.rootContext()->setContextProperty("ModuleManager", &modules); @@ -187,16 +201,5 @@ int main(int argc, char *argv[]) if (fontId == -1) { logCritical() << "Loading of our symbol font failed!"; } - - // Clean shutdown on SIGTERM - struct sigaction sa; - sa.sa_handler = HandleSigTerm; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGTERM, &sa, nullptr); - sigaction(SIGINT, &sa, nullptr); - // Ignore SIGPIPE - signal(SIGPIPE, SIG_IGN); - return qapp.exec(); } -- 2.54.0 From d0c7944fdef8072368fd04246bc557c734091ced Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 19:44:43 +0100 Subject: [PATCH 1215/1428] Open wider Make the default, never before started, window size wider as it really feels like the first thing people should do manually... --- guis/desktop/defaults.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/defaults.ini b/guis/desktop/defaults.ini index c91da32..c2916ed 100644 --- a/guis/desktop/defaults.ini +++ b/guis/desktop/defaults.ini @@ -2,7 +2,7 @@ useragent="Flowee:1 (Pay)" [window] -width=600 +width=780 height=600 #darkSkin=true -- 2.54.0 From cf19583676fce26b23770def2ce24e1caf265a0f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 19:53:55 +0100 Subject: [PATCH 1216/1428] Fix off by one. --- guis/desktop/Transaction.qml | 2 +- guis/mobile/AccountHistory.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index 9eca1dd..752950f 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -174,7 +174,7 @@ Rectangle { anchors.right: parent.right anchors.top: mainLabel.bottom property int txHeight: model.height - property int confirmations: Pay.chainHeight - txHeight + property int confirmations: Pay.chainHeight - txHeight + 1 visible: txHeight === -1 || confirmations < 5 Row { diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index cef233c..aa3005b 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -248,7 +248,7 @@ ListView { anchors.top: txListItem.baseline anchors.topMargin: 4 property int txHeight: model.height - property int confirmations: Pay.chainHeight - txHeight + property int confirmations: Pay.chainHeight - txHeight + 1 visible: txHeight === -1 || confirmations < 5 Rectangle { -- 2.54.0 From 67b8dda1e934e50213bdc2f81042310d6ec5ac5e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 21:25:13 +0100 Subject: [PATCH 1217/1428] Show something when the transaction is not yet confirmed. --- guis/desktop/TransactionDetails.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 9111f55..8589fcf 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -113,8 +113,6 @@ QQC2.ApplicationWindow { var h = txInfo.height; if (h === -2) var s = qsTr("Rejected") - if (h === -1) - s = qsTr("Unconfirmed") else s = qsTr("Mined at"); return s + ":"; @@ -137,6 +135,11 @@ QQC2.ApplicationWindow { return answer; } } + Flowee.Label { + Layout.columnSpan: 2 + visible: text !== "" + text: txInfo.minedHeight === -1 ? qsTr("Waiting for block") : ""; + } Flowee.Label { Layout.alignment: Qt.AlignRight -- 2.54.0 From 7d675d26e6e98d24f599086955bd5f352f07b612 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 21:25:32 +0100 Subject: [PATCH 1218/1428] fix comparison with different signs. --- src/NetDataProvider.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetDataProvider.cpp b/src/NetDataProvider.cpp index 6bafefa..5ce16b9 100644 --- a/src/NetDataProvider.cpp +++ b/src/NetDataProvider.cpp @@ -182,7 +182,7 @@ QVariant NetDataProvider::data(const QModelIndex &index_, int role) const QMutexLocker l(&m_peerMutex); const auto index = index_.row(); - if (index < 0 || index >= m_peers.size()) + if (index < 0 || index >= (int) m_peers.size()) return QVariant(); const auto &peerData = m_peers.at(index); -- 2.54.0 From 2805c68294f730d0dc7c05dc29f6843f0930d43d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 21:28:53 +0100 Subject: [PATCH 1219/1428] Make the stars be the same color as text. Calm is good. --- guis/Flowee/CardTypeSelector.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/CardTypeSelector.qml b/guis/Flowee/CardTypeSelector.qml index 0821071..28a0d60 100644 --- a/guis/Flowee/CardTypeSelector.qml +++ b/guis/Flowee/CardTypeSelector.qml @@ -90,7 +90,7 @@ Item { width: 6 height: 6 rotation: 45 - color: mainWindow.floweeSalmon + color: palette.text } Rectangle { anchors.top: left.top @@ -99,7 +99,7 @@ Item { width: 6 height: 6 rotation: 45 - color: mainWindow.floweeSalmon + color: palette.text } } } -- 2.54.0 From bed970fd6e5ea79b99623f928a67d86098599956 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 21:38:35 +0100 Subject: [PATCH 1220/1428] Make sure the question mark is better centered --- guis/Flowee/CheckBox.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 87f8a5f..5b7d240 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -98,7 +98,8 @@ T.CheckBox { id: q text: "?" color: palette.base - anchors.centerIn: parent + anchors.horizontalCenter: parent.horizontalCenter + y: (parent.height - baselineOffset) / 2 - 3 height: implicitHeight * 1.3 MouseArea { anchors.fill: parent -- 2.54.0 From 4d921a2748f02900ce6abf33f3a09785c157ffdf Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 22:12:59 +0100 Subject: [PATCH 1221/1428] Make the list of addresses be cloaked by default --- guis/Flowee/AddressLabel.qml | 11 +++++++++-- guis/Flowee/WalletSecretsView.qml | 13 ++++++++----- src/WalletSecretsModel.cpp | 11 ++++++++++- src/WalletSecretsModel.h | 3 ++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml index 8cb6ae8..35e184f 100644 --- a/guis/Flowee/AddressLabel.qml +++ b/guis/Flowee/AddressLabel.qml @@ -30,7 +30,10 @@ QQC2.Control { implicitWidth: theLabel.implicitWidth + (copyIcon.visible ? (copyIcon.width + 10) : 0) + baselineOffset: theLabel.baselineOffset + Label { + id: theLabel background: Rectangle { color: Pay.useDarkSkin ? "#4fb2e7" : "yellow" id: highlight @@ -40,10 +43,14 @@ QQC2.Control { width: parent.width - (copyIcon.visible ? (copyIcon.width + 6) : 0) height: parent.height - id: theLabel elide: wrapMode === Text.NoWrap ? Text.ElideMiddle : Text.ElideNone - horizontalAlignment: Text.AlignRight property bool allowCloak: true + onContentHeightChanged: { + if (text === root.txInfo.cloakedAddress) { // this font is bigger. + root.baselineOffset = baselineOffset; + root.implicitHeight = Math.max(theLabel.implicitHeight, root.showCopyIcon ? copyIcon.height : 0) + } + } text: { if (allowCloak) { diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index a1a8505..e55b7bb 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -83,7 +83,6 @@ ListView { } delegate: Item { - id: delegateRoot width: ListView.view.width height: addressLabel.height + 6 + amountLabel.height + 6 + (lineCount === 3 ? coinCountLabel.height + 6: 0) + 12 @@ -113,17 +112,21 @@ ListView { visible: root.showHdIndex && root.account.isHDWallet } - LabelWithClipboard { + AddressLabel { id: addressLabel y: 5 x: root.account.isHDWallet ? 50 : 5 anchors.bottomMargin: 6 - text: address anchors.left: parent.left anchors.leftMargin: root.showHdIndex ? 40 : 0 anchors.right: hamburgerMenu.left anchors.rightMargin: 6 - menuText: qsTr("Copy Address") + showCopyIcon: false + highlight: false + txInfo: { + "cloakedAddress": (root.showHdIndex ? "" : cloakedAddress), + "address": address + } } Item { diff --git a/src/WalletSecretsModel.cpp b/src/WalletSecretsModel.cpp index 27dd49d..1fc617d 100644 --- a/src/WalletSecretsModel.cpp +++ b/src/WalletSecretsModel.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,6 +78,14 @@ QVariant WalletSecretsModel::data(const QModelIndex &index, int role) const return QVariant(details(foundItem->first).coins); case UsedSchnorr: return QVariant(item.signatureType == Wallet::SignedAsSchnorr); + case CloakedAddress: { + if (item.fromChangeChain) + return tr("Change #%1").arg(item.hdDerivationIndex); + else if (item.fromHdWallet) + return tr("Main #%1").arg(item.hdDerivationIndex); + else + return QString(); + } } return QVariant(); @@ -88,6 +96,7 @@ QHash WalletSecretsModel::roleNames() const assert(QThread::currentThread() == thread()); QHash answer; answer[BitcoinAddress] = "address"; + answer[CloakedAddress] = "cloakedAddress"; answer[PrivateKey] = "privatekey"; answer[FromChangeChain] = "isChange"; answer[HDIndex] = "hdIndex"; diff --git a/src/WalletSecretsModel.h b/src/WalletSecretsModel.h index 1a50d4d..afbc168 100644 --- a/src/WalletSecretsModel.h +++ b/src/WalletSecretsModel.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ public: HistoricalCoins, ///< int, the number of historical coins touched this NumCoins, ///< int, the number of coins still present on this address UsedSchnorr, ///< bool, has transactions signed with Schnorr sigantures + CloakedAddress, ///< string, the user friendly version of the Bitcoin Address // UserLabel ///< user-specified name }; -- 2.54.0 From d9e6374ef62b02a6f5ddc31f0c42c2798a5cb633 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 22:28:16 +0100 Subject: [PATCH 1222/1428] new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3ef1206..e1d4add 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="36" android:versionName="2025.02.1"> diff --git a/src/main.cpp b/src/main.cpp index 9eecafb..7cd09e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.02.0"); + qapp.setApplicationVersion("2025.02.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 6f7388ccc34af2d8e13ff568b7471aa1d90f569d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Feb 2025 22:56:13 +0100 Subject: [PATCH 1223/1428] Clear out the inconsistencies in the background colors --- guis/mobile/AccountHistory.qml | 5 +++-- guis/mobile/AccountSelectorPopup.qml | 7 ++++--- guis/mobile/ExploreModules.qml | 10 +++++----- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/Page.qml | 2 +- guis/mobile/PageTitledBox.qml | 6 ------ guis/mobile/PopupOverlay.qml | 2 +- guis/mobile/ReceiveTab.qml | 5 ++--- 8 files changed, 17 insertions(+), 22 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index aa3005b..83c586c 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -97,7 +97,7 @@ ListView { header of the listview. */ header: Rectangle { - color: palette.window + color: palette.light width: root.width height: column.height Column { @@ -201,12 +201,13 @@ ListView { return today.getMonth() == 11 && (today.getDate() == 24 || today.getDate() == 25 || today.getDate() == 26) } - delegate: Item { + delegate: Rectangle { id: transactionDelegate property var placementInGroup: model.placementInGroup // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. property bool isMoved: Utils.isMoved(model); + color: palette.light width: root.width height: 80 diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 34cdfbe..4209af1 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -37,7 +37,7 @@ QQC2.Popup { } background: Rectangle { - color: palette.light + color: palette.base border.color: palette.midlight border.width: 1 radius: 5 @@ -82,6 +82,8 @@ QQC2.Popup { id: selectedItemIndicator visible: modelData === root.selectedAccount anchors.fill: parent + anchors.leftMargin: -10 + anchors.rightMargin: -10 color: palette.highlight opacity: 0.15 } @@ -170,7 +172,6 @@ QQC2.Popup { Flowee.Label { id: totalLabel x: 6 - y: 5 text: qsTr("Balance Total") + ":" font.pixelSize: root.font.pixelSize * 0.9 } @@ -179,7 +180,7 @@ QQC2.Popup { value: portfolio.totalBalance anchors.right: parent.right anchors.rightMargin: 6 - anchors.bottom: parent.bottom + anchors.baseline: totalLabel.baseline font.pixelSize: root.font.pixelSize * 0.9 colorize: false } diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 93e35e8..00e7243 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -23,6 +23,10 @@ Page { id: root headerText: qsTr("Explore") + background: Rectangle { + color: palette.light + } + Item { anchors.fill: parent // clip: true @@ -45,7 +49,7 @@ Page { width: root.width - 30 height: 35 + titleLabel.height + statusField.height + Math.min(120, descriptionLabel.implicitHeight) radius: 20 - color: palette.alternateBase + color: palette.base border.width: 1 border.color: palette.midlight visible: modelData.hasUISections // has user-visible parts. @@ -77,10 +81,6 @@ Page { anchors.bottomMargin: 10 anchors.right: parent.right z: 10 - Rectangle { - color: palette.alternateBase - anchors.fill: descriptionLabel - } Flowee.Label { id: descriptionLabel width: parent.width - 20 diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 2b59b94..c4b47ae 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -26,7 +26,7 @@ QQC2.Control { height: parent.height background: Rectangle { - color: palette.light + color: palette.base } // This trick means any child items are actually added as the 'stack' item's children. diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 955617f..1fed4ed 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -27,7 +27,7 @@ QQC2.Control { height: parent == null ? 10 : parent.height background: Rectangle { - color: palette.light + color: palette.base } leftPadding: 10 diff --git a/guis/mobile/PageTitledBox.qml b/guis/mobile/PageTitledBox.qml index 25c7f9f..863d12c 100644 --- a/guis/mobile/PageTitledBox.qml +++ b/guis/mobile/PageTitledBox.qml @@ -38,12 +38,6 @@ Item { implicitWidth: 50 // have SOME non-zero default. visible: implicitHeight > 0 - Rectangle { - anchors.fill: parent - anchors.leftMargin: -10 - anchors.rightMargin: -10 - color: palette.alternateBase - } Flowee.Label { id: boxTitle font.weight: 700 diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 37461e2..d811518 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -76,7 +76,7 @@ FocusScope { } Rectangle { - color: palette.light + color: palette.base border.color: palette.midlight border.width: 1 radius: 5 diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index b191dd0..22e8e62 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -74,9 +74,8 @@ FocusScope { } Rectangle { anchors.fill: parent - color: palette.highlight - visible: parent.active - opacity: 0.15 + color: parent.active ? palette.highlight : palette.light + opacity: parent.active ? 0.15 : 1 } Image { anchors.fill: parent -- 2.54.0 From 7b693e9a60773589493e15ff75b34e62a7d82f66 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Feb 2025 17:04:13 +0100 Subject: [PATCH 1224/1428] Add priority to modules for display purposes. --- modules/big-transfer/BigTransferModuleInfo.cpp | 1 + .../BuildTransactionModuleInfo.cpp | 1 + modules/social-feed/SocialFeedModuleInfo.cpp | 1 + src/ModuleInfo.cpp | 10 ++++++++++ src/ModuleInfo.h | 5 +++++ src/ModuleManager.cpp | 14 +++++++++++--- 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/big-transfer/BigTransferModuleInfo.cpp b/modules/big-transfer/BigTransferModuleInfo.cpp index 63bc418..f71e7b9 100644 --- a/modules/big-transfer/BigTransferModuleInfo.cpp +++ b/modules/big-transfer/BigTransferModuleInfo.cpp @@ -32,6 +32,7 @@ BigTransferModuleInfo::BigTransferModuleInfo() setId("bigTransfer"); setTitle(tr("Wallet to Wallet")); setDescription(tr("Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses.")); + setPriority(5); ModuleSection *sendTab = new ModuleSection(ModuleSection::SendMethod, this); sendTab->setText(tr("Wallet to Wallet")); diff --git a/modules/build-transaction/BuildTransactionModuleInfo.cpp b/modules/build-transaction/BuildTransactionModuleInfo.cpp index 6abe677..3d64e07 100644 --- a/modules/build-transaction/BuildTransactionModuleInfo.cpp +++ b/modules/build-transaction/BuildTransactionModuleInfo.cpp @@ -22,6 +22,7 @@ ModuleInfo * BuildTransactionModuleInfo::build() auto module = new ModuleInfo(); module->setTitle(tr("Create Transactions")); module->setDescription(tr("This module allows building more powerful transactions in one simple user interface.")); + module->setPriority(7); auto sendButton = new ModuleSection(ModuleSection::SendMethod, module); sendButton->setText(tr("Build Transaction")); diff --git a/modules/social-feed/SocialFeedModuleInfo.cpp b/modules/social-feed/SocialFeedModuleInfo.cpp index 2fe1947..dc69d52 100644 --- a/modules/social-feed/SocialFeedModuleInfo.cpp +++ b/modules/social-feed/SocialFeedModuleInfo.cpp @@ -28,6 +28,7 @@ ModuleInfo * SocialFeedModuleInfo::build() info->setDescription(tr("Want to see the experts show how to use Bitcoin Cash " "with Flowee Pay? Find all you want via this library of videos.")); info->setIconSource("qrc:/social-feed/social-feed.svg"); + info->setPriority(20); auto last = new ModuleSection(ModuleSection::OtherSectionType, info); last->setText(info->title()); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 35e9821..3302f4a 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -127,6 +127,16 @@ bool ModuleInfo::hasUISections() const return false; } +int ModuleInfo::priority() const +{ + return m_priority; +} + +void ModuleInfo::setPriority(int newPriority) +{ + m_priority = newPriority; +} + void ModuleInfo::setIconSource(const QString &newIconSource) { m_iconSource = newIconSource; diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 49f9e87..9fd19ad 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -97,6 +97,10 @@ public: /// returns true if there is at least one section that is user-visible bool hasUISections() const; + /// the priority is used to sort modules in a user visible list. Highest gets shown first. + int priority() const; + void setPriority(int newPriority); + signals: void enabledChanged(); @@ -109,6 +113,7 @@ private: QString m_description; QString m_translationUnit; QString m_iconSource; + int m_priority = -1; QList m_sections; }; diff --git a/src/ModuleManager.cpp b/src/ModuleManager.cpp index 092c4ef..66b97bd 100644 --- a/src/ModuleManager.cpp +++ b/src/ModuleManager.cpp @@ -115,10 +115,18 @@ void ModuleManager::load(const char *translationUnit, const std::functionsetParent(this); // take ownership - if (info->enabled()) + if (info->enabled()) { logCritical() << "ModuleInfo starts 'enabled', denying user choice. Cowerdly refusing to register it"; - else - m_modules.append(info); + return; + } + int insertPoint = m_modules.size(); + while (insertPoint > 0) { + if (m_modules.at(insertPoint - 1)->priority() >= info->priority()) + break; + --insertPoint; + } + + m_modules.insert(insertPoint, info); } void ModuleManager::load() -- 2.54.0 From 709f5ec7edc713d03f6abef08f094b29a5c92191 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Feb 2025 18:29:44 +0100 Subject: [PATCH 1225/1428] Make sure that the TextButton has enough space. We should not require the user to put it in a layout with spacing, that will just create inconsistencies. Instead make sure that the button itself is tall enough for easy aiming on small screens. --- guis/mobile/MainView.qml | 1 - guis/mobile/TextButton.qml | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 84236fc..e6187a2 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -47,7 +47,6 @@ MainViewBase { Column { width: parent.width - spacing: 10 Repeater { model: ModuleManager.exploreTabItems diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 573a7bf..cef710f 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -24,7 +24,8 @@ Item { width: parent.width implicitWidth: label.x + Math.max(label.contentWidth, smallLabel.contentWidth) + ((rightIcon.implicitWidth === 0) ? 0 : (10 + rightIcon.implicitWidth)) - height: label.height + (smallLabel.text === "" ? 0 : smallLabel.height + 6) + 20 + implicitHeight: Math.max(60, label.height + (smallLabel.text === "" ? 0 : smallLabel.height) + 20) + height: implicitHeight baselineOffset: label.baselineOffset + 10 Layout.fillWidth: true @@ -55,7 +56,7 @@ Item { Flowee.Label { id: label - y: 10 + y: smallLabel.text === "" ? (parent.height - height) / 2 : 10 x: icon.visible ? 38 : 0 width: { var w = parent.width - x @@ -95,7 +96,6 @@ Item { Flowee.Label { id: smallLabel anchors.top: label.bottom - anchors.topMargin: 6 font.pixelSize: label.font.pixelSize * 0.8 font.bold: false color: palette.brightText -- 2.54.0 From f12b6f31f250ecebaba16f7d1537d835900d5c20 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Feb 2025 18:29:55 +0100 Subject: [PATCH 1226/1428] Update the url --- guis/mobile/About.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/About.qml b/guis/mobile/About.qml index ac6b756..994b8e8 100644 --- a/guis/mobile/About.qml +++ b/guis/mobile/About.qml @@ -99,7 +99,7 @@ You? text: qsTr("Project Home") subtext: qsTr("With git repository and issues tracker") imageSource: translate.imageSource - onClicked: Qt.openUrlExternally("https://codeberg.org/Flowee/pay"); + onClicked: Qt.openUrlExternally("http://bitcoincashcode.org/Flowee/pay"); } TextButton { text: qsTr("Telegram") -- 2.54.0 From 3d821d4be41581fefa1f160e320f12462026420a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Feb 2025 19:38:39 +0100 Subject: [PATCH 1227/1428] Add NewIndicator on socialFeedModule --- guis/mobile.qrc | 1 + guis/mobile/ExploreModules.qml | 25 ++++++++-- guis/mobile/MainView.qml | 6 ++- guis/mobile/MainViewBase.qml | 20 ++++++-- guis/mobile/NewIndicator.qml | 51 ++++++++++++++++++++ guis/mobile/TextButton.qml | 34 ++----------- modules/social-feed/SocialFeedModuleInfo.cpp | 1 + src/ModuleInfo.cpp | 13 +++++ src/ModuleInfo.h | 8 +++ src/NewIndicatorProvider.cpp | 8 ++- src/NewIndicatorProvider.h | 1 + src/main.cpp | 2 +- 12 files changed, 129 insertions(+), 41 deletions(-) create mode 100644 guis/mobile/NewIndicator.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 291f622..6118068 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -96,5 +96,6 @@ mobile/UnlockWalletPanel.qml mobile/UnlockWidget.qml mobile/VisualSeparator.qml + mobile/NewIndicator.qml diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 00e7243..482057d 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023-2024 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -61,6 +61,11 @@ Page { font.bold: true anchors.horizontalCenter: parent.horizontalCenter } + NewIndicator { + id: newIndicator + buddy: titleLabel + buttonId: modelData.buttonId + } Image { x: 10 width: 64 @@ -121,8 +126,10 @@ Page { MouseArea { anchors.left: openLabel.left anchors.right: parent.right - height: parent.height + height: parent.height + 20 + y: -10 onClicked: { + newIndicator.markSeen(); for (let s of modelData.sections) { if (s.isMainMenuMethod || s.isSendMethod || s.isForExploreTab) { thePile.replace(s.qml); @@ -133,11 +140,13 @@ Page { } Row { + id: enabledRow spacing: 10 - anchors.fill: parent - anchors.leftMargin: 10 + height: parent.height + x: 10 Flowee.CheckBox { + id: moduleEnabled anchors.verticalCenter: parent.verticalCenter checked: modelData.enabled onClicked: modelData.enabled = checked; @@ -196,6 +205,14 @@ Page { } } } + MouseArea { + anchors.fill: enabledRow + anchors.margins: -10 + onClicked: { + newIndicator.markSeen(); + modelData.enabled = !moduleEnabled.checked; + } + } } } } diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index e6187a2..1f081be 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,7 +40,8 @@ MainViewBase { FocusScope { id: apps property string icon: "qrc:/exploretab" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - property string title: qsTr("Explore") + property string title: qsTr("Explore") + property int buttonId: 73573 anchors.fill: parent anchors.leftMargin: 10 anchors.rightMargin: 10 @@ -64,6 +65,7 @@ MainViewBase { text: qsTr("Find More") iconSource: "qrc:/more" + (Pay.useDarkSkin ? "-light" : "") + ".svg"; pageButton: true + buttonId: 95231 onClicked: thePile.push("ExploreModules.qml") } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index c4b47ae..8920509 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -154,7 +154,7 @@ QQC2.Control { function calcNumFilters() { var filterCount = 0; var currentAccount = portfolio.current - if (currentAccount != null) + if (currentAccount !== null) filterCount = currentAccount.transactions.filterCount; return filterCount; } @@ -243,15 +243,29 @@ QQC2.Control { anchors.horizontalCenter: parent.horizontalCenter } Flowee.Label { + id: buttonLabel text: stack.children[modelData].title anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 2 font.pixelSize: 14 } + NewIndicator { + id: newIndicator + buddy: buttonLabel + Component.onCompleted: { + var buttonId = stack.children[modelData].buttonId; + if (typeof(buttonId) === "number") + newIndicator.buttonId = buttonId; + } + + } MouseArea { anchors.fill: parent - onClicked: root.currentIndex = modelData + onClicked: { + newIndicator.markSeen(); + root.currentIndex = modelData + } } } } diff --git a/guis/mobile/NewIndicator.qml b/guis/mobile/NewIndicator.qml new file mode 100644 index 0000000..954b014 --- /dev/null +++ b/guis/mobile/NewIndicator.qml @@ -0,0 +1,51 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +Rectangle { + id: root + color: Pay.useDarkSkin ? "#0b45a2" : "#0d61b4" + width: 14 + height: 14 + radius: 7 + x: visible ? buddy.x + buddy.contentWidth - 1 : 0; + y: { + if (!visible) + return 0; + return buddy.y + buddy.baselineOffset - height / 2 - buddy.font.pixelSize * 0.8 + } + property int buttonId: 0 + + // the label this is drawn over. + required property var buddy + + function markSeen() { + var bID = root.buttonId; + if (bID !== 0) { + NewIndicatorProvider.seen(bID); + root.visible = false; + } + } + + visible: { + var bID = root.buttonId; + if (bID === 0) + return false; + return NewIndicatorProvider.isNew(bID); + } +} diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index cef710f..25b44d6 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -40,7 +40,7 @@ Item { property string imageSource: "" property int imageWidth: 20 property int imageHeight: 20 - property int buttonId: 0 + property alias buttonId: newIndicator.buttonId /// Add an icon before the label property string iconSource: "" @@ -67,31 +67,9 @@ Item { elide: Text.ElideRight color: enabled ? palette.windowText : palette.brightText } - Rectangle { + NewIndicator { id: newIndicator - color: Pay.useDarkSkin ? "#0b45a2" : "#0d61b4" - width: 14 - height: 14 - radius: 7 - x: { - if (!visible) - return 0; - var w = label.contentWidth; - return w - 1; - } - y: { - if (!visible) - return 0; - - return label.y + label.baselineOffset - height / 2 - label.font.pixelSize * 0.8 - } - - visible: { - var bID = root.buttonId; - if (bID === 0) - return false; - return NewIndicator.isNew(bID); - } + buddy: label } Flowee.Label { id: smallLabel @@ -106,11 +84,7 @@ Item { MouseArea { anchors.fill: parent onClicked: { - var bID = root.buttonId; - if (bID !== 0) { - NewIndicator.seen(bID); - newIndicator.visible = false; - } + newIndicator.markSeen(); root.clicked() } } diff --git a/modules/social-feed/SocialFeedModuleInfo.cpp b/modules/social-feed/SocialFeedModuleInfo.cpp index dc69d52..2c07af7 100644 --- a/modules/social-feed/SocialFeedModuleInfo.cpp +++ b/modules/social-feed/SocialFeedModuleInfo.cpp @@ -29,6 +29,7 @@ ModuleInfo * SocialFeedModuleInfo::build() "with Flowee Pay? Find all you want via this library of videos.")); info->setIconSource("qrc:/social-feed/social-feed.svg"); info->setPriority(20); + info->setButtonId(13427); auto last = new ModuleSection(ModuleSection::OtherSectionType, info); last->setText(info->title()); diff --git a/src/ModuleInfo.cpp b/src/ModuleInfo.cpp index 3302f4a..6f82a21 100644 --- a/src/ModuleInfo.cpp +++ b/src/ModuleInfo.cpp @@ -137,6 +137,19 @@ void ModuleInfo::setPriority(int newPriority) m_priority = newPriority; } +int ModuleInfo::buttonId() const +{ + return m_buttonId; +} + +void ModuleInfo::setButtonId(int newButtonId) +{ + if (m_buttonId == newButtonId) + return; + m_buttonId = newButtonId; + emit buttonIdChanged(); +} + void ModuleInfo::setIconSource(const QString &newIconSource) { m_iconSource = newIconSource; diff --git a/src/ModuleInfo.h b/src/ModuleInfo.h index 9fd19ad..766e283 100644 --- a/src/ModuleInfo.h +++ b/src/ModuleInfo.h @@ -45,6 +45,8 @@ class ModuleInfo : public QObject Q_PROPERTY(QList sections READ sections CONSTANT FINAL) Q_PROPERTY(QString iconSource READ iconSource CONSTANT FINAL) Q_PROPERTY(QString id READ id CONSTANT FINAL) + /// the buttonId used by the NewIndicatorProvider / NewIndicator.qml + Q_PROPERTY(int buttonId READ buttonId WRITE setButtonId NOTIFY buttonIdChanged FINAL) public: explicit ModuleInfo(QObject *parent = nullptr); @@ -101,9 +103,14 @@ public: int priority() const; void setPriority(int newPriority); + int buttonId() const; + void setButtonId(int newButtonId); + signals: void enabledChanged(); + void buttonIdChanged(); + protected: bool m_enabled = false; @@ -114,6 +121,7 @@ private: QString m_translationUnit; QString m_iconSource; int m_priority = -1; + int m_buttonId = 0; QList m_sections; }; diff --git a/src/NewIndicatorProvider.cpp b/src/NewIndicatorProvider.cpp index bacbea5..71d7971 100644 --- a/src/NewIndicatorProvider.cpp +++ b/src/NewIndicatorProvider.cpp @@ -29,6 +29,9 @@ namespace { enum Pages { BackupPage = 92387, WalletsPage = 32948, + ModulesPage = 73573, + ExploreModulesPage = 95231, + HelpAndLearningModule = 13427 }; } @@ -37,11 +40,14 @@ NewIndicatorProvider::NewIndicatorProvider(QObject *parent) { m_pages.insert({WalletsPage, NewForEveryone}); m_pages.insert({BackupPage, NewForEveryone}); + m_pages.insert({ModulesPage, NewSince202502}); + m_pages.insert({ExploreModulesPage, NewSince202502}); + m_pages.insert({HelpAndLearningModule, NewSince202502}); load(); if (m_appInstallDate == 0) { // update this one when a new enum is introduced - m_appInstallDate = NewSince202501; + m_appInstallDate = NewSince202502; m_dirty = true; } } diff --git a/src/NewIndicatorProvider.h b/src/NewIndicatorProvider.h index 4542fd1..24ab1a7 100644 --- a/src/NewIndicatorProvider.h +++ b/src/NewIndicatorProvider.h @@ -42,6 +42,7 @@ private: NewForEveryone, ///< everyone should have a new indicator once. // Users that started using the software after YYYYMM date won't see a new indicator. NewSince202501, + NewSince202502, }; int m_appInstallDate = 0; diff --git a/src/main.cpp b/src/main.cpp index 7cd09e8..1abde17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -162,7 +162,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("Fiat", app->prices()); engine.rootContext()->setContextProperty("MenuModel", &menuModel); engine.rootContext()->setContextProperty("ModuleManager", &modules); - engine.rootContext()->setContextProperty("NewIndicator", &newIndicatorProvider); + engine.rootContext()->setContextProperty("NewIndicatorProvider", &newIndicatorProvider); qmlRegisterType("Flowee.org.pay", 1, 0, "QRScanner"); #ifndef NO_MULTIMEDIA -- 2.54.0 From 0e6dd39d2e12847818f8621a02ea36667b3c5b56 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Feb 2025 16:16:13 +0100 Subject: [PATCH 1228/1428] Move platform specific code to platform files. This removes the various ifdef based differences which are platform specific and instead moves the code to a file that will only be compiled for that target platform. We also remove the backwards compatible 'darktheme-from-system' code which is by now no longer needed. --- src/CMakeLists.txt | 24 ++++++---- src/FloweePay.cpp | 88 +++++------------------------------ src/FloweePay.h | 6 +-- src/FloweePay_android.cpp | 97 +++++++++++++++++++++++++++++++++++++++ src/FloweePay_dummy.cpp | 32 +++++++++++++ 5 files changed, 156 insertions(+), 91 deletions(-) create mode 100644 src/FloweePay_android.cpp create mode 100644 src/FloweePay_dummy.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9837a1d..f8166af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -63,23 +63,29 @@ set (PAY_SOURCES ModuleSection.cpp ) if (ANDROID) - list(APPEND PAY_SOURCES NotificationManager_p_android.h NotificationManager_android.cpp) + list(APPEND PAY_SOURCES + main_utils_android.cpp + NotificationManager_p_android.h NotificationManager_android.cpp + FloweePay_android.cpp + ) elseif (${Qt6DBus_FOUND}) - list(APPEND PAY_SOURCES NotificationManager_p_dbus.h NotificationManager_dbus.cpp) + list(APPEND PAY_SOURCES + NotificationManager_p_dbus.h NotificationManager_dbus.cpp + FloweePay_dummy.cpp + main_utils.cpp + ) else () - list(APPEND PAY_SOURCES NotificationManager_dummy.cpp) + list(APPEND PAY_SOURCES + NotificationManager_dummy.cpp + FloweePay_dummy.cpp + main_utils.cpp + ) endif () if (NetworkLogClient) list(APPEND PAY_SOURCES NetworkLogClient.cpp) endif () -if (ANDROID) - list(APPEND PAY_SOURCES main_utils_android.cpp) -else () - list(APPEND PAY_SOURCES main_utils.cpp) -endif () - if (${Qt6Multimedia_FOUND}) list(APPEND PAY_SOURCES CameraController.cpp) list(APPEND PayLib_PRIVATE_LIBS Qt6::Multimedia) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 2c28334..a4e41d4 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -95,6 +95,9 @@ enum FileTags { static P2PNet::Chain s_chain = P2PNet::MainChain; +// platform specific methods definitions. +bool fp_platformSkinDark(); + namespace { QList splitString(const QString &input) { @@ -157,51 +160,6 @@ FloweePay::FloweePay() } boost::filesystem::create_directories(boost::filesystem::path(m_basedir.toStdString())); - // make it move to the proper thread. - connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); -#ifdef TARGET_OS_Android - // on Android, an app is either full screen (active) or inactive and not visible. - // It is expected we save state as we move to inactive state in order to make the app - // trivial to kill without loss of data. - // The above 'aboutToQuit' will not be given enough CPU time to actually save much. - auto guiApp = qobject_cast(QCoreApplication::instance()); - assert(guiApp); - connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { - if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { - if (m_sleepStart.isNull()) { - logInfo() << "App no longer active. Start saving data"; - m_sleepStart = QDateTime::currentDateTimeUtc(); - saveAll(); - p2pNet()->saveData(); - saveData(); - m_indexerServices->save(); - } - } - else if (state == Qt::ApplicationActive) { - /* - * We are brought back to the foreground. - * - * On different iterations of Android the behavior can differ quite substantially - * when it comes to being allowed to continue using resources while not being active. - * As such there is no real way to know what being inactive has done to our network - * connections. They may have been kept alive for whatever time we were - * not active, they may all have been removed or timed out already. - * - * What we'll do is to start the actions again which will check up on our connections - * and create new ones if we need them. - */ - if (!m_offline && m_loadingCompleted) - p2pNet()->start(); - - if (m_appProtection == AppUnlocked - && m_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { - // re-lock the app after 10 minutes of not being in the front. - setAppProtection(AppPassword); - } - m_sleepStart = QDateTime(); - } - }); -#endif QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat); m_unit = static_cast(defaultConfig.value(UNIT_TYPE, BCH).toInt()); @@ -218,19 +176,8 @@ FloweePay::FloweePay() m_windowHeight = appConfig.value(WINDOW_HEIGHT, m_windowHeight).toInt(); m_windowWidth = appConfig.value(WINDOW_WIDTH, m_windowWidth).toInt(); - /* - * Darkskin and darkskinFromSystem were introduced one after the other. - * People that have darkskin stored, but not darkskinFromSystem thus need to - * be migrated from the old setup to the new. - * For migrating users, the darkSkinFromSystem is set to false. For - * new users (i.e. everyone else), it defaults to true. - */ - bool defaultPlatform = true; - if (!appConfig.value(DARKSKIN_FROM_PLATFORM).isValid() && appConfig.value(DARKSKIN).isValid()) - defaultPlatform = false; - m_darkSkin = appConfig.value(DARKSKIN, m_darkSkin).toBool(); - setSkinFollowsPlatform(appConfig.value(DARKSKIN_FROM_PLATFORM, defaultPlatform).toBool()); + setSkinFollowsPlatform(appConfig.value(DARKSKIN_FROM_PLATFORM, true).toBool()); m_activityShowsBch = appConfig.value(ACTIVITYSHOWBCH, m_activityShowsBch).toBool(); m_fontScaling = appConfig.value(FONTSCALING, m_fontScaling).toInt(); m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt(); @@ -306,6 +253,7 @@ FloweePay::FloweePay() m_lockFailed = !m_lockFile.tryLock(1); // forward signals + connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); connect (this, &FloweePay::startSaveData_priv, this, [=]() { // As Qt does not allow starting a timer from any thread, we first use a signal @@ -494,6 +442,7 @@ void FloweePay::loadingCompleted() m_prices->start(); } m_loadingCompleted = true; + setupPlatform(); emit loadComplete(); } @@ -1032,31 +981,16 @@ bool FloweePay::skinFollowsPlatform() const return m_skinFollowsPlatform; } -void FloweePay::setSkinFollowsPlatform(bool newSkinFollowsPlatform) +void FloweePay::setSkinFollowsPlatform(bool follows) { - if (m_skinFollowsPlatform == newSkinFollowsPlatform) + if (m_skinFollowsPlatform == follows) return; - m_skinFollowsPlatform = newSkinFollowsPlatform; + m_skinFollowsPlatform = follows; emit skinFollowsPlatformChanged(); QSettings appConfig; appConfig.setValue(DARKSKIN_FROM_PLATFORM, m_skinFollowsPlatform); - -#ifdef TARGET_OS_Android - if (newSkinFollowsPlatform) { - auto context = QJniObject(QNativeInterface::QAndroidApplication::context()); - // In Java: - // context.getResources().getConfiguration().uiMode - auto resources = context.callObjectMethod("getResources", - "()Landroid/content/res/Resources;"); - auto config = resources.callObjectMethod("getConfiguration", - "()Landroid/content/res/Configuration;"); - auto uiMode = config.getField("uiMode"); - constexpr int UI_MODE_NIGHT_MASK = 0x30; - constexpr int UI_MODE_NIGHT_YES = 0x20; - const bool dark = (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; - setDarkSkin(dark); - } -#endif + if (m_skinFollowsPlatform) + setDarkSkin(fp_platformSkinDark()); } FloweePay::ApplicationProtection FloweePay::appProtection() const diff --git a/src/FloweePay.h b/src/FloweePay.h index 1c622ac..1447aee 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -476,6 +476,7 @@ private: }; void init(); + void setupPlatform(); void shutdown(); void saveAll(); // create wallet and add to list. Please consider calling walletsChanged() after @@ -521,11 +522,6 @@ private: bool m_privateMode = false; // wallets marked private are hidden when true bool m_lockFailed = false; -#ifdef TARGET_OS_Android - // when the app is no longer the front app we record the time in order to - // know how much time has passed when we get active again. - QDateTime m_sleepStart; -#endif friend class AccountConfig; }; diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp new file mode 100644 index 0000000..31292cb --- /dev/null +++ b/src/FloweePay_android.cpp @@ -0,0 +1,97 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2020-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "FloweePay.h" +#include "IndexerServices.h" + +#include +# include + +void FloweePay::setupPlatform() +{ + // on Android, an app is either full screen (active) or inactive and not visible. + // It is expected we save state as we move to inactive state in order to make the app + // trivial to kill without loss of data. + // The above 'aboutToQuit' will not be given enough CPU time to actually save much. + auto guiApp = qobject_cast(QCoreApplication::instance()); + assert(guiApp); + static QDateTime g_sleepStart; + + connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { + if (g_sleepStart.isNull()) { + logInfo() << "App no longer active. Start saving data"; + g_sleepStart = QDateTime::currentDateTimeUtc(); + saveAll(); + p2pNet()->saveData(); + saveData(); + m_indexerServices->save(); + } + } + else if (state == Qt::ApplicationActive) { + /* + * We are brought back to the foreground. + * + * On different iterations of Android the behavior can differ quite substantially + * when it comes to being allowed to continue using resources while not being active. + * As such there is no real way to know what being inactive has done to our network + * connections. They may have been kept alive for whatever time we were + * not active, they may all have been removed or timed out already. + * + * What we'll do is to start the actions again which will check up on our connections + * and create new ones if we need them. + */ + if (!m_offline && m_loadingCompleted) + p2pNet()->start(); + + if (m_appProtection == AppUnlocked + && g_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { + // re-lock the app after 10 minutes of not being in the front. + setAppProtection(AppPassword); + } + g_sleepStart = QDateTime(); + } + }); + + assert(m_downloadManager.get()); + // ask the Android system which interfaces there are; + QJniEnvironment env; + jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); + quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); + if (flags != 0) { + logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; + auto &addressDb = m_downloadManager->connectionManager().peerAddressDb(); + addressDb.setSupportIPv4Net((flags & 1) == 1); + addressDb.setSupportIPv6Net((flags & 2) == 2); + } +} + +bool fp_platformSkinDark() +{ + auto context = QJniObject(QNativeInterface::QAndroidApplication::context()); + // In Java: + // context.getResources().getConfiguration().uiMode + auto resources = context.callObjectMethod("getResources", + "()Landroid/content/res/Resources;"); + auto config = resources.callObjectMethod("getConfiguration", + "()Landroid/content/res/Configuration;"); + auto uiMode = config.getField("uiMode"); + constexpr int UI_MODE_NIGHT_MASK = 0x30; + constexpr int UI_MODE_NIGHT_YES = 0x20; + const bool dark = (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; + return dark; +} diff --git a/src/FloweePay_dummy.cpp b/src/FloweePay_dummy.cpp new file mode 100644 index 0000000..48d1664 --- /dev/null +++ b/src/FloweePay_dummy.cpp @@ -0,0 +1,32 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "FloweePay.h" + +// this file contains dummy implementations of +// platform specific code to describe the default +// behavior when no platform specific implementation +// was found + +bool fp_platformSkinDark() +{ + return true; +} + +void FloweePay::setupPlatform() +{ +} -- 2.54.0 From 21eeab2f98ee6fa48a7b68c8cc58835024ac9cc6 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 26 Feb 2025 14:04:58 +0100 Subject: [PATCH 1229/1428] Add offline checking for Android. Add logic to notice that the device is offline and notify the user of this fact on screen. Repeat checking so we can notify the user that network is back. This shows in the UI (of the mobile form factor) that the device is offline and we avoid starting the p2p layer until network is detected to be there again. --- CMakeLists.txt | 1 + android/java/org/flowee/pay/Networks.java | 87 ++++++++++++++++------- guis/mobile/AccountSyncState.qml | 2 +- guis/mobile/MainViewBase.qml | 14 ++++ modules/peers-view/NetView.qml | 8 +++ src/FloweePay.cpp | 29 +++++--- src/FloweePay.h | 9 ++- src/FloweePay_android.cpp | 74 +++++++++++++++---- src/FloweePay_dummy.cpp | 5 +- 9 files changed, 174 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba839dc..525a6ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,7 @@ if (ANDROID AND build_mobile_pay) set (SOURCES_PAY_MOBILE src/main.cpp + src/Periodic.cpp ${CMAKE_BINARY_DIR}/src/qml_path_helper.cpp ${CMAKE_BINARY_DIR}/modules/modules-load.cpp ) diff --git a/android/java/org/flowee/pay/Networks.java b/android/java/org/flowee/pay/Networks.java index f6930de..7dff695 100644 --- a/android/java/org/flowee/pay/Networks.java +++ b/android/java/org/flowee/pay/Networks.java @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,8 +17,13 @@ */ package org.flowee.pay; +import android.os.Build; import android.content.Context; import java.net.*; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; // this is causing a deprecated warn, ignore it while we can. import java.util.Enumeration; import java.util.List; @@ -33,32 +38,32 @@ public class Networks boolean ipv4Available = false; boolean ipv6Available = false; - try { - Enumeration networking = NetworkInterface.getNetworkInterfaces(); - while (networking.hasMoreElements()) { - NetworkInterface i = networking.nextElement(); - if (i.isLoopback()) - continue; - if (!i.isUp()) - continue; - List addresses = i.getInterfaceAddresses(); - for (InterfaceAddress address : addresses) { - InetAddress ip = address.getAddress(); - try { - Inet6Address ip6 = (Inet6Address) ip; - if (!ip6.isLinkLocalAddress()) ipv6Available = true; - } catch (ClassCastException e) { } - try { - Inet4Address ip4 = (Inet4Address) ip; - if (!ip4.isLinkLocalAddress()) ipv4Available = true; - } catch (ClassCastException e) { } - } - } - } catch (SocketException e) { - // sane default if we don't have permission to check stuff.. - ipv4Available = true; - } - + try { + Enumeration networking = NetworkInterface.getNetworkInterfaces(); + while (networking.hasMoreElements()) { + NetworkInterface i = networking.nextElement(); + if (i.isLoopback()) + continue; + if (!i.isUp()) + continue; + List addresses = i.getInterfaceAddresses(); + for (InterfaceAddress address : addresses) { + InetAddress ip = address.getAddress(); + try { + Inet6Address ip6 = (Inet6Address) ip; + if (!ip6.isLinkLocalAddress()) ipv6Available = true; + } catch (ClassCastException e) { } + try { + Inet4Address ip4 = (Inet4Address) ip; + if (!ip4.isLinkLocalAddress()) ipv4Available = true; + } catch (ClassCastException e) { } + } + } + } catch (SocketException e) { + // sane default if we don't have permission to check stuff.. + ipv4Available = true; + } + // simple bitfield. int answer = 0; if (ipv4Available) @@ -67,4 +72,32 @@ public class Networks answer += 2; return answer; } + + public static boolean isInternetAvailable(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Network nw = connectivityManager.getActiveNetwork(); + if (nw == null) return false; + NetworkCapabilities actNw = connectivityManager.getNetworkCapabilities(nw); + if (actNw == null || !(actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + || actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET))) + return false; + } + else { + // we call the deprecated API to be compatible with old devices. + NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); + if (activeNetwork == null) + return false; + if (!activeNetwork.isConnected()) + return false; + } + try { // Quick ping to a reliable server + InetAddress address = InetAddress.getByName("8.8.8.8"); + return address.isReachable(2000); // Timeout of 2 seconds + } catch (Exception e) { + return false; + } + } } diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 3231d1e..9d536b5 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -91,7 +91,7 @@ Item { var account = portfolio.current; if (account === null) return ""; - if (account.needsPinToOpen && !account.isDecrypted) + if (Pay.deviceOffline || (account.needsPinToOpen && !account.isDecrypted)) return qsTr("Status: Offline"); return account.timeBehind; } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 8920509..a8f3f69 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -64,9 +64,23 @@ QQC2.Control { child.focus = true; } } + Rectangle { + id: offlineNotice + color: mainWindow.errorRedBg + width: parent.width + height: visible ? 35 : 0 + visible: Pay.deviceOffline + Flowee.Label { + text: qsTr("No Internet Available"); + font.bold: true + anchors.centerIn: parent + } + Behavior on height { NumberAnimation { } } + } Rectangle { id: header width: parent.width + y: offlineNotice.height height: 50 color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue diff --git a/modules/peers-view/NetView.qml b/modules/peers-view/NetView.qml index fd90863..dd07a86 100644 --- a/modules/peers-view/NetView.qml +++ b/modules/peers-view/NetView.qml @@ -33,11 +33,19 @@ Mobile.Page { menuItems: [statsAction]; + Flowee.Label { + id: offlineLabel + font.bold: true + text: qsTr("No Internet Available") + color: mainWindow.errorRed + visible: Pay.deviceOffline + } ListView { id: listView model: net anchors.fill: parent + anchors.topMargin: offlineLabel.visible ? offlineLabel.height + 10 : 0 QQC2.ScrollBar.vertical: QQC2.ScrollBar { } focus: true diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index a4e41d4..d7cbf37 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -436,13 +436,13 @@ void FloweePay::loadingCompleted() for (auto wallet : std::as_const(m_wallets)) { wallet->performUpgrades(); } + setupPlatform(); if (m_chain == P2PNet::MainChain) { m_prices->loadPriceHistory(m_basedir); - if (!m_offline) + if (!m_offline && !m_deviceOffline) m_prices->start(); } m_loadingCompleted = true; - setupPlatform(); emit loadComplete(); } @@ -899,7 +899,7 @@ void FloweePay::connectToWallet(Wallet *wallet) { connect (wallet, &Wallet::encryptionChanged, wallet, [=]() { // make sure that we get peers for the wallet directly after it gets decrypted - if (!m_offline && wallet->isDecrypted()) + if (!m_offline && !m_deviceOffline && wallet->isDecrypted()) FloweePay::p2pNet()->addAction(); }); connect (wallet, &Wallet::encryptionSeedChanged, wallet, [=]() { @@ -909,6 +909,19 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +bool FloweePay::deviceOffline() const +{ + return m_deviceOffline; +} + +void FloweePay::setDeviceOffline(bool newDeviceOffline) +{ + if (m_deviceOffline == newDeviceOffline) + return; + m_deviceOffline = newDeviceOffline; + emit deviceOfflineChanged(); +} + bool FloweePay::lockFailed() const { return m_lockFailed; @@ -1068,7 +1081,7 @@ void FloweePay::setOffline(bool offline) void FloweePay::startNet() { - if (m_offline) + if (m_offline || m_deviceOffline) return; p2pNet()->start(); // lets go! } @@ -1138,7 +1151,7 @@ NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, cons startHeight = m_chain == P2PNet::MainChain ? 850000 : 1000; wallet->addPrivateKey(words.first().toString(), startHeight); emit walletsChanged(); - if (!m_offline) + if (!m_offline && !m_deviceOffline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. return new NewWalletConfig(wallet); @@ -1239,7 +1252,7 @@ NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, cons derivationPath, startHeight, /* isImport = */ true, electrumFormat ? HDMasterKey::ElectrumMnemonic : HDMasterKey::BIP39Mnemonic); emit walletsChanged(); - if (!m_offline) + if (!m_offline && !m_deviceOffline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. return new NewWalletConfig(wallet); @@ -1358,7 +1371,7 @@ NewWalletConfig* FloweePay::createNewBasicWallet(const QString &walletName) auto wallet = createWallet(walletName); wallet->createNewPrivateKey(walletStartHeightHint()); emit walletsChanged(); - if (!m_offline) + if (!m_offline && !m_deviceOffline) p2pNet()->addAction(); return new NewWalletConfig(wallet); } @@ -1402,7 +1415,7 @@ NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const auto mnemonic = m_hdSeedValidator.generateMnemonic(seed, "en"); wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint(), /* isImport = */ false); emit walletsChanged(); - if (!m_offline) + if (!m_offline && !m_deviceOffline) p2pNet()->addAction(); // make sure that we get peers for the new wallet. return new NewWalletConfig(wallet); diff --git a/src/FloweePay.h b/src/FloweePay.h index 1447aee..2b91ef3 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -78,6 +78,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) Q_PROPERTY(bool offline READ isOffline CONSTANT) + Q_PROPERTY(bool deviceOffline READ deviceOffline WRITE setDeviceOffline NOTIFY deviceOfflineChanged FINAL) Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) public: @@ -433,6 +434,9 @@ public: */ bool lockFailed() const; + bool deviceOffline() const; + void setDeviceOffline(bool newDeviceOffline); + signals: void loadComplete(); /// \internal @@ -461,6 +465,8 @@ signals: void notificationChanged(); + void deviceOfflineChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -517,7 +523,8 @@ private: bool m_hideBalance = false; /// Show the BCH amount involved in a transaction on the activity screen. bool m_activityShowsBch = false; - bool m_offline = false; + bool m_offline = false; // user wants to run the app offline + bool m_deviceOffline = false; // the platform reports we have no network bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true bool m_lockFailed = false; diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 31292cb..f8b8f72 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -17,20 +17,71 @@ */ #include "FloweePay.h" #include "IndexerServices.h" +#include "PriceDataProvider.h" #include -# include +#include +#include + + +static QTimer *g_checkOfflineTimer = nullptr; + +namespace { +void checkOnline() { + QJniEnvironment env; + jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); + const jboolean rc = QJniObject::callStaticMethod(floweeNetworks, + "isInternetAvailable", "(Landroid/content/Context;)Z", + QJniObject(QNativeInterface::QAndroidApplication::context())); + auto *fp = FloweePay::instance(); + const bool wasOffline = fp->deviceOffline(); + const bool isOffline = JNI_FALSE == rc; + fp->setDeviceOffline(isOffline); + if (wasOffline && !isOffline) { // came online + fp->startNet(); + fp->prices()->start(); + } + if (!isOffline && g_checkOfflineTimer) // we're online. Stop the timer + g_checkOfflineTimer->stop(); +} +} void FloweePay::setupPlatform() { + assert(m_downloadManager.get()); + // ask the Android system which interfaces there are; + QJniEnvironment env; + jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); + quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); + if (flags != 0) { + logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; + auto &addressDb = m_downloadManager->connectionManager().peerAddressDb(); + addressDb.setSupportIPv4Net((flags & 1) == 1); + addressDb.setSupportIPv6Net((flags & 2) == 2); + } + checkOnline(); + + auto guiApp = qobject_cast(QCoreApplication::instance()); + if (guiApp == nullptr) { + // this is headless. + return; + } + + assert(QThread::currentThread() == thread()); // can't make timers otherwise + assert(!m_offline); // not available on Android + assert(!g_checkOfflineTimer); + g_checkOfflineTimer = new QTimer(this); + g_checkOfflineTimer->setTimerType(Qt::VeryCoarseTimer); + g_checkOfflineTimer->setInterval(7000); + connect (g_checkOfflineTimer, &QTimer::timeout, &checkOnline); + g_checkOfflineTimer->start(); // but that won't happen for another 7s. + // on Android, an app is either full screen (active) or inactive and not visible. // It is expected we save state as we move to inactive state in order to make the app // trivial to kill without loss of data. // The above 'aboutToQuit' will not be given enough CPU time to actually save much. - auto guiApp = qobject_cast(QCoreApplication::instance()); assert(guiApp); static QDateTime g_sleepStart; - connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { if (g_sleepStart.isNull()) { @@ -40,9 +91,13 @@ void FloweePay::setupPlatform() p2pNet()->saveData(); saveData(); m_indexerServices->save(); + + g_checkOfflineTimer->stop(); } } else if (state == Qt::ApplicationActive) { + g_checkOfflineTimer->start(); + checkOnline(); /* * We are brought back to the foreground. * @@ -55,7 +110,7 @@ void FloweePay::setupPlatform() * What we'll do is to start the actions again which will check up on our connections * and create new ones if we need them. */ - if (!m_offline && m_loadingCompleted) + if (!m_deviceOffline && m_loadingCompleted) p2pNet()->start(); if (m_appProtection == AppUnlocked @@ -67,17 +122,6 @@ void FloweePay::setupPlatform() } }); - assert(m_downloadManager.get()); - // ask the Android system which interfaces there are; - QJniEnvironment env; - jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); - quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); - if (flags != 0) { - logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; - auto &addressDb = m_downloadManager->connectionManager().peerAddressDb(); - addressDb.setSupportIPv4Net((flags & 1) == 1); - addressDb.setSupportIPv6Net((flags & 2) == 2); - } } bool fp_platformSkinDark() diff --git a/src/FloweePay_dummy.cpp b/src/FloweePay_dummy.cpp index 48d1664..6aac784 100644 --- a/src/FloweePay_dummy.cpp +++ b/src/FloweePay_dummy.cpp @@ -18,9 +18,8 @@ #include "FloweePay.h" // this file contains dummy implementations of -// platform specific code to describe the default -// behavior when no platform specific implementation -// was found +// platform code to describe the default behavior +// when no platform specific implementation was found bool fp_platformSkinDark() { -- 2.54.0 From 9c350493726ffe09426bee775da6da1ba1349721 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 19:29:29 +0100 Subject: [PATCH 1230/1428] Start new app 'pay-headless' this is a command line only application that syncs the wallets and then exits. --- CMakeLists.txt | 12 ++++++ src/Periodic.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++++++ src/Periodic.h | 38 +++++++++++++++++++ src/headless.cpp | 27 ++++++++++++++ src/main_utils.cpp | 4 +- 5 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/Periodic.cpp create mode 100644 src/Periodic.h create mode 100644 src/headless.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 525a6ac..51e9355 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,6 +328,18 @@ if (build_pay_tools) ${Boost_LIBRARIES} ) install(TARGETS blockheaders-meta-extractor DESTINATION bin) + + add_executable(pay-headless + src/Periodic.cpp + src/headless.cpp + ) + target_link_libraries(pay-headless PRIVATE pay_lib + flowee_p2p + flowee_utils + ${OPENSSL_LIBRARIES} + ${Boost_LIBRARIES} + ) + install(TARGETS pay-headless DESTINATION bin) endif() install(FILES guis/desktop/org.flowee.pay.desktop DESTINATION share/applications) diff --git a/src/Periodic.cpp b/src/Periodic.cpp new file mode 100644 index 0000000..3222f8a --- /dev/null +++ b/src/Periodic.cpp @@ -0,0 +1,92 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "Periodic.h" +#include "FloweePay.h" +#include "PriceDataProvider.h" +#include "Wallet.h" + +#include +#include + +struct CommandLineParserData; +std::unique_ptr handleStaticChain(CommandLineParserData *cld); + +Periodic::Periodic() + : m_verifyHandle(new ECCVerifyHandle()) +{ + ECC_Start(); // Init crypto lib. +} + +Periodic::~Periodic() +{ + ECC_Stop(); +} + +int Periodic::run() +{ + int argc = 0; + char *argv[] = { nullptr }; + QCoreApplication qapp(argc, argv); + qapp.setOrganizationName("flowee"); + qapp.setApplicationName("pay"); + + auto app = FloweePay::instance(); + if (app->lockFailed()) + return 1; + + auto blockheaders = handleStaticChain(nullptr); + QObject::connect(app, &FloweePay::loadComplete, app, [=]() { + auto app = FloweePay::instance(); + if (app->deviceOffline()) { + QCoreApplication::quit(); + return; + } + // for each wallet check if it is up to date yet. + for (Wallet *wallet : app->wallets()) { + QObject::connect(wallet, &Wallet::lastBlockSynchedChanged, + this, &Periodic::checkFinished, Qt::QueuedConnection); + } + QObject::connect(app, &FloweePay::blockHeightCertaintyChanged, + this, &Periodic::checkFinished); + app->startNet(); // lets go! + app->prices()->start(); + + }); + FloweePay::instance()->startP2PInit(); + + return qapp.exec(); +} + +void Periodic::checkFinished() +{ + auto *app = FloweePay::instance(); + if (app->blockHeightCertainty() != FloweePay::Certain) + return; + bool done = true; + for (auto *wallet : app->wallets()) { + if (wallet->segment()->priority() == PrivacySegment::OnlyManual) + continue; + assert(wallet->segment()); + auto lbs = wallet->segment()->lastBlockSynched(); + if (lbs != -2 // special value for just created wallet + && app->chainHeight() != lbs) + done = false; + } + if (done) + QCoreApplication::quit(); +} diff --git a/src/Periodic.h b/src/Periodic.h new file mode 100644 index 0000000..6b4f0c9 --- /dev/null +++ b/src/Periodic.h @@ -0,0 +1,38 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef PERIODIC_H +#define PERIODIC_H + +#include +#include // for ECC_Start() +#include + +class Periodic : public QObject +{ +public: + Periodic(); + ~Periodic(); + + int run(); + +private: + void checkFinished(); + std::unique_ptr m_verifyHandle; +}; + +#endif diff --git a/src/headless.cpp b/src/headless.cpp new file mode 100644 index 0000000..fe19701 --- /dev/null +++ b/src/headless.cpp @@ -0,0 +1,27 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +int main(int, char **) { + Periodic periodic; + return periodic.run(); +} + +class ModuleManager; +// no modules in the periodic, so lets not load any +void load_all_modules(ModuleManager*) { } diff --git a/src/main_utils.cpp b/src/main_utils.cpp index d1e59ec..41f8903 100644 --- a/src/main_utils.cpp +++ b/src/main_utils.cpp @@ -133,10 +133,10 @@ std::unique_ptr handleStaticChain(CommandLineParserData *cld) { std::unique_ptr blockheaders; // pointer to own the memmapped blockheaders file. blockheaders.reset(new QFile(QString("/usr/share/floweepay/") - + (cld->chain == P2PNet::MainChain ? "blockheaders" : "blockheaders-testnet4"))); + + ((!cld || cld->chain == P2PNet::MainChain) ? "blockheaders" : "blockheaders-testnet4"))); #ifndef NDEBUG // override only available in debug mode - if (cld->parser.isSet(cld->headers)) { + if (cld && cld->parser.isSet(cld->headers)) { QFileInfo info(cld->parser.value(cld->headers)); if (info.exists()) { if (info.isDir()) -- 2.54.0 From 7f28284b35b74c5799bd30f114c683462a7b6421 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 19:30:16 +0100 Subject: [PATCH 1231/1428] Remove unneeded include This was added by default by the Qt tools, making me ship 4MB too much without noticing for too long. Stop shipping androidx classes. --- android/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 0c88b42..414ba44 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -18,7 +18,6 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation 'androidx.core:core:1.13.1' } android { -- 2.54.0 From ef2309fbe869680dcfa457b36cf08cca200772b7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 19:31:36 +0100 Subject: [PATCH 1232/1428] Allow TextButton to have a 'value' shown. --- guis/mobile/TextButton.qml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index 25b44d6..d4e62c1 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -45,6 +45,8 @@ Item { /// Add an icon before the label property string iconSource: "" + property var currentValue: undefined + Image { id: icon visible: root.iconSource !== "" @@ -67,6 +69,24 @@ Item { elide: Text.ElideRight color: enabled ? palette.windowText : palette.brightText } + Flowee.Label { + id: currentValueLabel + y: label.y + text: { + if (typeof(root.currentValue) == "undefined") + return ""; + if (typeof(root.currentValue) == "boolean") + return root.currentValue ? qsTr("Enabled") : qsTr("Disabled") + return "" + root.currentValue; + } + x: { + var x = parent.width - contentWidth + if (rightIcon.item != null) + x -= rightIcon.width + 10; + return x; + } + color: enabled ? palette.midlight : palette.brightText + } NewIndicator { id: newIndicator buddy: label -- 2.54.0 From cf9c3f7191c11e2f85c7573bf7d8a426802b4770 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 19:32:10 +0100 Subject: [PATCH 1233/1428] Move the explore down Since we have an explore tab, we can make this one be much less visible. --- src/MenuModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index a46edc0..7d85a56 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -24,11 +24,11 @@ MenuModel::MenuModel(ModuleManager *mm) m_moduleManager(mm) { assert(mm); - m_baseItems.append({tr("Explore"), "./ExploreModules.qml", 0}); m_baseItems.append({tr("Settings"), "./GuiSettings.qml", 0}); m_baseItems.append({tr("Security"), "./LockApplication.qml", 0}); m_baseItems.append({tr("About"), "./About.qml", 0}); m_baseItems.append({tr("Wallets"), "AccountsList.qml", 32948}); + m_baseItems.append({tr("Explore"), "./ExploreModules.qml", 0}); initData(); connect (m_moduleManager, &ModuleManager::mainMenuSectionsChanged, this, [=]() { -- 2.54.0 From a85f5e7894244ddbacfb7766b984ceb7aa03b433 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 19:35:00 +0100 Subject: [PATCH 1234/1428] Add background sync for Android On Android this enables the application to run just a simple quick sync and then exit the app on regular intervals. The main benefit is that when the user doesn't run the app very often they will avoid having a long sync time. --- android/AndroidManifest.xml | 11 +++ android/java/org/flowee/pay/MainActivity.java | 45 ++++++++++- .../java/org/flowee/pay/PayNotifications.java | 21 ++++- .../java/org/flowee/pay/PeriodicService.java | 45 +++++++++++ guis/mobile.qrc | 1 + guis/mobile/BackgroundSyncConfig.qml | 74 ++++++++++++++++++ guis/mobile/GuiSettings.qml | 77 +++++++++++-------- src/FloweePay.cpp | 42 ++++++++++ src/FloweePay.h | 16 +++- src/FloweePay_android.cpp | 37 ++++++++- src/main.cpp | 7 ++ 11 files changed, 335 insertions(+), 41 deletions(-) create mode 100644 android/java/org/flowee/pay/PeriodicService.java create mode 100644 guis/mobile/BackgroundSyncConfig.qml diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index e1d4add..9fdca63 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + + + + + + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 6c505af..5eccd21 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,12 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtActivity; import android.os.Bundle; import android.content.Intent; +import android.net.Uri; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.provider.Settings; +import android.os.SystemClock; + public class MainActivity extends QtActivity { @@ -32,6 +38,42 @@ public class MainActivity extends QtActivity filterAndForward(getIntent()); } + public void enableRegularUpdates(int hours) + { + if (updatesInterval == hours) + return; + updatesInterval = hours; + AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); + if (alarmManager == null) + return; + + // They can be scheduled, but they won't do anything unless the + // user approves the previous ask. + Intent serviceIntent = new Intent(this, PeriodicService.class); + serviceIntent.setPackage("org.flowee.pay"); + PendingIntent pendingIntent = PendingIntent.getService(this, 9875, + serviceIntent, PendingIntent.FLAG_IMMUTABLE); + + if (hours < 1) { + alarmManager.cancel(pendingIntent); + return; + } + + long HourInMillis = 60 * 60 * 1000; + // Schedule to run approximately every \a hours hour. + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME_WAKEUP, // Use elapsed time, wake device if asleep + SystemClock.elapsedRealtime() + hours * HourInMillis, // first run + hours * HourInMillis, // Repeat interval + pendingIntent + ); + + // ask to run in the background + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + } + @Override protected void onNewIntent(Intent intent) { @@ -55,4 +97,5 @@ public class MainActivity extends QtActivity } } + private int updatesInterval = -1; // in hours } diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index 497128d..189b894 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,7 +32,7 @@ public class PayNotifications { private static final int BlockNotificationId = 9839874; private static PayNotifications g_singleton = null; - private static PayNotifications instance(Context context) { + public static PayNotifications instance(Context context) { if (g_singleton == null) g_singleton = new PayNotifications(context); return g_singleton; @@ -41,6 +41,7 @@ public class PayNotifications private Notification.Builder m_blockMessageBuilder = null; private NotificationManager m_notificationManager = null; private String m_blockChannelId = null; + private String m_bgSyncChannelId = null; private Vector m_blockFoundMessages = new Vector(); private int m_walletMessageId = 1; @@ -55,7 +56,6 @@ public class PayNotifications PayNotifications.instance(null).notifyBlock_priv(message); } - public PayNotifications(Context context) { // see example here on how to make these translatable: // https://developer.android.com/develop/ui/views/notifications/build-notification#java @@ -65,6 +65,11 @@ public class PayNotifications m_notificationManager.createNotificationChannel(newBlocksChannel); m_blockChannelId = newBlocksChannel.getId(); m_blockMessageBuilder = new Notification.Builder(context, m_blockChannelId); + + NotificationChannel bgChannel = new NotificationChannel("bgsync", "Sync", + NotificationManager.IMPORTANCE_LOW); + m_notificationManager.createNotificationChannel(bgChannel); + m_bgSyncChannelId = bgChannel.getId(); } private void notifyBlock_priv(String message) { @@ -120,4 +125,14 @@ public class PayNotifications // notification in the air of this type. m_notificationManager.notify(BlockNotificationId, m_blockMessageBuilder.build()); } + + public Notification buildBackgroundNotification(Context context) { + Notification.Builder builder = new Notification.Builder(context, m_bgSyncChannelId); + builder.setContentTitle("Background Updator") + .setSmallIcon(R.drawable.icon) + .setContentText("Wallet sync") + .setColor(0xA0F87) // flowee blue + .setOngoing(true); + return builder.build(); + } } diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java new file mode 100644 index 0000000..83a8ad5 --- /dev/null +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.flowee.pay; + +import android.os.Build; +import android.content.Intent; +import android.app.Notification; +import android.content.pm.ServiceInfo; +import org.qtproject.qt.android.bindings.QtService; + +public class PeriodicService extends QtService { + private static final int NotificationId = 23614; + + @Override + public void onCreate() { + super.onCreate(); + PayNotifications notificationManager = PayNotifications.instance(this); + Notification longRun = notificationManager.buildBackgroundNotification(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) + startForeground(NotificationId, longRun, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); + else + startForeground(NotificationId, longRun); + } + + @Override + public void onDestroy() { + super.onDestroy(); + stopForeground(true); + } +} diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6118068..d2552ca 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -60,6 +60,7 @@ mobile/AccountSelectorWidget.qml mobile/AccountsList.qml mobile/AccountSyncState.qml + mobile/BackgroundSyncConfig.qml mobile/CurrencySelector.qml mobile/EditableLabel.qml mobile/ExploreModules.qml diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml new file mode 100644 index 0000000..c0cbbed --- /dev/null +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -0,0 +1,74 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls as QQC2 +import QtQuick.Layouts +import "../Flowee" as Flowee + +Page { + headerText: qsTr("Background updates") + id: root + + Column { + width: parent.width + spacing: 10 + + Flowee.Label { + width: parent.width + text: qsTr("Without background updates your wallets will not see new transactions until you open Flowee Pay") + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + + Flowee.CheckBox { + id: checkbox + text: qsTr("Allow Background Updates") + checked: Pay.backgroundUpdates + onCheckedChanged: Pay.backgroundUpdates = checked + } + + Item { width: 1; height: 10 } // spacer + + QQC2.Slider { + id: slider + property int timeSpan: { + var i = slider.value; + if (i < 4) { + for (var h = 48; i > 0; --i) { + h = h / 2; + } + return h; + } + return 6 - (i - 3); + } + onTimeSpanChanged: Pay.backgroundUpdateInterval = timeSpan + + visible: checkbox.checked + width: parent.width + stepSize: 1 + from: 0 + value: 3 + to: 8 + snapMode: QQC2.Slider.SnapAlways + } + Flowee.Label { + visible: checkbox.checked + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Every %1 hours").arg(slider.timeSpan) + } + } +} diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/GuiSettings.qml index a7736c6..1ed36c8 100644 --- a/guis/mobile/GuiSettings.qml +++ b/guis/mobile/GuiSettings.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -72,31 +72,24 @@ Page { } PageTitledBox { - title: qsTr("Dark Theme") - Flowee.RadioButton { - text: "Follow System" - checked: Pay.skinFollowsPlatform - onClicked: Pay.skinFollowsPlatform = true; + title: qsTr("Main View") + visible: Pay.isMainChain // because we only have one option right now + + Flowee.CheckBox { width: parent.width + text: qsTr("Show Bitcoin Cash value") + checked: Pay.activityShowsBch + onCheckedChanged: Pay.activityShowsBch = checked + visible: Pay.isMainChain // only mainchain has fiat value } - Flowee.RadioButton { - text: "Dark" - checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin - width: parent.width - onClicked: { - Pay.skinFollowsPlatform = false; - Pay.useDarkSkin = true; - } - } - Flowee.RadioButton { - text: "Light" - checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin - width: parent.width - onClicked: { - Pay.skinFollowsPlatform = false; - Pay.useDarkSkin = false; - } - } + } + + TextButton { + text: qsTr("Background Updates") + subtext: qsTr("Keep your walles synchronized by enabling this") + currentValue: Pay.backgroundUpdates + pageButton: true + onClicked: thePile.push("./BackgroundSyncConfig.qml") } PageTitledBox { @@ -160,24 +153,40 @@ Page { } TextButton { - Layout.fillWidth: true - text: qsTr("Change Currency (%1)").arg(Fiat.currencyName) + text: qsTr("Change Currency"); + currentValue: Fiat.currencyName pageButton: true onClicked: thePile.push("./CurrencySelector.qml") } PageTitledBox { - title: qsTr("Main View") - visible: Pay.isMainChain // because we only have one option right now - - Flowee.CheckBox { + title: qsTr("Dark Theme") + Flowee.RadioButton { + text: "Follow System" + checked: Pay.skinFollowsPlatform + onClicked: Pay.skinFollowsPlatform = true; width: parent.width - text: qsTr("Show Bitcoin Cash value") - checked: Pay.activityShowsBch - onCheckedChanged: Pay.activityShowsBch = checked - visible: Pay.isMainChain // only mainchain has fiat value + } + Flowee.RadioButton { + text: "Dark" + checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin + width: parent.width + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = true; + } + } + Flowee.RadioButton { + text: "Light" + checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin + width: parent.width + onClicked: { + Pay.skinFollowsPlatform = false; + Pay.useDarkSkin = false; + } } } + } } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d7cbf37..5093d6f 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -72,6 +72,8 @@ constexpr const char *DSPTIMEOUT = "payment/dsp-timeout"; constexpr const char *CURRENCY_COUNTRIES = "countryCodes"; // historical constexpr const char *CURRENCY_COUNTRY = "countryCode"; // current constexpr const char *PRIVATE_MODE = "private-mode"; +constexpr const char *BACKGROUND_GROUP = "background"; +constexpr const char *BG_INTERVAL = "interval"; constexpr const char *AppdataFilename = "/appdata"; // used for the default wallet @@ -185,6 +187,10 @@ FloweePay::FloweePay() m_privateMode = appConfig.value(PRIVATE_MODE, false).toBool(); m_prices.reset(new PriceDataProvider(appConfig.value(CURRENCY_COUNTRY).toString())); m_unlockingKeyboard = static_cast(appConfig.value(KEYBOARD4UNLOCK, m_unlockingKeyboard).toInt()); + appConfig.beginGroup(BACKGROUND_GROUP); + m_backgroundUpdates = appConfig.value("enabled", false).toBool(); + m_backgroundUpdateInterval = appConfig.value(BG_INTERVAL, 6).toInt(); + appConfig.endGroup(); // Update expected chain-height every 5 minutes QTimer *timer = new QTimer(this); @@ -909,6 +915,42 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +int FloweePay::backgroundUpdateInterval() const +{ + return m_backgroundUpdateInterval; +} + +void FloweePay::setBackgroundUpdateInterval(int hours) +{ + if (m_backgroundUpdateInterval == hours) + return; + m_backgroundUpdateInterval = hours; + emit backgroundUpdateIntervalChanged(); + QSettings appConfig; + appConfig.beginGroup(BACKGROUND_GROUP); + appConfig.setValue(BG_INTERVAL, hours); +} + +// notice, setter is in platform specific file. +bool FloweePay::backgroundUpdates() const +{ + return m_backgroundUpdates; +} + +void FloweePay::setBackgroundUpdates(bool on) +{ + if (m_backgroundUpdates == on) + return; + m_backgroundUpdates = on; + emit backgroundUpdatesChanged(); + + QSettings appConfig; + appConfig.beginGroup(BACKGROUND_GROUP); + appConfig.setValue("enabled", on); + appConfig.endGroup(); + +} + bool FloweePay::deviceOffline() const { return m_deviceOffline; diff --git a/src/FloweePay.h b/src/FloweePay.h index 2b91ef3..3762e96 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -79,6 +79,10 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) Q_PROPERTY(bool offline READ isOffline CONSTANT) Q_PROPERTY(bool deviceOffline READ deviceOffline WRITE setDeviceOffline NOTIFY deviceOfflineChanged FINAL) + /// if true, supported platforms will try to update also when the app isn't running. + Q_PROPERTY(bool backgroundUpdates READ backgroundUpdates WRITE setBackgroundUpdates NOTIFY backgroundUpdatesChanged FINAL) + /// In hours. Ignored if backgroundUpdates is false. + Q_PROPERTY(int backgroundUpdateInterval READ backgroundUpdateInterval WRITE setBackgroundUpdateInterval NOTIFY backgroundUpdateIntervalChanged FINAL) Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) public: @@ -437,6 +441,12 @@ public: bool deviceOffline() const; void setDeviceOffline(bool newDeviceOffline); + bool backgroundUpdates() const; + void setBackgroundUpdates(bool on); + + int backgroundUpdateInterval() const; + void setBackgroundUpdateInterval(int hours); + signals: void loadComplete(); /// \internal @@ -462,10 +472,10 @@ signals: void blockHeightCertaintyChanged(); void internal_heightCertaintyChanged(); // not thread-safe void unlockingKeyboardChanged(); - void notificationChanged(); - void deviceOfflineChanged(); + void backgroundUpdatesChanged(); + void backgroundUpdateIntervalChanged(); private slots: void loadingCompleted(); @@ -528,6 +538,8 @@ private: bool m_gotHeadersSyncedOnce = false; bool m_privateMode = false; // wallets marked private are hidden when true bool m_lockFailed = false; + bool m_backgroundUpdates = false; + int m_backgroundUpdateInterval = 6; // in hours friend class AccountConfig; }; diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index f8b8f72..51520c9 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -23,8 +23,8 @@ #include #include - static QTimer *g_checkOfflineTimer = nullptr; +static QTimer *g_updateBackgroundTimer = nullptr; namespace { void checkOnline() { @@ -44,6 +44,38 @@ void checkOnline() { if (!isOffline && g_checkOfflineTimer) // we're online. Stop the timer g_checkOfflineTimer->stop(); } + +void backgroundUpdaterToggled() +{ + int h = 0; // zero is effectively a cancel + auto *pay = FloweePay::instance(); + if (pay->backgroundUpdates()) + h = pay->backgroundUpdateInterval(); + auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); + main.callObjectMethod("enableRegularUpdates", "(I)V", h); +} + +void followBGSettings() +{ + if (g_updateBackgroundTimer == nullptr) { + g_updateBackgroundTimer = new QTimer(FloweePay::instance()); + g_updateBackgroundTimer->setTimerType(Qt::CoarseTimer); + g_updateBackgroundTimer->setInterval(45000); + QObject::connect (g_updateBackgroundTimer, &QTimer::timeout, FloweePay::instance(), [=]() { + auto *pay = FloweePay::instance(); + int h = 0; // zero is effectively a cancel + if (pay->backgroundUpdates()) + h = pay->backgroundUpdateInterval(); + auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); + main.callObjectMethod("enableRegularUpdates", "(I)V", h); + g_updateBackgroundTimer->deleteLater(); + g_updateBackgroundTimer = nullptr; + }); + } + g_updateBackgroundTimer->stop(); + g_updateBackgroundTimer->start(); +} + } void FloweePay::setupPlatform() @@ -122,6 +154,9 @@ void FloweePay::setupPlatform() } }); + connect (this, &FloweePay::backgroundUpdateIntervalChanged, &followBGSettings); + connect (this, &FloweePay::backgroundUpdatesChanged, &backgroundUpdaterToggled); + backgroundUpdaterToggled(); // make sure that we tell the android side. } bool fp_platformSkinDark() diff --git a/src/main.cpp b/src/main.cpp index 1abde17..f0550e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,6 +69,7 @@ struct ECC_State } #ifdef TARGET_OS_Android +# include "Periodic.h" # include "main_utils_android.cpp" #else #include "UserIntent.h" @@ -81,6 +82,12 @@ void handleLocalQml(QQmlApplicationEngine &engine); int main(int argc, char *argv[]) { +#ifdef TARGET_OS_Android + if (argc > 1 && strcmp(argv[1], "--headless") == 0) { + Periodic periodic; + return periodic.run(); + } +#endif QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); -- 2.54.0 From 16ef0e568aaa836e29cd8d9217566761350ad312 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 2 Mar 2025 20:28:04 +0100 Subject: [PATCH 1235/1428] Move package name --- android/AndroidManifest.xml | 2 +- android/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 9fdca63..58bc14c 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ diff --git a/android/build.gradle b/android/build.gradle index 414ba44..08e141c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -35,7 +35,7 @@ android { * Changing them manually might break the compilation! *******************************************************/ - namespace androidPackageName + namespace 'org.flowee.pay.test' compileSdkVersion androidCompileSdkVersion buildToolsVersion androidBuildToolsVersion ndkVersion androidNdkVersion -- 2.54.0 From 3991869dfa4cd71ee6c53ecde81f937b2e2c8149 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 13:42:36 +0100 Subject: [PATCH 1236/1428] Stop the service afterwards --- android/java/org/flowee/pay/PeriodicService.java | 7 ++++++- src/FloweePay_android.cpp | 13 ++++++------- src/main.cpp | 5 ++++- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index 83a8ad5..bba98e5 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -37,9 +37,14 @@ public class PeriodicService extends QtService { startForeground(NotificationId, longRun); } + // called from C++ + public void done() { + stopForeground(STOP_FOREGROUND_REMOVE); + stopSelf(); + } + @Override public void onDestroy() { super.onDestroy(); - stopForeground(true); } } diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 51520c9..0bdbd2c 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -23,10 +23,10 @@ #include #include +namespace { static QTimer *g_checkOfflineTimer = nullptr; static QTimer *g_updateBackgroundTimer = nullptr; -namespace { void checkOnline() { QJniEnvironment env; jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); @@ -47,12 +47,10 @@ void checkOnline() { void backgroundUpdaterToggled() { - int h = 0; // zero is effectively a cancel auto *pay = FloweePay::instance(); - if (pay->backgroundUpdates()) - h = pay->backgroundUpdateInterval(); auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); - main.callObjectMethod("enableRegularUpdates", "(I)V", h); + main.callObjectMethod("enableRegularUpdates", "(I)V", + pay->backgroundUpdates() ? pay->backgroundUpdateInterval() : 0); } void followBGSettings() @@ -60,7 +58,7 @@ void followBGSettings() if (g_updateBackgroundTimer == nullptr) { g_updateBackgroundTimer = new QTimer(FloweePay::instance()); g_updateBackgroundTimer->setTimerType(Qt::CoarseTimer); - g_updateBackgroundTimer->setInterval(45000); + g_updateBackgroundTimer->setInterval(2000); QObject::connect (g_updateBackgroundTimer, &QTimer::timeout, FloweePay::instance(), [=]() { auto *pay = FloweePay::instance(); int h = 0; // zero is effectively a cancel @@ -156,7 +154,8 @@ void FloweePay::setupPlatform() connect (this, &FloweePay::backgroundUpdateIntervalChanged, &followBGSettings); connect (this, &FloweePay::backgroundUpdatesChanged, &backgroundUpdaterToggled); - backgroundUpdaterToggled(); // make sure that we tell the android side. + if (!FloweePay::instance()->deviceOffline()) + backgroundUpdaterToggled(); // make sure that we tell the android side. } bool fp_platformSkinDark() diff --git a/src/main.cpp b/src/main.cpp index f0550e5..5c9d4ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,7 +85,10 @@ int main(int argc, char *argv[]) #ifdef TARGET_OS_Android if (argc > 1 && strcmp(argv[1], "--headless") == 0) { Periodic periodic; - return periodic.run(); + int rc = periodic.run(); + QJniObject(QNativeInterface::QAndroidApplication::context()) + .callObjectMethod("done", "()V"); + return rc; } #endif QGuiApplication qapp(argc, argv); -- 2.54.0 From 040ae97e8f7729ab5f49456c5149b14fdb9ab443 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 13:47:34 +0100 Subject: [PATCH 1237/1428] Improve readability of coin selector --- guis/desktop/SendTransactionPane.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index d70498d..6937f4e 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -601,6 +601,8 @@ Item { anchors.right: cfIcon.visible ? cfIcon.left : parent.right // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 + colorize: false + fiatWidth: 90 } Flowee.Label { id: ageLabel -- 2.54.0 From 7265cdc11e5bf8e509acd9e917491960141dc69c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 13:54:21 +0100 Subject: [PATCH 1238/1428] Make checkbox look less bloated Additionally, make the touch area bigger. --- guis/Flowee/CheckBox.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 5b7d240..32b9914 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -27,7 +27,6 @@ T.CheckBox { implicitWidth: indicator.width + contentItem.implicitWidth + (toolTipText === "" ? 0 : (questionMarkIcon.width + 10)) implicitHeight: Math.max(indicator.implicitHeight, contentItem.implicitHeight) - clip: true spacing: 6 indicator: Item { @@ -35,7 +34,9 @@ T.CheckBox { implicitWidth: implicitHeight * 2.1 Rectangle { - anchors.fill: parent + width: parent.width + height: 16 + anchors.verticalCenter: parent.verticalCenter radius: parent.height / 3 color: { if (control.sliderOnIndicator && control.enabled && control.checked) -- 2.54.0 From 7da8bac289aa10fba77301bbe38b3483bc0f7dfc Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 13:58:05 +0100 Subject: [PATCH 1239/1428] Rename qml file --- guis/mobile.qrc | 2 +- guis/mobile/{GuiSettings.qml => Settings.qml} | 0 src/MenuModel.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename guis/mobile/{GuiSettings.qml => Settings.qml} (100%) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index d2552ca..6fff9cb 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -65,7 +65,7 @@ mobile/EditableLabel.qml mobile/ExploreModules.qml mobile/FilterPopup.qml - mobile/GuiSettings.qml + mobile/Settings.qml mobile/ImportWalletPage.qml mobile/InstaPayConfigButton.qml mobile/InstaPayConfigPage.qml diff --git a/guis/mobile/GuiSettings.qml b/guis/mobile/Settings.qml similarity index 100% rename from guis/mobile/GuiSettings.qml rename to guis/mobile/Settings.qml diff --git a/src/MenuModel.cpp b/src/MenuModel.cpp index 7d85a56..d59c7a0 100644 --- a/src/MenuModel.cpp +++ b/src/MenuModel.cpp @@ -24,7 +24,7 @@ MenuModel::MenuModel(ModuleManager *mm) m_moduleManager(mm) { assert(mm); - m_baseItems.append({tr("Settings"), "./GuiSettings.qml", 0}); + m_baseItems.append({tr("Settings"), "./Settings.qml", 0}); m_baseItems.append({tr("Security"), "./LockApplication.qml", 0}); m_baseItems.append({tr("About"), "./About.qml", 0}); m_baseItems.append({tr("Wallets"), "AccountsList.qml", 32948}); -- 2.54.0 From 52642a4b3e8ed47cea2905f33309b73831508ce7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 14:10:09 +0100 Subject: [PATCH 1240/1428] Show if it is enabled or not --- guis/mobile/InstaPayConfigButton.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index 6167b3d..2d78a3b 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ TextButton { text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") property int limit: 0 + currentValue: root.account.allowInstaPay function updateLimit() { limit = root.account.fiatInstaPayLimit(Fiat.currencyName); -- 2.54.0 From 40b3c58de6850bed8649180975ca3fb679e92c97 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 14:10:34 +0100 Subject: [PATCH 1241/1428] Make the unlock screen move to the price input This makes the flow a bit smoother for the people that want to use this as a point of sale system. --- guis/mobile/ReceiveTab.qml | 1 + guis/mobile/UnlockApplication.qml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 22e8e62..31dcb64 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -28,6 +28,7 @@ FocusScope { property QtObject account: portfolio.current property bool qrViewActive: true; property bool editViewActive: false + property alias verticalTab: swipeView.currentIndex focus: true PaymentRequest { diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 46ca660..a04ee86 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -122,7 +122,10 @@ FocusScope { Component { id: receive Page { - function takeFocus() { receiveTab.forceActiveFocus(); } + function takeFocus() { + receiveTab.verticalTab = 1; // the money input + receiveTab.forceActiveFocus(); + } backHandler: function handler() { myLittleStack.clear() } headerText: receiveTab.title function switchToTab(tab) { -- 2.54.0 From 61b85f3a4dc2e1ddf706758eb40d9b8a888cf47d Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 14:16:40 +0100 Subject: [PATCH 1242/1428] Close virtual keyboard on receive --- guis/mobile/ReceiveTab.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 31dcb64..e513f3f 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -288,6 +288,14 @@ FocusScope { enabled: opacity > 0 visible: opacity > 0 + onVisibleChanged: { + if (visible) { + // move it to the screen that doesn't have a textfield + // to ensure that the virtual keyboard gets closed. + swipeView.currentIndex = 0; + } + } + // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) Flowee.Label { -- 2.54.0 From 2524a8e67532c7b6546c76e6bd9a9476416eca54 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 15:32:45 +0100 Subject: [PATCH 1243/1428] Add 'confetti' on detecting a receive. --- guis/mobile.qrc | 1 + guis/mobile/ReceiveTab.qml | 36 ++++++++++++++++++++++++++++++++++-- guis/mobile/images/star.png | Bin 0 -> 409 bytes 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 guis/mobile/images/star.png diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 6fff9cb..ffe3588 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -48,6 +48,7 @@ mobile/images/more.svg mobile/images/more-light.svg mobile/images/play-button.svg + mobile/images/star.png mobile/main.qml mobile/defaults.ini ControlColors.js diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index e513f3f..3af3a83 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls as QQC2 +import QtQuick.Particles import "../Flowee" as Flowee import Flowee.org.pay @@ -86,7 +87,6 @@ FocusScope { opacity: enabled ? 1 : 0.4 } } - } } @@ -260,6 +260,7 @@ FocusScope { } } + // the "payment received" screen. Rectangle { id: feedback @@ -293,6 +294,7 @@ FocusScope { // move it to the screen that doesn't have a textfield // to ensure that the virtual keyboard gets closed. swipeView.currentIndex = 0; + emitter.pulse(1500) } } @@ -367,4 +369,34 @@ FocusScope { onClicked: feedback.hide = true; } } + + ParticleSystem { + anchors.fill: parent + ImageParticle { + source: "qrc:/star.png" + alpha: 0.3 + colorVariation: 1 + } + Gravity { + magnitude: 32 + angle: 90 + } + Emitter { + id: emitter + x: parent.width/2 + y: feedback.y + 20 + emitRate: 600 + lifeSpan: 20000 + lifeSpanVariation: 3000 + enabled: false + velocity: AngleDirection { + angle: 270 + angleVariation: 90 + magnitude: 64 + magnitudeVariation: 40 + } + size: 10 + sizeVariation: 6 + } + } } diff --git a/guis/mobile/images/star.png b/guis/mobile/images/star.png new file mode 100644 index 0000000000000000000000000000000000000000..68b779959c3392ad733f2abfbe70f68992ea7554 GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4i*Lm2K9!kZ43;I37#&FArg|T2fed`0~r`D z6u;lCR%!UGb=C}pWr19w0R=jug^$iO22X#=wX1ilOGI*bh*3{MVioA+CY!GVRrr1XtM#&Zq^7Y2uT_ocZj7#M^Yp3Gag&RWV$ z?+;4@3&XarUrV;y8l7TamdHDS!QuLe;OIQ>c`Itp#UC~lXo#syJg@ESEA1J3_STtl z`NQlUPYz9+9(Ox%rHM;x$BNYJziqgGFS%pz>B72gvszaKa5U*GxbVrs{<5c6c`479 zJ5BGTLlTlx-}QGIYUb$7 zd7}U9+n3O1Vi_8Li<2H1ym4k|_;W6r*>h9TGld!^1;&Pz%B8A#e+*a~SQrxLR6gE0 ztMjk{TLTM2OX0r4wO@357#mm^9$bp|4rgFsWKdB5zGY5ZF~bA Date: Mon, 3 Mar 2025 15:55:44 +0100 Subject: [PATCH 1244/1428] Fix unlock app keyboard focus This allows us to detect the android BACK button and act appropriately --- guis/mobile/UnlockApplication.qml | 15 ++++++++++----- guis/mobile/main.qml | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index a04ee86..9112fb3 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2023 Tom Zander + * Copyright (C) 2023-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ import "../Flowee" as Flowee import Flowee.org.pay; FocusScope { + id: root Rectangle { anchors.fill: parent color: palette.window @@ -49,9 +50,9 @@ FocusScope { assumeDarkBackground: Pay.unlockingKeyboard !== FloweePay.BigNumbersKeyboard } Keys.onPressed: (event)=> { - if (event.key !== Qt.Key_Back) { // exit app on 'back'. - event.accepted = true; // at all other key events. - } + event.accepted = true; + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) + mainWindow.close(); // exit app on 'back'. } Column { @@ -88,6 +89,8 @@ FocusScope { } } MouseArea { + id: quickReceiveButton + focus: true enabled: quickReceive.visible anchors.fill: quickReceive onClicked: myLittleStack.push(receive) @@ -110,8 +113,10 @@ FocusScope { } Keys.onPressed: (event)=> { if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { - if (myLittleStack.depth <= 1) + if (myLittleStack.depth <= 1) { myLittleStack.clear(); + quickReceiveButton.forceActiveFocus(); + } else myLittleStack.pop(); event.accepted = true; diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 6797ea8..1c24a05 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -155,6 +155,7 @@ QQC2.ApplicationWindow { Loader { source: Pay.appProtection === FloweePay.AppPassword ? "./UnlockApplication.qml" : "" anchors.fill: parent + onLoaded: item.forceActiveFocus(); } Keys.onPressed: (event)=> { -- 2.54.0 From 31e6dea3fd687e5ca74a43d2b54a0df0aca6a481 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 16:18:15 +0100 Subject: [PATCH 1245/1428] Fix scanning an unsupported QR causing a problem --- guis/mobile/PayWithQR.qml | 4 ++-- src/PaymentDetailOutput.cpp | 13 +++++++++---- src/PaymentDetailOutput_p.h | 7 +++++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 3b7373c..be0aaf9 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -69,7 +69,7 @@ Page { function start(paymentAddress) { let success = payment.pasteTargetAddress(paymentAddress); if (!success) { - scannedUrlFaultyDialog. + scannedUrlFaultyDialog.scanResult = paymentAddress scannedUrlFaultyDialog.open(); } @@ -119,7 +119,7 @@ Page { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - onClicked: detailsLabel.visible = true; + onClicked: detailsLabel.visible = !detailsLabel.visible } } Flowee.Label { diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index ff91f87..cc1ecc2 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2023 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -99,9 +99,9 @@ const QString &PaymentDetailOutput::address() const return m_address; } -void PaymentDetailOutput::setAddress(const QString &address_) +void PaymentDetailOutput::parseAddress(const QString &targetString) { - QString address = address_.trimmed(); + QString address = targetString.trimmed(); if (m_address == address) return; if (address.indexOf('?') >= 12) { @@ -109,10 +109,15 @@ void PaymentDetailOutput::setAddress(const QString &address_) Payment *p = qobject_cast(parent()); assert(p); if (p->paymentDetails().size() == 1) { // payment protocols only for sole-outputs - p->setTargetAddress(address); + p->pasteTargetAddress(address); return; } } + setAddress(address); +} + +void PaymentDetailOutput::setAddress(const QString &address) +{ m_address = address; m_outputScript.clear(); createFormattedAddress(); diff --git a/src/PaymentDetailOutput_p.h b/src/PaymentDetailOutput_p.h index 12cb641..6980644 100644 --- a/src/PaymentDetailOutput_p.h +++ b/src/PaymentDetailOutput_p.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2021 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ class PaymentDetailOutput : public PaymentDetail * We verify this string and fill \a formattedTarget only when this is a correct address. * Notice that a legacy address will only be seen as correct when the forceLegacyOk is true. */ - Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) + Q_PROPERTY(QString address READ address WRITE parseAddress NOTIFY addressChanged) Q_PROPERTY(double paymentAmount READ paymentAmount WRITE setPaymentAmount NOTIFY paymentAmountChanged) Q_PROPERTY(qint64 paymentAmountFiat READ paymentAmountFiat WRITE setPaymentAmountFiat NOTIFY paymentAmountFiatChanged) // cleaned up and re-formatted, empty if invalid. @@ -53,7 +53,10 @@ public: /// @see FloweePay::identifyString() /// @see setOutputScript() const QString &address() const; + /// simple direct setter for the address void setAddress(const QString &newAddress); + /// take a user input string and try to understand what it is. + void parseAddress(const QString &targetString); /// is non-empty if the address() is proper. const QString &formattedTarget() const; -- 2.54.0 From 3ab63d6dc59fb7e4a96b9ff00ad6031331420da1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 17:01:29 +0100 Subject: [PATCH 1246/1428] strings; Be much less technical --- modules/send-sweep/SendSweepModuleInfo.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/send-sweep/SendSweepModuleInfo.cpp b/modules/send-sweep/SendSweepModuleInfo.cpp index 60ad4e7..b1facdd 100644 --- a/modules/send-sweep/SendSweepModuleInfo.cpp +++ b/modules/send-sweep/SendSweepModuleInfo.cpp @@ -30,10 +30,10 @@ SendSweepModuleInfo::SendSweepModuleInfo() : m_actionTabItem(new ModuleSection(ModuleSection::OtherSectionTypeDefault, this)) { setId("sendSweepModule"); - setTitle(tr("Sweep & Send")); - setDescription(tr("Allows sweeping a paper-wallet, moving the contents to your own wallet")); + setTitle(tr("Claim Cash Stamp")); + setDescription(tr("A QR code with a CashStamp can be taken to transfer the money to your wallet.")); - m_actionTabItem->setText(tr("Sweep Paper Wallet")); + m_actionTabItem->setText(tr("Claim a Cash Stamp")); m_actionTabItem->setStartQMLFile("qrc:/send-sweep/StartScan.qml"); addSection(m_actionTabItem); -- 2.54.0 From 810a622e04673c8ad25f77bce92b89b3f1889159 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 17:38:38 +0100 Subject: [PATCH 1247/1428] Add slider styling --- guis/mobile/BackgroundSyncConfig.qml | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index c0cbbed..4ed923a 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -64,6 +64,41 @@ Page { value: 3 to: 8 snapMode: QQC2.Slider.SnapAlways + background: Rectangle { + x: slider.leftPadding + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: 200 + implicitHeight: 4 + width: slider.availableWidth + height: implicitHeight + radius: 2 + color: palette.button + + Repeater { + id: repeater + model: slider.to - slider.from - 1 + Rectangle { + width: 1.8 + height: 10 + color: palette.button + x: { + var offset = slider.handle.implicitWidth + var totalWidth = slider.width - slider.leftPadding - slider.rightPadding - offset; + var unit = totalWidth / (slider.to - slider.from); + return offset / 2 + unit + unit * modelData + } + y: 10 + } + } + } + handle: Rectangle { + x: slider.leftPadding + slider.visualPosition * (slider.availableWidth - width) + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + implicitWidth: slider.font.pixelSize + implicitHeight: implicitWidth + radius: implicitWidth / 2 + color: Pay.useDarkSkin ? slider.palette.windowText : slider.palette.highlight + } } Flowee.Label { visible: checkbox.checked -- 2.54.0 From 05781e42b6b14abc4fb6864ffeff7bb182e8c45c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 19:04:19 +0100 Subject: [PATCH 1248/1428] fix typo --- guis/mobile/Settings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index 1ed36c8..dc72cc6 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -86,7 +86,7 @@ Page { TextButton { text: qsTr("Background Updates") - subtext: qsTr("Keep your walles synchronized by enabling this") + subtext: qsTr("Keep your wallets synchronized by enabling this") currentValue: Pay.backgroundUpdates pageButton: true onClicked: thePile.push("./BackgroundSyncConfig.qml") -- 2.54.0 From 2d511fbe867e4c37c3808fb752f61f19ee1a2d5a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 19:38:28 +0100 Subject: [PATCH 1249/1428] Tweak 'light' color and usage This swaps the colors on the AccountHistory and ExploreModules rounded rects in order to make the background universally the same everywhere. To make it work the 'light' color on the light theme had to remove some of its contrast to the base color in order to not remove contrast to the text. --- guis/ControlColors.js | 2 +- guis/mobile/AccountHistory.qml | 8 ++++---- guis/mobile/ExploreModules.qml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index 1430d3f..d4d031b 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -58,7 +58,7 @@ function applyLightSkin(item) { item.palette.window = "#e0dfde"; item.palette.base = "#f0efee"; item.palette.alternateBase = "#e2e1e0"; - item.palette.light = "#dddcdb"; + item.palette.light = "#eae9e8"; item.palette.dark = "#353637"; item.palette.mid = "#bdbdbd"; item.palette.windowText = "#26282a"; diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 83c586c..3721b08 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -97,7 +97,7 @@ ListView { header of the listview. */ header: Rectangle { - color: palette.light + color: palette.base width: root.width height: column.height Column { @@ -183,7 +183,7 @@ ListView { height: label.height + 15 width: root.width Rectangle { - color: palette.light + color: palette.base anchors.fill: parent } Flowee.Label { @@ -207,7 +207,7 @@ ListView { // Is this transaction a 'move between addresses' tx. // This is a heuristic and not available in the model, which is why its in the view. property bool isMoved: Utils.isMoved(model); - color: palette.light + color: palette.base width: root.width height: 80 @@ -235,7 +235,7 @@ ListView { } radius: 20 - color: palette.base + color: palette.light border.width: 1 border.color: palette.midlight } diff --git a/guis/mobile/ExploreModules.qml b/guis/mobile/ExploreModules.qml index 482057d..ddf4185 100644 --- a/guis/mobile/ExploreModules.qml +++ b/guis/mobile/ExploreModules.qml @@ -24,7 +24,7 @@ Page { headerText: qsTr("Explore") background: Rectangle { - color: palette.light + color: palette.base } Item { @@ -49,7 +49,7 @@ Page { width: root.width - 30 height: 35 + titleLabel.height + statusField.height + Math.min(120, descriptionLabel.implicitHeight) radius: 20 - color: palette.base + color: palette.light border.width: 1 border.color: palette.midlight visible: modelData.hasUISections // has user-visible parts. -- 2.54.0 From 9ead6eea07ea539018a8edafcfc01318972aadcd Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 19:58:49 +0100 Subject: [PATCH 1250/1428] Improve UX Use the new TextButton feature better for current setting and make clear in the sub text which wallet this is for. On the send tab the InstaPayConfigButton is directly next to the default wallet, as such it may be assumed that the insta pay text applied to the name mentioned there. This lists the wallet here in order to make things clear and now lists the actual limit on the right side for consistency. --- guis/mobile/InstaPayConfigButton.qml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index 2d78a3b..73bf697 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -23,7 +23,12 @@ TextButton { text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") property int limit: 0 - currentValue: root.account.allowInstaPay + currentValue: { + root.account.allowInstaPay + if (!root.account.allowInstaPay) + return false; + return Fiat.formattedPrice(limit); + } function updateLimit() { limit = root.account.fiatInstaPayLimit(Fiat.currencyName); @@ -43,11 +48,9 @@ TextButton { } subtext: { - if (!root.account.allowInstaPay) - return qsTr("Fast payments for low amounts") - if (limit === 0) - return qsTr("Not configured"); - return qsTr("Limit set to: %1").arg(Fiat.formattedPrice(limit)); + if (root.account.allowsInstaPay) + return qsTr("Limits for %1", "argument is a name").arg(root.account.name); + return qsTr("Disabled for %1", "argument is a name").arg(root.account.name); } pageButton: true -- 2.54.0 From c3a993f45cf3e9695ac16ec336ba761dec6d039f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 21:35:00 +0100 Subject: [PATCH 1251/1428] Add request for block notifications. This is the only useful notification type we have today, but at least it allows the user to explicitly go and request notifications from the Android OS. --- android/java/org/flowee/pay/MainActivity.java | 27 ++++++++++++++++++- .../java/org/flowee/pay/PayNotifications.java | 24 ++++++++++++----- guis/mobile/Settings.qml | 10 +++++++ src/NotificationManager.cpp | 3 +++ src/NotificationManager.h | 1 + src/NotificationManager_android.cpp | 10 ++++--- src/NotificationManager_dbus.cpp | 3 +++ src/NotificationManager_dummy.cpp | 4 +++ src/main_utils_android.cpp | 13 +++++++-- 9 files changed, 83 insertions(+), 12 deletions(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 5eccd21..835745c 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -18,19 +18,22 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtActivity; +import android.Manifest; import android.os.Bundle; +import android.os.SystemClock; import android.content.Intent; import android.net.Uri; import android.app.AlarmManager; import android.app.PendingIntent; import android.provider.Settings; -import android.os.SystemClock; +import android.content.pm.PackageManager; public class MainActivity extends QtActivity { public native void setPaymentIntentOnCPP(String data); public native void startScan(); + public native void notificationPermissionDenied(); // the C++ should call this one when it finished startup. public void onQtAppStarted() @@ -74,6 +77,28 @@ public class MainActivity extends QtActivity startActivity(intent); } + public void requestNotificationPermission() + { + PayNotifications me = PayNotifications.instance(null); + if (me.areNotificationsEnabled()) + return; + + String perms[] = new String[1]; + perms[0] = Manifest.permission.POST_NOTIFICATIONS; + requestPermissions(perms, 1); // 1 is a requestCode + } + + @Override + // call-back from requestPermission + public void onRequestPermissionsResult (int requestCode, String[] permissions, + int[] grantResults) + { + if (requestCode == 1) { // POST_NOTIFICATIONS + if (grantResults[0] == PackageManager.PERMISSION_DENIED) + notificationPermissionDenied(); + } + } + @Override protected void onNewIntent(Intent intent) { diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index 189b894..30d63fb 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -32,12 +32,19 @@ public class PayNotifications { private static final int BlockNotificationId = 9839874; private static PayNotifications g_singleton = null; - public static PayNotifications instance(Context context) { + public static PayNotifications instance(Context context) + { if (g_singleton == null) g_singleton = new PayNotifications(context); return g_singleton; } + public boolean areNotificationsEnabled() + { + return instance(null).m_notificationManager.areNotificationsEnabled(); + } + + private Notification.Builder m_blockMessageBuilder = null; private NotificationManager m_notificationManager = null; private String m_blockChannelId = null; @@ -47,16 +54,19 @@ public class PayNotifications private int m_walletMessageId = 1; private String m_walletChannelId = null; - public static void setup(Context context) { + public static void setup(Context context) + { // create the signleton as it creates our channels PayNotifications.instance(context); } - public static void notifyBlock(String message) { + public static void notifyBlock(String message) + { PayNotifications.instance(null).notifyBlock_priv(message); } - public PayNotifications(Context context) { + public PayNotifications(Context context) + { // see example here on how to make these translatable: // https://developer.android.com/develop/ui/views/notifications/build-notification#java NotificationChannel newBlocksChannel = new NotificationChannel("blocks", "New Blocks", @@ -72,7 +82,8 @@ public class PayNotifications m_bgSyncChannelId = bgChannel.getId(); } - private void notifyBlock_priv(String message) { + private void notifyBlock_priv(String message) + { // We split this into the title and the message since that is how the anatomy of android // notifications work. int dot = message.indexOf('.'); @@ -126,7 +137,8 @@ public class PayNotifications m_notificationManager.notify(BlockNotificationId, m_blockMessageBuilder.build()); } - public Notification buildBackgroundNotification(Context context) { + public Notification buildBackgroundNotification(Context context) + { Notification.Builder builder = new Notification.Builder(context, m_bgSyncChannelId); builder.setContentTitle("Background Updator") .setSmallIcon(R.drawable.icon) diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index dc72cc6..d311002 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -187,6 +187,16 @@ Page { } } + PageTitledBox { + title: qsTr("Notifications") + + Flowee.CheckBox { + width: parent.width + text: qsTr("On new block found") + checked: !Pay.newBlockMuted + onCheckedChanged: Pay.newBlockMuted = !checked + } + } } } } diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 6488327..51299f8 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -35,6 +35,9 @@ void NotificationManager::setNewBlockMuted(bool mute) QSettings appConfig; appConfig.setValue(KEY_MUTE, m_newBlockMuted); emit newBlockMutedChanged(); + + if (!mute) + requestNotificationPermissions(); } void NotificationManager::headerSyncComplete() diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 6052416..0e996d7 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -48,6 +48,7 @@ private: bool m_newBlockMuted = false; QString describeCollated(int &txCount) const; + void requestNotificationPermissions(); static constexpr const char *KEY_MUTE = "notificationNewblockMute"; friend class NotificationManagerPrivate; diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 52c06a8..b8a1fa6 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -33,7 +33,7 @@ NotificationManager::NotificationManager(QObject *parent) { setCollation(true); QSettings appConfig; - m_newBlockMuted = appConfig.value(KEY_MUTE, false).toBool(); + m_newBlockMuted = appConfig.value(KEY_MUTE, true).toBool(); } void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) @@ -51,6 +51,12 @@ void NotificationManager::segmentUpdated(const P2PNet::Notification&) d->segmentUpdated_fromNetwork(); } +void NotificationManager::requestNotificationPermissions() +{ + auto japp = QJniObject(QNativeInterface::QAndroidApplication::context()); + japp.callObjectMethod("requestNotificationPermission", "()V"); +} + // --------------------------------------------------------- NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) @@ -81,7 +87,6 @@ void NotificationManagerPrivate::newBlockSeen(int blockHeight) else message = tr("tBCH (testnet4) block mined: %1").arg(blockHeight); -#if TARGET_OS_Android auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { QJniEnvironment env; auto jmessage = QJniObject::fromString(message); @@ -91,7 +96,6 @@ void NotificationManagerPrivate::newBlockSeen(int blockHeight) }); // The java side will handle everything from here, so we just ignore the task. Q_UNUSED(task); -#endif } // on the local thread. diff --git a/src/NotificationManager_dbus.cpp b/src/NotificationManager_dbus.cpp index 8db0c5e..c99dd63 100644 --- a/src/NotificationManager_dbus.cpp +++ b/src/NotificationManager_dbus.cpp @@ -45,6 +45,9 @@ void NotificationManager::segmentUpdated(const P2PNet::Notification&) d->segmentUpdated_fromNetwork(); } +void NotificationManager::requestNotificationPermissions() +{ +} // ///////////////////////////////// diff --git a/src/NotificationManager_dummy.cpp b/src/NotificationManager_dummy.cpp index dc62bad..1fefc69 100644 --- a/src/NotificationManager_dummy.cpp +++ b/src/NotificationManager_dummy.cpp @@ -34,3 +34,7 @@ void NotificationManager::notifyNewBlock(const P2PNet::Notification&) void NotificationManager::segmentUpdated(const P2PNet::Notification&) { } + +void NotificationManager::requestNotificationPermissions() +{ +} diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index defa1db..c9cc95a 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -53,16 +53,25 @@ void JNI_startQRScanner(JNIEnv *env, jobject thiz) g_userIntent->setStartPaymentScanner(true); } +void JNI_notificationPermissionDenied(JNIEnv *env, jobject thiz) +{ + // turn this off if the user doesn't allow android notifications. + // The UI will reflect this and enabling it will request the + // permission again. + FloweePay::instance()->setNewBlockMuted(true); +} + void setupCallbacks(UserIntent *pi) { g_userIntent = pi; const JNINativeMethod methods[] = { {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)}, - {"startScan", "()V", reinterpret_cast(JNI_startQRScanner)} + {"startScan", "()V", reinterpret_cast(JNI_startQRScanner)}, + {"notificationPermissionDenied", "()V", reinterpret_cast(JNI_notificationPermissionDenied)} }; QJniEnvironment env; - env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 2); + env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 3); // setup the notification channels and data jclass notifications = env.findClass("org/flowee/pay/PayNotifications"); -- 2.54.0 From 4dc01102808836fd8a86f0e7555cecf2df99a3df Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 21:48:36 +0100 Subject: [PATCH 1252/1428] Make checkbox not stick out --- guis/Flowee/CheckBox.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 32b9914..35e8f4e 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -34,8 +34,9 @@ T.CheckBox { implicitWidth: implicitHeight * 2.1 Rectangle { - width: parent.width - height: 16 + width: parent.width - 10 + x: 5 + height: 14 anchors.verticalCenter: parent.verticalCenter radius: parent.height / 3 color: { -- 2.54.0 From 66f1812cd4355db567699e4c6c0ecad08930aac8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 3 Mar 2025 21:50:43 +0100 Subject: [PATCH 1253/1428] new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 58bc14c..c6e7d9d 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="37" android:versionName="2025.03.0"> diff --git a/src/main.cpp b/src/main.cpp index 5c9d4ff..9086db2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.02.1"); + qapp.setApplicationVersion("2025.03.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 44bdb8e8b302342afca47f81b0b4624e949a28b8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 11:14:54 +0100 Subject: [PATCH 1254/1428] Small fixes --- guis/ControlColors.js | 2 +- guis/mobile/InstaPayConfigButton.qml | 2 ++ guis/mobile/TextButton.qml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/guis/ControlColors.js b/guis/ControlColors.js index d4d031b..7237e97 100644 --- a/guis/ControlColors.js +++ b/guis/ControlColors.js @@ -39,7 +39,7 @@ function applyDarkSkin(item) { item.palette.button = "#4d4d4d" // buttons and groupbox borders and slider borders item.palette.buttonText = "#fcfcfc" // just text for buttons - item.palette.highlight = "#76bfc7" // mouseover on popup, slider thumb, outline of active textfield, selected-text background + item.palette.highlight = "#7fa7d6" // mouseover on popup, slider thumb, outline of active textfield, selected-text background item.palette.highlightedText = "#090909"// selected text // Tooltips colors are currently unused diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index 73bf697..77aa12a 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -48,6 +48,8 @@ TextButton { } subtext: { + if (portfolio.singleAccountSetup) + return ""; if (root.account.allowsInstaPay) return qsTr("Limits for %1", "argument is a name").arg(root.account.name); return qsTr("Disabled for %1", "argument is a name").arg(root.account.name); diff --git a/guis/mobile/TextButton.qml b/guis/mobile/TextButton.qml index d4e62c1..ac762de 100644 --- a/guis/mobile/TextButton.qml +++ b/guis/mobile/TextButton.qml @@ -85,7 +85,7 @@ Item { x -= rightIcon.width + 10; return x; } - color: enabled ? palette.midlight : palette.brightText + color: enabled ? palette.highlight : palette.brightText } NewIndicator { id: newIndicator -- 2.54.0 From e59eda5050d80d287d7d82641b4aa6946166689c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 11:23:17 +0100 Subject: [PATCH 1255/1428] Seems QtCore doesn't (any longer) requires this --- android/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index c6e7d9d..21d6a98 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -3,7 +3,6 @@ android:installLocation="auto" android:versionCode="37" android:versionName="2025.03.0"> - -- 2.54.0 From ad415ece2350ab30d93ba8c276e5af601754c601 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 11:40:46 +0100 Subject: [PATCH 1256/1428] Make popup not have a background Just the loader has a background, avoiding weird placement issues due to the frankly bad defaults and insets etc of the popup component --- guis/mobile/PopupOverlay.qml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index d811518..255cc95 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -71,17 +71,25 @@ FocusScope { } root.isOpen = visible; // ensure listeners of that property get notified after we acted on visibility changes. } - background: Rectangle { - color: palette.base - } + background: Item { } + Loader { + // use offsets to counter the popup insets + width: parent.width + 42 + x: -21 + id: overlayLoader + } Rectangle { color: palette.base border.color: palette.midlight border.width: 1 radius: 5 anchors.fill: loader - anchors.margins: -10 // the popup imposes this border on us, we take it back + // the popup imposes a border on us, we take it back + anchors.topMargin: -10 + anchors.bottomMargin: -2 + anchors.leftMargin: -14 + anchors.rightMargin: -14 } Loader { @@ -96,11 +104,11 @@ FocusScope { if (item == null) return; var h = loader.item.implicitHeight - thePopup.height = h + 12; // 12 is for top and bottom inset of the popup + thePopup.height = h + 22; // 22 is for top and bottom margins of the popup var rect = thePopup.sourceRect; if (overlayLoader.item) { // the overlay is supposed to be at the same position as the sourceRect var h2 = overlayLoader.height - thePopup.height += h2 + 10; + thePopup.height += h2; if (root.height - rect.bottom >= h) { // fits below thePopup.y = rect.bottom - h2; @@ -130,13 +138,6 @@ FocusScope { thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps } } - - Loader { - // use offsets to counter the popup insets - width: parent.width + 40 - x: -20 - id: overlayLoader - } } Keys.onPressed: (event)=> { -- 2.54.0 From d9991cace6fcd17ac741ced0bf9639449336161b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 12:43:37 +0100 Subject: [PATCH 1257/1428] Prepare modules for i18n --- .gitignore | 3 +++ CMakeLists.txt | 6 ++++++ modules/big-transfer/BigTransferModuleInfo.h | 2 +- .../big-transfer/{transfer-all-data.qrc => bigtransfer.qrc} | 0 modules/social-feed/SocialFeedModuleInfo.h | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) rename modules/big-transfer/{transfer-all-data.qrc => bigtransfer.qrc} (100%) diff --git a/.gitignore b/.gitignore index 4863fc9..f595114 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ module-build-transaction.ts module-example.ts module-peers-view.ts module-send-sweep.ts +module-bigtransfer.ts +module-social-feed.ts + diff --git a/CMakeLists.txt b/CMakeLists.txt index 51e9355..ee734a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,12 @@ if(NOT ANDROID) COMMAND lupdate modules/send-sweep/send-sweep-data.qrc modules/send-sweep/SendSweepModuleInfo.cpp -ts translations/module-send-sweep.ts + COMMAND lupdate modules/social-feed/social-feed-data.qrc + modules/social-feed/SocialFeedModuleInfo.cpp + -ts translations/module-social-feed.ts + COMMAND lupdate modules/big-transfer/bigtransfer.qrc + modules/big-transfer/BigTransferModuleInfo.cpp + -ts translations/module-bigtransfer.ts WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Updating internationalization (i18n) translation files" diff --git a/modules/big-transfer/BigTransferModuleInfo.h b/modules/big-transfer/BigTransferModuleInfo.h index e73164a..f75ad32 100644 --- a/modules/big-transfer/BigTransferModuleInfo.h +++ b/modules/big-transfer/BigTransferModuleInfo.h @@ -28,6 +28,6 @@ public: BigTransferModuleInfo(); static const char *translationUnit() { - return "module-transfer-all"; + return "module-bigtransfer"; } }; diff --git a/modules/big-transfer/transfer-all-data.qrc b/modules/big-transfer/bigtransfer.qrc similarity index 100% rename from modules/big-transfer/transfer-all-data.qrc rename to modules/big-transfer/bigtransfer.qrc diff --git a/modules/social-feed/SocialFeedModuleInfo.h b/modules/social-feed/SocialFeedModuleInfo.h index 13a3c23..09073ca 100644 --- a/modules/social-feed/SocialFeedModuleInfo.h +++ b/modules/social-feed/SocialFeedModuleInfo.h @@ -26,6 +26,6 @@ public: static ModuleInfo *build(); static const char *translationUnit() { - return "social-feed"; + return "module-social-feed"; } }; -- 2.54.0 From 36f1c79889afff572e9a6830b8d449c3bd489d18 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 13:01:11 +0100 Subject: [PATCH 1258/1428] Translation fixes --- guis/mobile/BackgroundSyncConfig.qml | 6 +++--- guis/mobile/Settings.qml | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index 4ed923a..cfd8706 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -21,7 +21,7 @@ import QtQuick.Layouts import "../Flowee" as Flowee Page { - headerText: qsTr("Background updates") + headerText: qsTr("Background Synchronization") id: root Column { @@ -30,13 +30,13 @@ Page { Flowee.Label { width: parent.width - text: qsTr("Without background updates your wallets will not see new transactions until you open Flowee Pay") + text: qsTr("Without background synchronization your wallets will not see new transactions until you open Flowee Pay") wrapMode: Text.WrapAtWordBoundaryOrAnywhere } Flowee.CheckBox { id: checkbox - text: qsTr("Allow Background Updates") + text: qsTr("Allow Background Synchronization") checked: Pay.backgroundUpdates onCheckedChanged: Pay.backgroundUpdates = checked } diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index d311002..d6355cb 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -85,7 +85,7 @@ Page { } TextButton { - text: qsTr("Background Updates") + text: qsTr("Background Synchronization") subtext: qsTr("Keep your wallets synchronized by enabling this") currentValue: Pay.backgroundUpdates pageButton: true @@ -162,13 +162,13 @@ Page { PageTitledBox { title: qsTr("Dark Theme") Flowee.RadioButton { - text: "Follow System" + text: qsTr("Follow System") checked: Pay.skinFollowsPlatform onClicked: Pay.skinFollowsPlatform = true; width: parent.width } Flowee.RadioButton { - text: "Dark" + text: qsTr("Dark") checked: !Pay.skinFollowsPlatform && Pay.useDarkSkin width: parent.width onClicked: { @@ -177,7 +177,7 @@ Page { } } Flowee.RadioButton { - text: "Light" + text: qsTr("Light") checked: !Pay.skinFollowsPlatform && !Pay.useDarkSkin width: parent.width onClicked: { -- 2.54.0 From 7f5a047f1adfd007fdeb33ab48b4ce77613f99bf Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 14:39:28 +0100 Subject: [PATCH 1259/1428] Import translations from crowdin --- CMakeLists.txt | 14 + translations/floweepay-common_de.ts | 74 ++- translations/floweepay-common_en.ts | 202 +++---- translations/floweepay-common_es.ts | 157 +++--- translations/floweepay-common_ha.ts | 157 +++--- translations/floweepay-common_nl.ts | 74 ++- translations/floweepay-common_pl.ts | 161 +++--- translations/floweepay-desktop_de.ts | 70 ++- translations/floweepay-desktop_en.ts | 332 ++++++----- translations/floweepay-desktop_es.ts | 285 +++++----- translations/floweepay-desktop_ha.ts | 329 ++++++----- translations/floweepay-desktop_nl.ts | 70 ++- translations/floweepay-desktop_pl.ts | 285 +++++----- translations/floweepay-mobile_de.ts | 303 ++++++---- translations/floweepay-mobile_en.ts | 595 ++++++++++++-------- translations/floweepay-mobile_es.ts | 477 +++++++++------- translations/floweepay-mobile_ha.ts | 515 ++++++++++------- translations/floweepay-mobile_nl.ts | 303 ++++++---- translations/floweepay-mobile_pl.ts | 477 +++++++++------- translations/mobile-i18n.qrc | 12 + translations/module-bigtransfer_de.ts | 97 ++++ translations/module-bigtransfer_en.ts | 97 ++++ translations/module-bigtransfer_es.ts | 97 ++++ translations/module-bigtransfer_ha.ts | 97 ++++ translations/module-bigtransfer_nl.ts | 97 ++++ translations/module-bigtransfer_pl.ts | 101 ++++ translations/module-build-transaction_de.ts | 46 +- translations/module-build-transaction_en.ts | 122 ++-- translations/module-build-transaction_es.ts | 117 ++-- translations/module-build-transaction_ha.ts | 117 ++-- translations/module-build-transaction_nl.ts | 46 +- translations/module-build-transaction_pl.ts | 117 ++-- translations/module-peers-view_de.ts | 25 +- translations/module-peers-view_en.ts | 25 +- translations/module-peers-view_es.ts | 25 +- translations/module-peers-view_ha.ts | 25 +- translations/module-peers-view_nl.ts | 25 +- translations/module-peers-view_pl.ts | 25 +- translations/module-send-sweep_de.ts | 12 +- translations/module-send-sweep_en.ts | 80 +-- translations/module-send-sweep_es.ts | 43 +- translations/module-send-sweep_ha.ts | 43 +- translations/module-send-sweep_nl.ts | 12 +- translations/module-send-sweep_pl.ts | 43 +- translations/module-social-feed_de.ts | 30 + translations/module-social-feed_en.ts | 30 + translations/module-social-feed_es.ts | 30 + translations/module-social-feed_ha.ts | 30 + translations/module-social-feed_nl.ts | 30 + translations/module-social-feed_pl.ts | 30 + 50 files changed, 4026 insertions(+), 2580 deletions(-) create mode 100644 translations/module-bigtransfer_de.ts create mode 100644 translations/module-bigtransfer_en.ts create mode 100644 translations/module-bigtransfer_es.ts create mode 100644 translations/module-bigtransfer_ha.ts create mode 100644 translations/module-bigtransfer_nl.ts create mode 100644 translations/module-bigtransfer_pl.ts create mode 100644 translations/module-social-feed_de.ts create mode 100644 translations/module-social-feed_en.ts create mode 100644 translations/module-social-feed_es.ts create mode 100644 translations/module-social-feed_ha.ts create mode 100644 translations/module-social-feed_nl.ts create mode 100644 translations/module-social-feed_pl.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index ee734a8..68a2678 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,20 @@ if(NOT ANDROID) translations/module-send-sweep_de.ts translations/module-send-sweep_pl.ts translations/module-send-sweep_ha.ts + + translations/module-bigtransfer_en.ts + translations/module-bigtransfer_nl.ts + translations/module-bigtransfer_es.ts + translations/module-bigtransfer_de.ts + translations/module-bigtransfer_pl.ts + translations/module-bigtransfer_ha.ts + + translations/module-social-feed_en.ts + translations/module-social-feed_nl.ts + translations/module-social-feed_es.ts + translations/module-social-feed_de.ts + translations/module-social-feed_pl.ts + translations/module-social-feed_ha.ts ) qt6_add_translation(qmFiles ${TS_FILES}) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index ddb4975..7612ef6 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -142,30 +142,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -174,13 +174,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -200,27 +200,27 @@ MenuModel - + Explore Erkunden - + Settings Einstellungen - + Security Sicherheit - + About Über - + Wallets Geldbörsen @@ -228,17 +228,17 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) %1 neue Transaktionen über %2 Wallets gefunden (%3) - + A payment of %1 has been sent Eine Zahlung von %1 wurde gesendet - + %1 new transactions found (%2) %1 neue Transaktion gefunden (%2) @@ -249,24 +249,24 @@ NotificationManagerPrivate - - + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash Block abgebaut. Höhe: %1 - - + + tBCH (testnet4) block mined: %1 tBCH (testnet4) Block erstellt: %1 - + Mute Stummschalten - + New Transactions dialog-title @@ -343,7 +343,7 @@ QRWidget - + Copied to clipboard In die Zwischenablage kopiert @@ -461,6 +461,19 @@ Früher in diesem Monat + + WalletSecretsModel + + + Change #%1 + Wechselgeld #%1 + + + + Main #%1 + Haupt #%1 + + WalletSecretsView @@ -478,33 +491,32 @@ b) historische Coin-Anzahl. - - + Copy Address Adresse kopieren - + QR of Address QR der Adresse - + Copy Private Key Privaten Schlüssel kopieren - + QR of Private Key QR des privaten Schlüssels - + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signiert mit Schnorr-Signaturen in der Vergangenheit diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index ec75f61..2452c5f 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -4,17 +4,17 @@ AccountInfo - + Offline Offline - + Wallet: Up to date Wallet: Up to date - + Behind: %1 weeks, %2 days counter on weeks @@ -23,7 +23,7 @@ - + Behind: %1 days Behind: %1 day @@ -31,17 +31,17 @@ - + Up to date Up to date - + Updating Updating - + Still %1 hours behind Still an hour behind @@ -76,53 +76,47 @@ self + + BigCloseButton + + + Close + Close + + BroadcastFeedback - + Sending Payment Sending Payment - + Payment Sent Payment Sent - + Failed Failed - + Transaction rejected by network Transaction rejected by network - - Payment has been sent to: - Payment has been sent to: + + The payment has been sent to: + Followed by the address + The payment has been sent to: - - Copied address to clipboard - Copied address to clipboard - - - - Opening Website - Opening Website - - - + Add a personal note Add a personal note - - - Close - Close - CFIcon @@ -135,12 +129,12 @@ FiatTxInfo - + Value now Value now - + Value then Value then @@ -148,30 +142,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -180,13 +174,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -206,27 +200,27 @@ MenuModel - + Explore Explore - + Settings Settings - + Security Security - + About About - + Wallets Wallets @@ -234,22 +228,45 @@ NotificationManager - + + %1 new transactions across %2 wallets found (%3) + %1 new transactions across %2 wallets found (%3) + + + + A payment of %1 has been sent + A payment of %1 has been sent + + + + %1 new transactions found (%2) + + %1 new transactions found (%2) + %1 new transactions found (%2) + + + + + NotificationManagerPrivate + + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash block mined. Height: %1 - + + tBCH (testnet4) block mined: %1 tBCH (testnet4) block mined: %1 - + Mute Mute - + New Transactions dialog-title @@ -257,24 +274,6 @@ New Transactions - - - %1 new transactions across %2 wallets found (%3) - %1 new transactions across %2 wallets found (%3) - - - - A payment of %1 has been sent - A payment of %1 has been sent - - - - %1 new transactions found (%2) - - %1 new transactions found (%2) - %1 new transactions found (%2) - - Payment @@ -331,22 +330,12 @@ QRScanner - - Paste - Paste - - - - Failed - Failed - - - + Instant Pay limit is %1 Instant Pay limit is %1 - + Selected wallet: '%1' Selected wallet: '%1' @@ -354,7 +343,7 @@ QRWidget - + Copied to clipboard Copied to clipboard @@ -363,6 +352,11 @@ TextField + Copy + Copy + + + Paste Paste @@ -370,7 +364,7 @@ TextPasteDecorator - + Paste Paste @@ -378,14 +372,14 @@ Wallet - - + + Change #%1 Change #%1 - - + + Main #%1 Main #%1 @@ -442,31 +436,44 @@ WalletHistoryModel - + Today Today - + Yesterday Yesterday - + Earlier this week Earlier this week - + This week This week - + Earlier this month Earlier this month + + WalletSecretsModel + + + Change #%1 + Change #%1 + + + + Main #%1 + Main #%1 + + WalletSecretsView @@ -484,33 +491,32 @@ b) historical coin-count. - - + Copy Address Copy Address - + QR of Address QR of Address - + Copy Private Key Copy Private Key - + QR of Private Key QR of Private Key - + Coins: %1 / %2 Coins: %1 / %2 - + Signed with Schnorr signatures in the past Signed with Schnorr signatures in the past diff --git a/translations/floweepay-common_es.ts b/translations/floweepay-common_es.ts index 017412a..1837611 100644 --- a/translations/floweepay-common_es.ts +++ b/translations/floweepay-common_es.ts @@ -76,53 +76,47 @@ propio + + BigCloseButton + + + Close + Cerrar + + BroadcastFeedback - + Sending Payment Enviando Pago - + Payment Sent Pago Enviado - + Failed Fallido - + Transaction rejected by network Transacción rechazada por la red - - Payment has been sent to: + + The payment has been sent to: + Followed by the address El pago ha sido enviado a: - - Copied address to clipboard - Dirección copiada al portapapeles - - - - Opening Website - Abriendo Sitio Web - - - + Add a personal note Añadir una nota personal - - - Close - Cerrar - CFIcon @@ -148,30 +142,30 @@ FloweePay - + Initial Wallet Monedero inicial - - + + Today Hoy - - + + Yesterday Ayer - + Now timestamp Ahora - + %1 minutes ago relative time stamp @@ -180,13 +174,13 @@ - + ½ hour ago timestamp Hace ½ hora - + %1 hours ago timestamp @@ -206,27 +200,27 @@ MenuModel - + Explore Explorar - + Settings Configuraciones - + Security Seguridad - + About Acerca de - + Wallets Monederos @@ -234,35 +228,45 @@ NotificationManager - - Bitcoin Cash block mined. Height: %1 - Bloque de Bitcoin Cash minado. Altura: %1 + + %1 new transactions across %2 wallets found (%3) + %1 nuevas transacciones entre %2 monederos encontradas (%3) - - tBCH (testnet4) block mined: %1 - Bloque de tBCH (testnet4) minado: %1 + + A payment of %1 has been sent + Un pago de %1 ha sido enviado + + + + %1 new transactions found (%2) + + %1 nuevas transacciones encontradas (%2) + %1 new transactions found (%2) + NotificationManagerPrivate - + + Bitcoin Cash block mined. Height: %1 Bloque de Bitcoin Cash minado. Altura: %1 - + + tBCH (testnet4) block mined: %1 Bloque de tBCH (testnet4) minado: %1 - + Mute Silenciar - + New Transactions dialog-title @@ -270,24 +274,6 @@ Nuevas transacciones - - - %1 new transactions across %2 wallets found (%3) - %1 nuevas transacciones entre %2 monederos encontradas (%3) - - - - A payment of %1 has been sent - Un pago de %1 ha sido enviado - - - - %1 new transactions found (%2) - - %1 nuevas transacciones encontradas (%2) - %1 new transactions found (%2) - - Payment @@ -357,7 +343,7 @@ QRWidget - + Copied to clipboard Copiado al portapapeles @@ -366,6 +352,11 @@ TextField + Copy + Copiar + + + Paste Pegar @@ -445,31 +436,44 @@ WalletHistoryModel - + Today Hoy - + Yesterday Ayer - + Earlier this week Anteriormente en esta semana - + This week Esta semana - + Earlier this month Anteriormente este mes + + WalletSecretsModel + + + Change #%1 + Cambio #%1 + + + + Main #%1 + Principal #%1 + + WalletSecretsView @@ -485,33 +489,32 @@ Monedas a / b a) cantidad de monedas en posesión. b) historial de cantidad de monedas. - - + Copy Address Copiar la dirección - + QR of Address QR de la dirección - + Copy Private Key Copiar clave privada - + QR of Private Key QR de la clave privada - + Coins: %1 / %2 Monedas: %1 / %2 - + Signed with Schnorr signatures in the past Firmado con firmas de Schnorr en el pasado diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts index da23cb7..337e180 100644 --- a/translations/floweepay-common_ha.ts +++ b/translations/floweepay-common_ha.ts @@ -76,53 +76,47 @@ Kai + + BigCloseButton + + + Close + Kulle + + BroadcastFeedback - + Sending Payment Aika Biyan Kuɗi - + Payment Sent An aika Biya - + Failed Ba a yi nasara ba - + Transaction rejected by network hanyar sadarwa ta ƙi ciniki - - Payment has been sent to: + + The payment has been sent to: + Followed by the address An aika biyan kuɗi zuwa: - - Copied address to clipboard - Adireshin da aka kwafi zuwa allo - - - - Opening Website - Buɗe Yanar Gizon - - - + Add a personal note Ƙara bayanin kula na sirri - - - Close - Kulle - CFIcon @@ -148,30 +142,30 @@ FloweePay - + Initial Wallet Asusu na farko - - + + Today Yau - - + + Yesterday Jiya - + Now timestamp Yanzu - + %1 minutes ago relative time stamp @@ -180,13 +174,13 @@ - + ½ hour ago timestamp ½ awa da ya wuce - + %1 hours ago timestamp @@ -206,27 +200,27 @@ MenuModel - + Explore Bincika - + Settings Saituna - + Security Tsaro - + About Game da - + Wallets Asusu @@ -234,35 +228,45 @@ NotificationManager - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 + + %1 new transactions across %2 wallets found (%3) + %1 sabbin ma'amaloli a cikin %2 asusun da aka samu (%3) - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) toshe ma'adinai: %1 + + A payment of %1 has been sent + An aika biyan kuɗi na %1 + + + + %1 new transactions found (%2) + + %1 sababbin ma'amaloli akasamu (%2) + %1 sababbin ma'amaloli aka samu (%2) + NotificationManagerPrivate - + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 - + + tBCH (testnet4) block mined: %1 tBCH (testnet4) toshe ma'adinai: %1 - + Mute Shiru - + New Transactions dialog-title @@ -270,24 +274,6 @@ New Transactions - - - %1 new transactions across %2 wallets found (%3) - %1 sabbin ma'amaloli a cikin %2 asusun da aka samu (%3) - - - - A payment of %1 has been sent - An aika biyan kuɗi na %1 - - - - %1 new transactions found (%2) - - %1 sababbin ma'amaloli akasamu (%2) - %1 sababbin ma'amaloli aka samu (%2) - - Payment @@ -357,7 +343,7 @@ QRWidget - + Copied to clipboard An kwafo zuwa allo @@ -366,6 +352,11 @@ TextField + Copy + Kwafi + + + Paste Wallafa @@ -445,31 +436,44 @@ WalletHistoryModel - + Today Yau - + Yesterday Jiya - + Earlier this week A farkon wannan makon - + This week Wannan Makon - + Earlier this month A farkon wannan watan + + WalletSecretsModel + + + Change #%1 + Chanji #%1 + + + + Main #%1 + Mafarin #%1 + + WalletSecretsView @@ -487,33 +491,32 @@ a) ƙidaya tsabar kudi b) tsabar kudi na tarihi. - - + Copy Address Kwafi Adireshi - + QR of Address QR of Address - + Copy Private Key Kwafi Keɓaɓɓen Maɓalli - + QR of Private Key QR of Private Key - + Coins: %1 / %2 Tsabar kudi: %1/%2 - + Signed with Schnorr signatures in the past Sa hannu tare da sa hannun Schnorr a baya diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 118bf4f..11fcc8a 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -142,30 +142,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -174,13 +174,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -200,27 +200,27 @@ MenuModel - + Explore Ontdek - + Settings Instellingen - + Security Beveiliging - + About Over Ons - + Wallets Portemonnees @@ -228,17 +228,17 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) %1 nieuwe transacties in %2 portemonnees gevonden (%3) - + A payment of %1 has been sent Een betaling van %1 is verzonden - + %1 new transactions found (%2) %1 nieuwe transactie gevonden (%2) @@ -249,24 +249,24 @@ NotificationManagerPrivate - - + + Bitcoin Cash block mined. Height: %1 Bitcoin Cash-blok gemijnd. Hoogte: %1 - - + + tBCH (testnet4) block mined: %1 tBCH (testnet4) blok gemijnd: %1 - + Mute Negeren - + New Transactions dialog-title @@ -343,7 +343,7 @@ QRWidget - + Copied to clipboard Naar klembord gekopieerd @@ -461,6 +461,19 @@ Eerder deze maand + + WalletSecretsModel + + + Change #%1 + Wisselmunt #%1 + + + + Main #%1 + Standaard#%1 + + WalletSecretsView @@ -478,33 +491,32 @@ b) historische munten. - - + Copy Address Kopieer adres - + QR of Address QR van het adres - + Copy Private Key Kopieer privésleutel - + QR of Private Key QR van de privésleutel - + Coins: %1 / %2 Munten: %1 / %2 - + Signed with Schnorr signatures in the past In het verleden ondertekend met Schnorr diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 625202c..c672a2b 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -82,53 +82,47 @@ własny + + BigCloseButton + + + Close + Zamknij + + BroadcastFeedback - + Sending Payment Wysyłanie Płatności - + Payment Sent Płatność Wysłana - + Failed Niepowodzenie - + Transaction rejected by network Transakcja odrzucona przez sieć - - Payment has been sent to: + + The payment has been sent to: + Followed by the address Płatność została wysłana do: - - Copied address to clipboard - Adres skopiowany do schowka - - - - Opening Website - Otwieranie Strony - - - + Add a personal note Dodaj osobistą notatkę - - - Close - Zamknij - CFIcon @@ -154,30 +148,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -188,13 +182,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -216,27 +210,27 @@ MenuModel - + Explore Eksploruj - + Settings Ustawienia - + Security Bezpieczeństwo - + About O programie - + Wallets Portfele @@ -244,35 +238,47 @@ NotificationManager - - Bitcoin Cash block mined. Height: %1 - Blok Bitcoin Cash został wykopany. Wysokość: %1 + + %1 new transactions across %2 wallets found (%3) + %1 nowe transakcje w %2 portfelach (%3) - - tBCH (testnet4) block mined: %1 - Wykopano blok tBCH (testnet4): %1 + + A payment of %1 has been sent + Wysłano płatność w wysokości %1 + + + + %1 new transactions found (%2) + + Znaleziono %1 nową transakcję (%2) + Znaleziono %1 nowe transakcje (%2) + Znaleziono %1 nowych transakcji (%2) + Znaleziono %1 nowej transakcji (%2) + NotificationManagerPrivate - + + Bitcoin Cash block mined. Height: %1 Blok Bitcoin Cash został wykopany. Wysokość: %1 - + + tBCH (testnet4) block mined: %1 Wykopano blok tBCH (testnet4): %1 - + Mute Wycisz - + New Transactions dialog-title @@ -282,26 +288,6 @@ Nowe transakcje - - - %1 new transactions across %2 wallets found (%3) - %1 nowe transakcje w %2 portfelach (%3) - - - - A payment of %1 has been sent - Wysłano płatność w wysokości %1 - - - - %1 new transactions found (%2) - - Znaleziono %1 nową transakcję (%2) - Znaleziono %1 nowe transakcje (%2) - Znaleziono %1 nowych transakcji (%2) - Znaleziono %1 nowej transakcji (%2) - - Payment @@ -371,7 +357,7 @@ QRWidget - + Copied to clipboard Skopiowano do schowka @@ -380,6 +366,11 @@ TextField + Copy + Skopiuj + + + Paste Wklej @@ -467,31 +458,44 @@ WalletHistoryModel - + Today Dzisiaj - + Yesterday Wczoraj - + Earlier this week Wcześniej w tym tygodniu - + This week W tym tygodniu - + Earlier this month Wcześniej w tym miesiącu + + WalletSecretsModel + + + Change #%1 + Reszta #%1 + + + + Main #%1 + Główny #%1 + + WalletSecretsView @@ -509,33 +513,32 @@ b) liczba monet w przeszłości. - - + Copy Address Skopiuj adres - + QR of Address Kod QR adresu - + Copy Private Key Skopiuj klucz prywatny - + QR of Private Key Kod QR klucza prywatnego - + Coins: %1 / %2 Monety: %1 / %2 - + Signed with Schnorr signatures in the past Podpisano w przeszłości podpisem Schnorr diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index 932df36..ecea656 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -163,7 +163,7 @@ AccountListItem - + Last Transaction Letzte Transaktion @@ -710,7 +710,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Copy Address Adresse kopieren @@ -780,52 +780,52 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin Coin entsperren - + Lock coin Coin sperren - + Public-comment Öffentlicher Kommentar - + Add a comment you want to include in the transaction, visible for everyone. Fügen Sie einen Kommentar hinzu, den Sie in die Transaktion einfügen möchten, sichtbar für alle. - + Custom message, to be included in the transaction. Benutzerdefinierte Nachricht, die in die Transaktion aufgenommen werden soll. - + Text Text - + Size Größe @@ -955,16 +955,11 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - Unconfirmed - Unbestätigt - - - Mined at Abgebaut am - + %1 blocks ago %1 Block her @@ -972,67 +967,72 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + + Waiting for block + Warte auf Block + + + Comment Kommentar - + Fees paid Bezahlte Gebühren - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 Bytes - + Size Größe - + %1 bytes %1 Bytes - + Is Coinbase Ist Coinbase - + Copy transaction-ID Kopiere Transaktions-ID - + Fused from my addresses Fusioniert von meinen Adressen - + Sent from my addresses Gesendet von meinen Adressen - + Sent from addresses Gesendet von den Adressen - + Fused into my addresses Fusioniert in meine Adressen - + Received at addresses Empfangen auf den Adressen - + Received at my addresses Empfangen auf meinen Adressen @@ -1228,6 +1228,14 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. (Geöffnet) + + locked + + + Already running? + Already running? + + main diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index 5916108..cde2b72 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -49,107 +49,113 @@ AccountDetails - + Wallet Details Wallet Details - + Name Name - + Sync Status Sync Status - + Encryption Encryption - + + Password Password - + Open Open - + Invalid PIN Invalid PIN - + Include balance in total Include balance in total - + Hide in private mode Hide in private mode - + Address List Address List - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Backup details Backup details - + Seed-phrase Seed-phrase - + + Copy + Copy + + + Seed format Seed format - + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. @@ -157,11 +163,31 @@ AccountListItem - + Last Transaction Last Transaction + + ActivityConfigBar + + + Filter + Filter + + + + + Received + Received + + + + + Sent + Sent + + AddressDbStats @@ -318,102 +344,102 @@ This ensures only one private key will need to be backed up NewAccountImportAccount - + Select import method Select import method - + Camera Camera - + OR OR - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Address to import Address to import - + + + New Wallet Name + New Wallet Name + + + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - - New Wallet Name - New Wallet Name - - - - + + Start Start - - Password - Password - - - - imported wallet password - imported wallet password - - - + Discover Details online check for wallet details Discover Details - + Derivation Derivation - - Nothing found for seed - Nothing found for seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Password + + + + imported wallet password + imported wallet password @@ -605,7 +631,7 @@ Change will come back to the imported key. - + Warning Warning @@ -661,65 +687,65 @@ Change will come back to the imported key. Send - + Destination Destination - + Max available The maximum balance available Max available - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Enter Bitcoin Cash Address - - + + Copy Address Copy Address - + Amount Amount - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + Cancel Cancel - + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -728,78 +754,78 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin - + Public-comment Public-comment - + Add a comment you want to include in the transaction, visible for everyone. Add a comment you want to include in the transaction, visible for everyone. - + Custom message, to be included in the transaction. Custom message, to be included in the transaction. - + Text Text - + Size Size @@ -812,67 +838,67 @@ Change will come back to the imported key. Settings - + Unit Unit - + Show Bitcoin Cash value on Activity page Show Bitcoin Cash value on Activity page - + Show Block Notifications Show Block Notifications - + When a new block is mined, Flowee Pay shows a desktop notification When a new block is mined, Flowee Pay shows a desktop notification - + Night Mode Night Mode - + Private Mode Private Mode - + Hides private wallets while enabled Hides private wallets while enabled - + Font sizing Font sizing - + Version Version - + Library Version Library Version - + Synchronization Synchronization - + Network Status Network Status - + Address Stats Address Stats @@ -880,17 +906,17 @@ Change will come back to the imported key. Transaction - + Miner Reward Miner Reward - + Fused Fused - + Received Received @@ -929,16 +955,11 @@ Change will come back to the imported key. - Unconfirmed - Unconfirmed - - - Mined at Mined at - + %1 blocks ago %1 blocks ago @@ -946,67 +967,72 @@ Change will come back to the imported key. - + + Waiting for block + Waiting for block + + + Comment Comment - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Size - + %1 bytes %1 bytes - + Is Coinbase Is Coinbase - + Copy transaction-ID Copy transaction-ID - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses @@ -1014,22 +1040,22 @@ Change will come back to the imported key. TransactionInfoSmall - + Transaction is rejected Transaction is rejected - + Processing Processing - + Mined Mined - + %1 blocks ago Confirmations @@ -1038,22 +1064,22 @@ Change will come back to the imported key. - + Fees Fees - + Copy transaction-ID Copy transaction-ID - + Holds a token Holds a token - + Opening Website Opening Website @@ -1202,98 +1228,106 @@ Change will come back to the imported key. (Opened) + + locked + + + Already running? + Already running? + + main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + Offline Offline - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1302,12 +1336,12 @@ Change will come back to the imported key. - + Preparing... Preparing... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_es.ts b/translations/floweepay-desktop_es.ts index 17439fc..55fcc84 100644 --- a/translations/floweepay-desktop_es.ts +++ b/translations/floweepay-desktop_es.ts @@ -49,108 +49,113 @@ AccountDetails - + Wallet Details Detalles del monedero - + Name Nombre - + Sync Status Estado de la sincronización - + Encryption Cifrado - - + + Password Contraseña - + Open Abrir - + Invalid PIN PIN inválido - + Include balance in total Incluir en el balance total - + Hide in private mode Ocultar en modo privado - + Address List Lista de direcciones - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Alterna entre direcciones donde otros pueden pagarte y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between unused and used Bitcoin addresses Alterna entre direcciones no usadas y usadas de Bitcoin - + Backup details Detalles de la copia de seguridad - + Seed-phrase Frase semilla - + + Copy + Copiar + + + Seed format Formato de semilla - + Derivation Derivación - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que falle su hardware. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Este monedero está protegido por contraseña (pin to pay). Para ver los detalles de la copia de seguridad necesita proporcionar la contraseña. @@ -158,11 +163,31 @@ AccountListItem - + Last Transaction Última transacción + + ActivityConfigBar + + + Filter + Filter + + + + + Received + Recibido + + + + + Sent + Enviado + + AddressDbStats @@ -340,79 +365,79 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Secreto como texto - + Unknown word(s) found Palabra(s) desconocidas encontradas - + Address to import Dirección a importar - - + + New Wallet Name Nuevo nombre de billetera - + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - + + Start Comenzar - + Discover Details online check for wallet details Detalles de Descubre - + Derivation Derivación - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Contraseña - + imported wallet password Contraseña del monedero importada @@ -606,7 +631,7 @@ El cambio volverá a la clave importada. - + Warning Advertencia @@ -662,65 +687,65 @@ El cambio volverá a la clave importada. Enviar - + Destination Destino - + Max available The maximum balance available Máximo disponible - + %1 to %2 summary text to pay X-euro to address M %1 a %2 - + Enter Bitcoin Cash Address Introduzca la dirección de Bitcoin Cash - - + + Copy Address Copiar dirección - + Amount Monto - + Max Máx - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - + Continue Continuar - + Cancel Cancelar - + Coin Selector Selector de monedas - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -729,78 +754,78 @@ El cambio volverá a la clave importada. - + Total Number of coins Total - + Needed Necesario - + Selected Seleccionado - + Value Valor - + Locked coins will never be used for payments. Right-click for menu. Las monedas bloqueadas nunca se utilizarán para los pagos. Clic derecho para desplegar el menú. - + Age Edad - + Unselect All Deseleccionar Todo - + Select All Seleccionar Todo - + Unlock coin Desbloquear moneda - + Lock coin Bloquear moneda - + Public-comment Comentario-público - + Add a comment you want to include in the transaction, visible for everyone. Agrega un comentario que quieras incluir en la transacción, visible para todos. - + Custom message, to be included in the transaction. Mensaje personalizado, a ser incluido en la transacción. - + Text Texto - + Size Tamaño @@ -813,67 +838,67 @@ El cambio volverá a la clave importada. Configuraciones - + Unit Unidad - + Show Bitcoin Cash value on Activity page Mostrar valor de Bitcoin Cash en la página de Actividad - + Show Block Notifications Mostrar notificaciones bloqueadas - + When a new block is mined, Flowee Pay shows a desktop notification Cuando un nuevo bloque es minado, Flowee Pay muestra una notificación de escritorio - + Night Mode Modo nocturno - + Private Mode Modo Privado - + Hides private wallets while enabled Oculta monederos privados mientras está habilitado - + Font sizing Tamaño de fuente - + Version Versión - + Library Version Versión de la biblioteca - + Synchronization Sincronización - + Network Status Estado de la red - + Address Stats Estadísticas de dirección @@ -881,32 +906,32 @@ El cambio volverá a la clave importada. Transaction - + Miner Reward Recompensa del minero - + Fused Fusionado - + Received Recibido - + Moved Movido - + Sent Enviado - + rejected rechazada @@ -930,16 +955,11 @@ El cambio volverá a la clave importada. - Unconfirmed - Sin confirmar - - - Mined at Minado en - + %1 blocks ago Hace %1 bloque @@ -947,67 +967,72 @@ El cambio volverá a la clave importada. - + + Waiting for block + Waiting for block + + + Comment Comentario - + Fees paid Comisiones pagadas - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Tamaño - + %1 bytes %1 bytes - + Is Coinbase Es Coinbase - + Copy transaction-ID Copiar ID de la transacción - + Fused from my addresses Fusionado desde mis direcciones - + Sent from my addresses Enviado desde mis direcciones - + Sent from addresses Enviado desde direcciones - + Fused into my addresses Fusionado en mis direcciones - + Received at addresses Recibido en direcciones - + Received at my addresses Recibido en mis direcciones @@ -1203,6 +1228,14 @@ El cambio volverá a la clave importada. (Abierto) + + locked + + + Already running? + Already running? + + main @@ -1211,90 +1244,90 @@ El cambio volverá a la clave importada. Actividad - + Archived wallets do not check for activities. Balance may be out of date. Las carteras archivadas no verifican las actividades. El saldo puede estar desactualizado. - + Unarchive Desarchivar - + This wallet needs a password to open. Esta cartera necesita una contraseña para abrirse. - + Password: Contraseña: - + Invalid password Contraseña invalida - + Open Abrir - + Send Enviar - + Receive Recibir - + Balance Balance - + Main balance (money), non specified Principal - + Unconfirmed balance (money) Sin confirmar - + Immature balance (money) Sin madurar - + 1 BCH is: %1 1 BCH es: %1 - + Network status Estado de la red - + Offline Sin conexión - + Add Bitcoin Cash wallet Añadir monedero de Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1303,12 +1336,12 @@ El cambio volverá a la clave importada. - + Preparing... Preparando... - + QR-Scan Escaneo de QR diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts index a88a07d..6e1b264 100644 --- a/translations/floweepay-desktop_ha.ts +++ b/translations/floweepay-desktop_ha.ts @@ -49,108 +49,113 @@ AccountDetails - + Wallet Details Bayanan asusun ajiya - + Name Suna - + Sync Status Daidaita matsayi - + Encryption Rufewa - - + + Password Kwadon shiga - + Open Bude - + Invalid PIN Makulli Mara inganci - + Include balance in total Haɗa ma'aunin chanji baki ɗaya - + Hide in private mode Ɓoye a yanayin sirri - + Address List Jerin adireshi - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Backup details Ajiyayyen bayanai - + Seed-phrase Jimlar iri - + + Copy + Kwafi + + + Seed format Tsarin iri - + Derivation Samo asali - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Wannan asusun na tsare da almar sirri (pin-to-pay). Don ganin bayanan ciki akwai buƙatar samar da kalmar wucewa. @@ -158,11 +163,31 @@ AccountListItem - + Last Transaction Ciniki na ƙarshe + + ActivityConfigBar + + + Filter + Filter + + + + + Received + An samu + + + + + Sent + An aika + + AddressDbStats @@ -339,78 +364,78 @@ This ensures only one private key will need to be backed up Secret as text - + Unknown word(s) found Unknown word(s) found - + Address to import Address to import - - + + New Wallet Name New Wallet Name - + Force Single Address Tilasta Adireshi Guda - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + Start Start - + Discover Details online check for wallet details Discover Details - + Derivation Samo asali - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Kwadon shiga - + imported wallet password imported wallet password @@ -604,7 +629,7 @@ Change will come back to the imported key. - + Warning Gargaɗi @@ -660,65 +685,65 @@ Change will come back to the imported key. Aika - + Destination Madakata - + Max available The maximum balance available Mafi girman samuwa - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Shigar da Adireshin Kuɗi na Bitcoin - - + + Copy Address Kwafi Adireshi - + Amount Adadi - + Max Matsakaici - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + Continue Ci gaba - + Cancel Soke - + Coin Selector Zaɓin Tsabar kudi - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -727,80 +752,80 @@ Change will come back to the imported key. - + Total Number of coins Jimilla - + Needed Ake Bukata - + Selected An zaɓa - + Value Daraja - + Locked coins will never be used for payments. Right-click for menu. Kullallun tsabar kudi ba za a taɓa amfani da su don biyan kuɗi ba. Danna-dama don menu. - + Age Shekaru - + Unselect All Cire Zaɓi Duk - + Select All Zaɓi Duk - + Unlock coin Buɗe tsabar kudi - + Lock coin Kulle tsabar kudi - + Public-comment Public-comment - + Add a comment you want to include in the transaction, visible for everyone. Add a comment you want to include in the transaction, visible for everyone. - + Custom message, to be included in the transaction. Custom message, to be included in the transaction. - + Text Text - + Size - Size + Girman @@ -811,67 +836,67 @@ Change will come back to the imported key. Saituna - + Unit Naúrar - + Show Bitcoin Cash value on Activity page Nuna ƙimar Bitcoin Cash akan shafin Aiki - + Show Block Notifications Nuna Sanarwa da ta toshe - + When a new block is mined, Flowee Pay shows a desktop notification Lokacin da aka haƙa sabon toshe, Flowee Pay yana nuna sanarwar tebur - + Night Mode Yanayin dare - + Private Mode Yanayin sirri - + Hides private wallets while enabled Ɓoye sirrin asusu yayin kunnawa - + Font sizing Girman haruffa - + Version Siga - + Library Version Sigar Laburare - + Synchronization Haɗa Aiki tare - + Network Status Matsayin hanyar sadarwa - + Address Stats Address Stats @@ -879,34 +904,34 @@ Change will come back to the imported key. Transaction - + Miner Reward - Miner Reward + Ladan Ma'adinai - + Fused - Fused + Fuskanci - + Received - Received + An samu + + + + Moved + Motsa - Moved - Moved - - - Sent - Sent + An aika - + rejected - rejected + an ƙii @@ -924,88 +949,88 @@ Change will come back to the imported key. Rejected - Rejected + An ƙii - Unconfirmed - Ba'a tabbatar ba - - - Mined at An haƙo daga - + %1 blocks ago - - %1 blocks ago - %1 blocks ago + + %1 tubalin da suka gabata + %1 tubalan da suka wuce - + + Waiting for block + Waiting for block + + + Comment Comment - + Fees paid An biya kudade - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size - Size + Girman - + %1 bytes %1 bytes - + Is Coinbase Is Coinbase - + Copy transaction-ID Kwafi shaidar ma'amala-ID - + Fused from my addresses An haɗe daga adiresoshin na - + Sent from my addresses An aiko daga adiresoshin na - + Sent from addresses An aiko daga adiresoshi - + Fused into my addresses An haɗe cikin adiresoshin na - + Received at addresses An karɓa a adiresoshi - + Received at my addresses An karɓa a adireshi na @@ -1015,31 +1040,31 @@ Change will come back to the imported key. Transaction is rejected - Transaction is rejected + An ƙi ma'amalar ciniki Processing - Processing + Sarrafawa Mined - Mined + Haƙar ma'adinai %1 blocks ago Confirmations - - %1 blocks ago - %1 blocks ago + + %1 tubalin da suka gabata + %1 tubalan da suka wuce Fees - Fees + Kudin @@ -1049,7 +1074,7 @@ Change will come back to the imported key. Holds a token - Holds a token + Rike alama @@ -1201,6 +1226,14 @@ Change will come back to the imported key. (An buɗe) + + locked + + + Already running? + Already running? + + main @@ -1209,90 +1242,90 @@ Change will come back to the imported key. Ayyuka - + Archived wallets do not check for activities. Balance may be out of date. Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya tsufa. - + Unarchive Cire Takardu - + This wallet needs a password to open. Wannan asusun tana buƙatar kalmar sirri don buɗewa. - + Password: Kalmar wucewa: - + Invalid password Kalmar shiga mara inganci - + Open Bude - + Send Aika - + Receive Karɓa - + Balance Sauran kudi - + Main balance (money), non specified Babban - + Unconfirmed balance (money) Ba'a tabbatar ba - + Immature balance (money) Rashin cika - + 1 BCH is: %1 1 BCH shi ne: %1 - + Network status Matsayin hanyar sadarwa - + Offline Baya kan na'ura - + Add Bitcoin Cash wallet Sanya asusun Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1301,12 +1334,12 @@ Change will come back to the imported key. - + Preparing... Shiryawa... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index cdae812..685f58f 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -163,7 +163,7 @@ AccountListItem - + Last Transaction Laatste Transactie @@ -710,7 +710,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Copy Address Kopieer adres @@ -780,52 +780,52 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt - + Public-comment Publiek commentaar - + Add a comment you want to include in the transaction, visible for everyone. Voeg een opmerking toe die u wilt opnemen in de transactie, zichtbaar voor iedereen. - + Custom message, to be included in the transaction. Speciaal bericht dat wordt opgenomen in de transactie. - + Text Tekst - + Size Grootte @@ -955,16 +955,11 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - Unconfirmed - Onbevestigd - - - Mined at Gedolven in - + %1 blocks ago %1 blok terug @@ -972,67 +967,72 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + + Waiting for block + Wachten op blok + + + Comment Opmerking - + Fees paid Betaalde kosten - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Size Grootte - + %1 bytes %1 bytes - + Is Coinbase Is Coinbase - + Copy transaction-ID Kopieer transactie-ID - + Fused from my addresses Mijn gefuseerde adressen - + Sent from my addresses Verzonden vanaf mijn adressen - + Sent from addresses Verzonden vanaf adressen - + Fused into my addresses Gefuseerd naar mijn adressen - + Received at addresses Ontvangen op adressen - + Received at my addresses Ontvangen op mijn adressen @@ -1228,6 +1228,14 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. (Geopend) + + locked + + + Already running? + Al actief? + + main diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index a14be8c..90d2f80 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -49,108 +49,113 @@ AccountDetails - + Wallet Details Szczegóły portfela - + Name Nazwa - + Sync Status Status synchronizacji - + Encryption Szyfrowanie - - + + Password Hasło - + Open Otwórz - + Invalid PIN Nieprawidłowy PIN - + Include balance in total Uwzględnij saldo w podsumowaniu - + Hide in private mode Ukryj w trybie prywatnym - + Address List Lista adresów - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Backup details Szczegóły kopii zapasowej - + Seed-phrase Seed - + + Copy + Skopiuj + + + Seed format Format seeda - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -158,11 +163,31 @@ AccountListItem - + Last Transaction Ostatnia transakcja + + ActivityConfigBar + + + Filter + Filter + + + + + Received + Otrzymano + + + + + Sent + Wysłano + + AddressDbStats @@ -342,78 +367,78 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Address to import Adres do zaimportowania - - + + New Wallet Name Nazwa nowego portfela - + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - + + Start Rozpocznij - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Derywacja - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Hasło - + imported wallet password hasło do importowanego portfela @@ -607,7 +632,7 @@ Change will come back to the imported key. - + Warning Uwaga! @@ -663,65 +688,65 @@ Change will come back to the imported key. Wyślij - + Destination Odbiorca - + Max available The maximum balance available Maksymalnie dostępne - + %1 to %2 summary text to pay X-euro to address M %1 do %2 - + Enter Bitcoin Cash Address Wprowadź adres Bitcoin Cash - - + + Copy Address Skopiuj Adres - + Amount Kwota - + Max Maks. - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + Continue Kontynuuj - + Cancel Anuluj - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -732,78 +757,78 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę - + Public-comment Publiczny komentarz - + Add a comment you want to include in the transaction, visible for everyone. Dodaj komentarz, który chcesz zawrzeć w transakcji, widoczny dla wszystkich. - + Custom message, to be included in the transaction. Komunikat użytkownika, który ma być zawarty w transakcji. - + Text Tekst - + Size Rozmiar @@ -816,67 +841,67 @@ Change will come back to the imported key. Ustawienia - + Unit Jednostka - + Show Bitcoin Cash value on Activity page Pokaż wartość Bitcoin Cash na stronie Aktywności - + Show Block Notifications Pokaż powiadomienia o blokach - + When a new block is mined, Flowee Pay shows a desktop notification Gdy nowy blok zostanie wykopany, Flowee Pay pokazuje powiadomienia na pulpicie - + Night Mode Tryb nocny - + Private Mode Tryb prywatny - + Hides private wallets while enabled Ukrywa prywatne portfele - + Font sizing Rozmiar czcionki - + Version Wersja - + Library Version Wersja biblioteki - + Synchronization Synchronizacja - + Network Status Status sieci - + Address Stats Statystyki adresu @@ -884,32 +909,32 @@ Change will come back to the imported key. Transaction - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymano - + Moved Przeniesiono - + Sent Wysłano - + rejected odrzucona @@ -933,16 +958,11 @@ Change will come back to the imported key. - Unconfirmed - Niepotwierdzona - - - Mined at Wykopano - + %1 blocks ago %1 blok temu @@ -952,67 +972,72 @@ Change will come back to the imported key. - + + Waiting for block + Waiting for block + + + Comment Komentarz - + Fees paid Koszt transakcji - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bajtów - + Size Rozmiar - + %1 bytes %1 B - + Is Coinbase Czy Coinbase - + Copy transaction-ID Skopiuj ID transakcji - + Fused from my addresses Fuzja z moich adresów - + Sent from my addresses Wysłano z moich adresów - + Sent from addresses Wysłano z adresów - + Fused into my addresses Fuzja na mój adres - + Received at addresses Wpłynęło na adresy - + Received at my addresses Wpłynęło na moje adresy @@ -1210,6 +1235,14 @@ Change will come back to the imported key. (otwarty) + + locked + + + Already running? + Already running? + + main @@ -1218,90 +1251,90 @@ Change will come back to the imported key. Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1312,12 +1345,12 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… - + QR-Scan Skanowanie QR diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 08fe5d3..d306fee 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -213,17 +213,17 @@ Ihre Geldbörsen - + last active zuletzt aktiv - + Needs PIN to open Benötigt PIN zum Öffnen - + Balance Total Gesamtguthaben @@ -274,6 +274,29 @@ Versteckt als privat markierte Geldbörsen + + BackgroundSyncConfig + + + Background Synchronization + Background Synchronization + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + + + + Allow Background Synchronization + Allow Background Synchronization + + + + Every %1 hours + Every %1 hours + + CurrencySelector @@ -290,7 +313,7 @@ Erkunden - + Open Öffnen @@ -309,44 +332,6 @@ Nur mit einem Kommentar - - GuiSettings - - - Display Settings - Anzeigeeinstellungen - - - - Font sizing - Schriftgröße - - - - Dark Theme - Dunkles Design - - - - Unit - Einheit - - - - Change Currency (%1) - Währung ändern (%1) - - - - Main View - Hauptansicht - - - - Show Bitcoin Cash value - Zeige Bitcoin Cash Wert - - ImportWalletPage @@ -355,105 +340,105 @@ Geldbörse importieren - + Select import method Importmethode auswählen - - Camera - Kamera + + Scan QR + Scan QR - + OR ODER - + Secret as text The seed-phrase or private key Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Next Weiter - + Address to import Zu importierende Adresse - - + + New Wallet Name Neuer Geldbörsen Name - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + Start Start - + Discover Details online check for wallet details Entdecke Details - + Derivation Path Ableitungspfad - + Nothing found for seed. Does it have a password? Nichts für Seed gefunden. Hat es ein Passwort? - + Password Passwort - + imported wallet password importiertes Geldbörsen-Passwort @@ -471,19 +456,16 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Sofortzahlung konfigurieren - - Fast payments for low amounts - Schnelle Zahlungen für niedrige Beträge + + Limits for %1 + argument is a name + Limits for %1 - - Not configured - Nicht konfiguriert - - - - Limit set to: %1 - Limit gesetzt auf: %1 + + Disabled for %1 + argument is a name + Disabled for %1 @@ -580,6 +562,14 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Finde mehr + + MainViewBase + + + No Internet Available + No Internet Available + + MenuOverlay @@ -732,37 +722,37 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBetrag bearbeiten - + Invalid QR code Ungültiger QR Code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. - + details details - + Scanned text: <pre>%1</pre> Gescannter Text: <pre>%1</pre> - + Payment description Zahlungsbeschreibung - + Destination Address Zieladresse - + Unlock Wallet Geldbörse entsperren @@ -805,7 +795,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss ReceiveTab - + Receive Empfange @@ -825,49 +815,49 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussImport läuft... - + Description Beschreibung - + Address Bitcoin Cash address Adresse - + Clear Leeren - - + + Payment Seen Zahlung gesichtet - + High risk transaction Hochriskante Transaktion - + Partially Paid Teilweise bezahlt - + Payment Accepted Zahlung akzeptiert - + Payment Settled Zahlung abgeschlossen - + Continue Fortfahren @@ -941,6 +931,79 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussOptionen + + Settings + + + Display Settings + Anzeigeeinstellungen + + + + Font sizing + Schriftgröße + + + + Main View + Hauptansicht + + + + Show Bitcoin Cash value + Zeige Bitcoin Cash Wert + + + + Background Synchronization + Background Synchronization + + + + Keep your wallets synchronized by enabling this + Keep your wallets synchronized by enabling this + + + + Unit + Einheit + + + + Change Currency + Währung ändern + + + + Dark Theme + Dunkles Design + + + + Follow System + Follow System + + + + Dark + Dark + + + + Light + Light + + + + Notifications + Notifications + + + + On new block found + On new block found + + SlideToApprove @@ -952,52 +1015,60 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss StartupScreen - - Welcome! - Willkommen! - - - + Continue Fortfahren - + Moving the world towards a Bitcoin Cash economy Bewege die Welt in Richtung einer Bitcoin Cash Wirtschaft - + Getting Started Erste Schritte - + All Videos Alle Videos - + Claim a Cash Stamp Cash Stamp beanspruchen - - + + OR OR - + Scan to send to your wallet Scannen um an Ihre Geldbörse zu senden - + Add a different wallet Eine andere Geldbörse hinzufügen + + TextButton + + + Enabled + Enabled + + + + Disabled + Disabled + + TransactionDetails @@ -1174,32 +1245,32 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss TransactionListItem - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + Rejected Abgelehnt @@ -1207,7 +1278,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss UnlockApplication - + Quick Receive Schnell empfangen diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 27788b7..5a11b80 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -26,21 +26,21 @@ - © 2020-2024 Tom Zander and contributors - © 2020-2024 Tom Zander and contributors + © 2020-2025 Tom Zander and contributors + © 2020-2025 Tom Zander and contributors - + Project Home Project Home - + With git repository and issues tracker With git repository and issues tracker - + Telegram Telegram @@ -48,17 +48,17 @@ AccountHistory - + Home Home - + Pay Pay - + Receive Receive @@ -81,115 +81,126 @@ Backup information - + Backup Details Backup Details - + Wallet seed-phrase Wallet seed-phrase - + + Password + Password + + + Seed format Seed format - - + + Starting Height height refers to block-height Starting Height - + Derivation Path Derivation Path - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + Wallet keys Wallet keys - + Show Index toggle to show numbers Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet - - Re-scan Chain - Re-scan Chain + + Really Delete? + Really Delete? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Removing wallet "%1" can not be undone. + + + Remove Wallet Remove Wallet @@ -197,22 +208,22 @@ AccountSelectorPopup - + Your Wallets Your Wallets - + last active last active - + Needs PIN to open Needs PIN to open - + Balance Total Balance Total @@ -220,7 +231,7 @@ AccountSyncState - + Status: Offline Status: Offline @@ -244,35 +255,48 @@ - Default Wallet - Default Wallet - - - - %1 is used on startup - %1 is used on startup - - - Exit Private Mode Exit Private Mode - + Enter Private Mode Enter Private Mode - + Reveals wallets marked private Reveals wallets marked private - + Hides wallets marked private Hides wallets marked private + + BackgroundSyncConfig + + + Background Synchronization + Background Synchronization + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + + + + Allow Background Synchronization + Allow Background Synchronization + + + + Every %1 hours + Every %1 hours + + CurrencySelector @@ -289,48 +313,23 @@ Explore - - ON - Enabled. SHORT TEXT! - ON + + Open + Open - GuiSettings + FilterPopup - - Display Settings - Display Settings + + Transactions Filter + Transactions Filter - - Font sizing - Font sizing - - - - Dark Theme - Dark Theme - - - - Unit - Unit - - - - Change Currency (%1) - Change Currency (%1) - - - - Main View - Main View - - - - Show Bitcoin Cash value - Show Bitcoin Cash value + + Only with a comment + This is a statement about a transaction + Only with a comment @@ -341,107 +340,107 @@ Import Wallet - + Select import method Select import method - - Camera - Camera + + Scan QR + Scan QR - + OR OR - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Next Next - + Address to import Address to import - + + + New Wallet Name + New Wallet Name + + + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - - New Wallet Name - New Wallet Name - - - - + + Start Start - - Password - Password - - - - imported wallet password - imported wallet password - - - + Discover Details online check for wallet details Discover Details - + Derivation Path Derivation Path - - Nothing found for seed - Nothing found for seed + + Nothing found for seed. Does it have a password? + Nothing found for seed. Does it have a password? + + + + Password + Password + + + + imported wallet password + imported wallet password @@ -457,19 +456,16 @@ Change will come back to the imported key. Configure Instant Pay - - Fast payments for low amounts - Fast payments for low amounts + + Limits for %1 + argument is a name + Limits for %1 - - Not configured - Not configured - - - - Limit set to: %1 - Limit set to: %1 + + Disabled for %1 + argument is a name + Disabled for %1 @@ -548,15 +544,36 @@ Change will come back to the imported key. PIN has been set - + Ok Ok + + MainView + + + Explore + Explore + + + + Find More + Find More + + + + MainViewBase + + + No Internet Available + No Internet Available + + MenuOverlay - + Add Wallet Add Wallet @@ -705,37 +722,37 @@ This ensures only one private key will need to be backed up Edit Amount - + Invalid QR code Invalid QR code - + I don't understand the scanned code. I'm sorry, I can't start a payment. I don't understand the scanned code. I'm sorry, I can't start a payment. - + details details - + Scanned text: <pre>%1</pre> Scanned text: <pre>%1</pre> - + Payment description Payment description - + Destination Address Destination Address - + Unlock Wallet Unlock Wallet @@ -783,81 +800,64 @@ This ensures only one private key will need to be backed up Receive - + Share this QR to receive Share this QR to receive - + Encrypted Wallet Encrypted Wallet - + Import Running... Import Running... - - + Description Description - - Amount - requested amount of coin - Amount - - - + Address Bitcoin Cash address Address - + Clear Clear - - + + Payment Seen Payment Seen - - Checking... - Checking... - - - + High risk transaction High risk transaction - + Partially Paid Partially Paid - + Payment Accepted Payment Accepted - + Payment Settled Payment Settled - - Instant payment failed. Wait for confirmation. (double spent proof received) - Instant payment failed. Wait for confirmation. (double spent proof received) - - - + Continue Continue @@ -890,18 +890,119 @@ This ensures only one private key will need to be backed up InstaPay is turned off + + SelectDefaultConfigButton + + + Default Wallet + Default Wallet + + + + %1 is used on startup + %1 is used on startup + + SendTransactionsTab - + Send Send - + Start Payment Start Payment + + + Scan QR + Scan QR + + + + Paste + Paste + + + + Options + Options + + + + Settings + + + Display Settings + Display Settings + + + + Font sizing + Font sizing + + + + Main View + Main View + + + + Show Bitcoin Cash value + Show Bitcoin Cash value + + + + Background Synchronization + Background Synchronization + + + + Keep your wallets synchronized by enabling this + Keep your wallets synchronized by enabling this + + + + Unit + Unit + + + + Change Currency + Change Currency + + + + Dark Theme + Dark Theme + + + + Follow System + Follow System + + + + Dark + Dark + + + + Light + Light + + + + Notifications + Notifications + + + + On new block found + On new block found + SlideToApprove @@ -914,80 +1015,99 @@ This ensures only one private key will need to be backed up StartupScreen - - Welcome! - Welcome! - - - + Continue Continue - + Moving the world towards a Bitcoin Cash economy Moving the world towards a Bitcoin Cash economy - - Scan me to send funds to your HD wallet - Scan me to send funds to your HD wallet + + Getting Started + Getting Started - + + All Videos + All Videos + + + + Claim a Cash Stamp + Claim a Cash Stamp + + + + OR OR - + + Scan to send to your wallet + Scan to send to your wallet + + + Add a different wallet Add a different wallet + + + TextButton - - Claim a Cash Stamp - Claim a Cash Stamp + + Enabled + Enabled + + + + Disabled + Disabled TransactionDetails - + Transaction Details Transaction Details - + Open in Explorer Open in Explorer - + Transaction Hash Transaction Hash - + First Seen First Seen - + Rejected Rejected - - Unconfirmed - Unconfirmed + + Waiting for block + Waiting for block - + Mined at Mined at - + %1 blocks ago %1 blocks ago @@ -995,57 +1115,57 @@ This ensures only one private key will need to be backed up - + Transaction comment Transaction comment - + Size: %1 bytes Size: %1 bytes - + Coinbase Coinbase - + Fees paid Fees paid - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fused from my addresses - + Sent from my addresses Sent from my addresses - + Sent from addresses Sent from addresses - + Fused into my addresses Fused into my addresses - + Received at addresses Received at addresses - + Received at my addresses Received at my addresses @@ -1063,12 +1183,12 @@ This ensures only one private key will need to be backed up Processing - + Mined Mined - + %1 blocks ago Confirmations @@ -1077,42 +1197,47 @@ This ensures only one private key will need to be backed up - + + Waiting for block + Waiting for block + + + Miner Reward Miner Reward - + Fees Fees - + Received Received - + Payment to self Payment to self - + Sent Sent - + Holds a token Holds a token - + Sent to Sent to - + Transaction Details Transaction Details @@ -1120,45 +1245,53 @@ This ensures only one private key will need to be backed up TransactionListItem - + Miner Reward Miner Reward - + Fused Fused - + Received Received - + Moved Moved - + Sent Sent - + Rejected Rejected + + UnlockApplication + + + Quick Receive + Quick Receive + + UnlockWidget - + Enter your wallet passcode Enter your wallet passcode - + Open open wallet with PIN Open diff --git a/translations/floweepay-mobile_es.ts b/translations/floweepay-mobile_es.ts index b148fb8..7dce636 100644 --- a/translations/floweepay-mobile_es.ts +++ b/translations/floweepay-mobile_es.ts @@ -26,8 +26,8 @@ - © 2020-2024 Tom Zander and contributors - ©️ 2020-2024 Tom Zander y colaboradores + © 2020-2025 Tom Zander and contributors + ©️ 2020-2025 Tom Zander y colaboradores @@ -48,17 +48,17 @@ AccountHistory - + Home Inicio - + Pay Pagar - + Receive Recibir @@ -81,120 +81,126 @@ Respaldar información - + Backup Details Detalles de la copia de seguridad - + Wallet seed-phrase Frase-semilla del monedero - + Password Contraseña - + Seed format Formato de semilla - - + + Starting Height height refers to block-height Altura inicial - + Derivation Path Ruta de Derivación - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Por favor, guarde la frase semilla en papel, en el orden correcto, con la ruta de derivación. Esta semilla le permitirá recuperar su cartera en caso de que pierda su móvil. - + <b>Important</b>: Never share your seed-phrase with others! <b>Importante</b>: ¡Nunca comparta su frase semilla con otros! - + Wallet keys Llaves del monedero - + Show Index toggle to show numbers Mostrar índice - + Change Addresses Cambiar direcciones - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Intercambia entre direcciones que otros pueden pagarle y direcciones que el monedero usa para enviarte el cambio de vuelta. - + Used Addresses Direcciones usadas - + Switches between unused and used Bitcoin addresses Alterna entre direcciones no usadas y usadas de Bitcoin - + Addresses and keys Direcciones y claves - + Sync Status Estado de la sincronización - + Hide balance in overviews Ocultar balance en vistas generales - + Hide in private mode Ocultar en modo privado - + Unarchive Wallet Desarchivar monedero - + Archive Wallet Archivar monedero - - Re-scan Chain - Re-escanear la cadena + + Really Delete? + Really Delete? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Removing wallet "%1" can not be undone. + + + Remove Wallet Eliminar Monedero @@ -207,17 +213,17 @@ Tus monederos - + last active Última vez activo - + Needs PIN to open Necesita PIN para abrir - + Balance Total Balance total @@ -268,6 +274,29 @@ Oculta monederos marcados como privados + + BackgroundSyncConfig + + + Background Synchronization + Background Synchronization + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + + + + Allow Background Synchronization + Allow Background Synchronization + + + + Every %1 hours + Every %1 hours + + CurrencySelector @@ -284,7 +313,7 @@ Explorar - + Open Abrir @@ -303,44 +332,6 @@ Only with a comment - - GuiSettings - - - Display Settings - Mostrar ajustes - - - - Font sizing - Tamaño de fuente - - - - Dark Theme - Tema Oscuro - - - - Unit - Unidad - - - - Change Currency (%1) - Cambiar moneda (%1) - - - - Main View - Vista principal - - - - Show Bitcoin Cash value - Mostrar valor de Bitcoin Cash - - ImportWalletPage @@ -349,105 +340,105 @@ Importar monedero - + Select import method Seleccionar método de importación - - Camera - Cámara + + Scan QR + Scan QR - + OR O - + Secret as text The seed-phrase or private key Secreto como texto - + Unknown word(s) found Palabra(s) desconocidas encontradas - + Next Siguiente - + Address to import Dirección a importar - - + + New Wallet Name Nuevo nombre de billetera - + Force Single Address Forzar dirección única - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Cuando está habilitado, no se generarán automáticamente direcciones adicionales en este monedero. El cambio volverá a la clave importada. - - + + Oldest Transaction Transacción más antigua - + Check Age online check for wallet age Comprobar edad - + Nothing found for wallet No se encontró nada para esta cartera - - + + Start Comenzar - + Discover Details online check for wallet details Detalles de Descubre - + Derivation Path Ruta de Derivación - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Contraseña - + imported wallet password Contraseña del monedero importada @@ -465,19 +456,16 @@ El cambio volverá a la clave importada. Configurar Pago Instantáneo - - Fast payments for low amounts - Pagos rápidos para cantidades bajas + + Limits for %1 + argument is a name + Limits for %1 - - Not configured - Sin configurar - - - - Limit set to: %1 - Límite establecido a: %1 + + Disabled for %1 + argument is a name + Disabled for %1 @@ -574,10 +562,18 @@ El cambio volverá a la clave importada. Find More + + MainViewBase + + + No Internet Available + No Internet Available + + MenuOverlay - + Add Wallet Añadir Monedero @@ -726,37 +722,37 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Editar cantidad - + Invalid QR code Código QR inválido - + I don't understand the scanned code. I'm sorry, I can't start a payment. No entiendo el código escaneado. Lo siento, no puedo iniciar un pago. - + details detalles - + Scanned text: <pre>%1</pre> Texto escaneado: <pre>%1</pre> - + Payment description Descripción del pago - + Destination Address Dirección de destino - + Unlock Wallet Desbloquear Monedero @@ -804,81 +800,64 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Recibir - + Share this QR to receive Comparte este QR para recibir - + Encrypted Wallet Monedero encriptado - + Import Running... Ejecutando Importación... - - + Description Descripción - - Amount - requested amount of coin - Monto - - - + Address Bitcoin Cash address Dirección - + Clear Borrar - - + + Payment Seen Pago Enviado - - Checking... - Comprobando... - - - + High risk transaction Transacción de alto riesgo - + Partially Paid Parcialmente pagado - + Payment Accepted Pago Aceptado - + Payment Settled Pago Enviado - - Instant payment failed. Wait for confirmation. (double spent proof received) - Pago instantáneo fallido. Espere la confirmación. (prueba de doble gasto recibida) - - - + Continue Continuar @@ -952,6 +931,79 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Options + + Settings + + + Display Settings + Mostrar ajustes + + + + Font sizing + Tamaño de fuente + + + + Main View + Vista principal + + + + Show Bitcoin Cash value + Mostrar valor de Bitcoin Cash + + + + Background Synchronization + Background Synchronization + + + + Keep your wallets synchronized by enabling this + Keep your wallets synchronized by enabling this + + + + Unit + Unidad + + + + Change Currency + Change Currency + + + + Dark Theme + Tema Oscuro + + + + Follow System + Follow System + + + + Dark + Dark + + + + Light + Light + + + + Notifications + Notifications + + + + On new block found + On new block found + + SlideToApprove @@ -963,81 +1015,99 @@ Esto asegura que solo una clave privada tendrá que ser respaldada StartupScreen - - Welcome! - ¡Bienvenido! - - - + Continue Continuar - + Moving the world towards a Bitcoin Cash economy Moviendo el mundo hacia una economía de Bitcoin Cash - + + Getting Started + Getting Started + + + + All Videos + All Videos + + + Claim a Cash Stamp Reclamar un Cash Stamp - - + + OR O - + Scan to send to your wallet Scan to send to your wallet - + Add a different wallet Añadir un monedero diferente + + TextButton + + + Enabled + Enabled + + + + Disabled + Disabled + + TransactionDetails - + Transaction Details Detalles de la transacción - + Open in Explorer Abrir en el explorador - + Transaction Hash Hash de transacción - + First Seen Visto por primera vez - + Rejected Rechazada - - Unconfirmed - Sin confirmar + + Waiting for block + Waiting for block - + Mined at Minado en - + %1 blocks ago Hace %1 bloque @@ -1045,57 +1115,57 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - + Transaction comment Comentario de la transacción - + Size: %1 bytes Tamaño: %1 bytes - + Coinbase Coinbase - + Fees paid Comisiones pagadas - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses Fusionado desde mis direcciones - + Sent from my addresses Enviado desde mis direcciones - + Sent from addresses Enviado desde direcciones - + Fused into my addresses Fusionado en mis direcciones - + Received at addresses Recibido en direcciones - + Received at my addresses Recibido en mis direcciones @@ -1113,12 +1183,12 @@ Esto asegura que solo una clave privada tendrá que ser respaldada Procesando - + Mined Minado - + %1 blocks ago Confirmations @@ -1127,42 +1197,47 @@ Esto asegura que solo una clave privada tendrá que ser respaldada - + + Waiting for block + Waiting for block + + + Miner Reward Recompensa del minero - + Fees Comisiones - + Received Recibido - + Payment to self Auto pago - + Sent Enviado - + Holds a token Contiene un token - + Sent to Enviado a - + Transaction Details Detalles de la transacción @@ -1170,45 +1245,53 @@ Esto asegura que solo una clave privada tendrá que ser respaldada TransactionListItem - + Miner Reward Recompensa del minero - + Fused Fusionado - + Received Recibido - + Moved Movido - + Sent Enviado - + Rejected Rechazado + + UnlockApplication + + + Quick Receive + Quick Receive + + UnlockWidget - + Enter your wallet passcode Introduzca su código de acceso al monedero - + Open open wallet with PIN Abrir diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts index dcaa37a..bdd9385 100644 --- a/translations/floweepay-mobile_ha.ts +++ b/translations/floweepay-mobile_ha.ts @@ -26,8 +26,8 @@ - © 2020-2024 Tom Zander and contributors - © 2020-2024 Tom Zander and contributors + © 2020-2025 Tom Zander and contributors + © 2020-2025 Tom Zander and contributors @@ -48,17 +48,17 @@ AccountHistory - + Home Gida - + Pay Biya - + Receive Karɓa @@ -81,120 +81,126 @@ Ajiyayyen Bayanai - + Backup Details Ajiyayyen bayanai - + Wallet seed-phrase Asusun Jumla iri-iri - + Password Kwadon shiga - + Seed format Tsarin iri - - + + Starting Height height refers to block-height Fara tsawo - + Derivation Path Hanyar Fitowa - + xpub Xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + Wallet keys Maɓallan asusu - + Show Index toggle to show numbers Nuna index - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Addresses and keys Adireshi da maɓallai - + Sync Status Daidaita matsayi - + Hide balance in overviews Boye ma'auni a cikin bayyani - + Hide in private mode Boye a yanayin sirri - + Unarchive Wallet Ma'ajiyin taskoki - + Archive Wallet Ma'ajiyin taskoki - - Re-scan Chain - Re-scan Chain + + Really Delete? + Really Delete? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Removing wallet "%1" can not be undone. + + + Remove Wallet Remove Wallet @@ -207,17 +213,17 @@ Asusun ku - + last active aiki na ƙarshe - + Needs PIN to open Akwai buƙatar PIN don buɗewa - + Balance Total Jimlar Ma'auni @@ -268,6 +274,29 @@ Ɓoye asusun ɗin da aka yiwa alama na sirri + + BackgroundSyncConfig + + + Background Synchronization + Background Synchronization + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + + + + Allow Background Synchronization + Allow Background Synchronization + + + + Every %1 hours + Every %1 hours + + CurrencySelector @@ -284,7 +313,7 @@ Bincika - + Open Bude @@ -303,44 +332,6 @@ Only with a comment - - GuiSettings - - - Display Settings - Baiyana Saituna - - - - Font sizing - Girman haruffa - - - - Dark Theme - Jigo mai duhu - - - - Unit - Na'ura - - - - Change Currency (%1) - Canja Kuɗi (%1) - - - - Main View - Babban Duba - - - - Show Bitcoin Cash value - Nuna darajar Bitcoin Cash - - ImportWalletPage @@ -349,104 +340,104 @@ Ɗauko asusu - + Select import method Select import method - - Camera - Camera + + Scan QR + Scan QR - + OR Ko - + Secret as text The seed-phrase or private key Secret as text - + Unknown word(s) found Unknown word(s) found - + Next Next - + Address to import Address to import - - + + New Wallet Name New Wallet Name - + Force Single Address Tilasta Adireshi Guda Daya - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + Start Start - + Discover Details online check for wallet details Discover Details - + Derivation Path Hanyar Fitowa - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Kwadon shiga - + imported wallet password imported wallet password @@ -464,19 +455,16 @@ Change will come back to the imported key. Sanya Biyan Nan take - - Fast payments for low amounts - Biyan kuɗi mai sauri don ƙananan kuɗi + + Limits for %1 + argument is a name + Limits for %1 - - Not configured - Ba a saita shi ba - - - - Limit set to: %1 - An saita iyaka zuwa: %1 + + Disabled for %1 + argument is a name + Disabled for %1 @@ -573,10 +561,18 @@ Change will come back to the imported key. Find More + + MainViewBase + + + No Internet Available + No Internet Available + + MenuOverlay - + Add Wallet Ƙara Asusu @@ -724,37 +720,37 @@ This ensures only one private key will need to be backed up Gyara Adadin - + Invalid QR code Lambar QR mara inganci - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ban fahimci lambar da aka bincika ba. Yi hakuri, ba zan iya fara biya ba. - + details bayanai - + Scanned text: <pre>%1</pre> Rubutun da aka duba: <pre>%1</pre> - + Payment description Bayanin biyan kuɗi - + Destination Address Adireshin Zuwa - + Unlock Wallet Buɗe Asusu @@ -802,81 +798,64 @@ This ensures only one private key will need to be backed up Karɓa - + Share this QR to receive Raba wannan QR don karɓa - + Encrypted Wallet Rufaffen asusu - + Import Running... Tsarin Shiga na gudu... - - + Description Bayani - - Amount - requested amount of coin - Adadi - - - + Address Bitcoin Cash address Adireshi - + Clear Share - - + + Payment Seen Anga shaidar biya - - Checking... - Ana dubawa... - - - + High risk transaction High risk transaction - + Partially Paid An biya wani bangare - + Payment Accepted An Karɓa Biya - + Payment Settled Biya An daidaita - - Instant payment failed. Wait for confirmation. (double spent proof received) - Biyan nan take ya kasa. Jira tabbaci. (Anga shaida guda biyu da aka kashe) - - - + Continue Ci gaba @@ -950,6 +929,79 @@ This ensures only one private key will need to be backed up Options + + Settings + + + Display Settings + Display Settings + + + + Font sizing + Girman haruffa + + + + Main View + Main View + + + + Show Bitcoin Cash value + Nuna darajar Bitcoin Cash + + + + Background Synchronization + Background Synchronization + + + + Keep your wallets synchronized by enabling this + Keep your wallets synchronized by enabling this + + + + Unit + Naúrar + + + + Change Currency + Canja Kuɗi + + + + Dark Theme + Jigo mai duhu + + + + Follow System + Follow System + + + + Dark + Dark + + + + Light + Light + + + + Notifications + Notifications + + + + On new block found + On new block found + + SlideToApprove @@ -961,81 +1013,99 @@ This ensures only one private key will need to be backed up StartupScreen - - Welcome! - Barka da zuwa! - - - + Continue Ci gaba - + Moving the world towards a Bitcoin Cash economy Matsar da duniya zuwa tattalin arzikin Bitcoin Cash - + + Getting Started + Getting Started + + + + All Videos + All Videos + + + Claim a Cash Stamp Claim a Cash Stamp - - + + OR Ko - + Scan to send to your wallet Scan to send to your wallet - + Add a different wallet Ƙara wani asusu na daban + + TextButton + + + Enabled + Enabled + + + + Disabled + Disabled + + TransactionDetails - + Transaction Details Cikakken Bayanin Kasuwanci - + Open in Explorer Open in Explorer - + Transaction Hash Zanta Ma'amala - + First Seen First Seen - + Rejected - Rejected + An ƙii - - Unconfirmed - Ba a tabbatar ba + + Waiting for block + Waiting for block - + Mined at An haƙo daga - + %1 blocks ago %1 tubalan da suka wuce @@ -1043,57 +1113,57 @@ This ensures only one private key will need to be backed up - + Transaction comment Sharhin Ma'amala - + Size: %1 bytes Girma: %1 bytes - + Coinbase Coinbase - + Fees paid An biya kudade - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bytes - + Fused from my addresses An haɗe daga adiresoshin na - + Sent from my addresses An aiko daga adiresoshin na - + Sent from addresses An aiko daga adiresoshi - + Fused into my addresses An haɗe cikin adiresoshin na - + Received at addresses An karɓa a adiresoshi - + Received at my addresses An karɓa a adireshi na @@ -1108,59 +1178,64 @@ This ensures only one private key will need to be backed up Processing - Processing + Sarrafawa - + Mined - Mined + Haƙar ma'adinai - + %1 blocks ago Confirmations - - %1 blocks ago - %1 blocks ago + + %1 tubalin da suka gabata + %1 tubalan da suka wuce - + + Waiting for block + Waiting for block + + + Miner Reward - Miner Reward + Ladan Ma'adinai - + Fees - Fees + Kudin - + Received - Received + An samu - + Payment to self - Payment to self + Biyan kuɗi ga kai - + Sent - Sent + An aika - + Holds a token - Holds a token + Rike alama - + Sent to - Sent to + An aika zuwa - + Transaction Details Cikakken Bayanin Kasuwanci @@ -1168,45 +1243,53 @@ This ensures only one private key will need to be backed up TransactionListItem - + Miner Reward - Miner Reward + Ladan Ma'adinai - + Fused - Fused + Fuskanci - + Received - Received + An samu - + Moved - Moved + Motsa - + Sent - Sent + An aika - + Rejected - Rejected + An ƙii + + + + UnlockApplication + + + Quick Receive + Quick Receive UnlockWidget - + Enter your wallet passcode Shiga da lambar wucewar asusu - + Open open wallet with PIN Bude diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 0a42bf8..65ab760 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -213,17 +213,17 @@ Uw portemonnees - + last active Laatst actief - + Needs PIN to open Benodigd pincode bij openen - + Balance Total Totale saldo @@ -274,6 +274,29 @@ Verbergt portemonnees gemarkeerd als privé + + BackgroundSyncConfig + + + Background Synchronization + Achtergrond synchronisatie + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Zonder achtergrond synchronisatie zien uw portemonnees geen nieuwe transacties totdat u Flowee Pay opent + + + + Allow Background Synchronization + Achtergrond synchronisatie toestaan + + + + Every %1 hours + Iedere %1 uur + + CurrencySelector @@ -290,7 +313,7 @@ Ontdek - + Open Open @@ -309,44 +332,6 @@ Alleen met omschrijving - - GuiSettings - - - Display Settings - Scherminstellingen - - - - Font sizing - Lettertypegrootte - - - - Dark Theme - Donker Thema - - - - Unit - Eenheid - - - - Change Currency (%1) - Verander valuta (%1) - - - - Main View - Hoofd overzicht - - - - Show Bitcoin Cash value - Toon Bitcoin Cash waarde - - ImportWalletPage @@ -355,105 +340,105 @@ Portemonnee importeren - + Select import method Kies import-methode - - Camera - Kamera + + Scan QR + Scan QR-code - + OR OF - + Secret as text The seed-phrase or private key Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Next Volgende - + Address to import Te importeren adres - - + + New Wallet Name Nieuwe naam Portemonnee - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + Start Start - + Discover Details online check for wallet details Vind de details - + Derivation Path Derivatie pad - + Nothing found for seed. Does it have a password? Niets gevonden voor herstelzin. Behoeft het een wachtwoord? - + Password Wachtwoord - + imported wallet password Wachtwoord geïmporteerde portemonnee @@ -471,19 +456,16 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Configureer Direct Betalen - - Fast payments for low amounts - Direct betalen bij lage bedragen + + Limits for %1 + argument is a name + Limieten voor %1 - - Not configured - Niet geconfigureerd - - - - Limit set to: %1 - Limiet ingesteld op: %1 + + Disabled for %1 + argument is a name + Uitgeschakeld voor %1 @@ -580,6 +562,14 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Vind meer + + MainViewBase + + + No Internet Available + Geen Internet gevonden + + MenuOverlay @@ -732,37 +722,37 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -805,7 +795,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt ReceiveTab - + Receive Ontvangen @@ -825,49 +815,49 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bezig met importeren... - + Description Omschrijving - + Address Bitcoin Cash address Adres - + Clear Wissen - - + + Payment Seen Betaling gezien - + High risk transaction Transactie met hoog risico - + Partially Paid Deels betaald - + Payment Accepted Betaling geaccepteerd - + Payment Settled Betaling Afgewikkeld - + Continue Doorgaan @@ -941,6 +931,79 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Opties + + Settings + + + Display Settings + Scherminstellingen + + + + Font sizing + Lettertypegrootte + + + + Main View + Opties voor Overzicht + + + + Show Bitcoin Cash value + Toon Bitcoin Cash waarde + + + + Background Synchronization + Achtergrond synchronisatie + + + + Keep your wallets synchronized by enabling this + Houd uw portemonnees gesynchroniseerd door dit in te schakelen + + + + Unit + Eenheid + + + + Change Currency + Verander valuta + + + + Dark Theme + Donker Thema + + + + Follow System + Volg systeemsinstelling + + + + Dark + Donker + + + + Light + Licht + + + + Notifications + Notificaties + + + + On new block found + Bij nieuw gevonden blok + + SlideToApprove @@ -952,52 +1015,60 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt StartupScreen - - Welcome! - Welkom! - - - + Continue Doorgaan - + Moving the world towards a Bitcoin Cash economy We lopen het pad naar een Bitcoin Cash economie - + Getting Started Om te beginnen - + All Videos Alle video's - + Claim a Cash Stamp Claim een Cash Stamp - - + + OR OF - + Scan to send to your wallet Scan om naar uw portemonnee te verzenden - + Add a different wallet Een andere portemonnee toevoegen + + TextButton + + + Enabled + Aan + + + + Disabled + Uitgeschakeld + + TransactionDetails @@ -1174,32 +1245,32 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionListItem - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + Rejected Afgewezen @@ -1207,7 +1278,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt UnlockApplication - + Quick Receive Snel Ontvangen diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 91dab92..7ffb0b0 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -26,8 +26,8 @@ - © 2020-2024 Tom Zander and contributors - ©️ 2020-2024 Tom Zander i współtwórcy + © 2020-2025 Tom Zander and contributors + ©️ 2020-2025 Tom Zander i współtwórcy @@ -48,17 +48,17 @@ AccountHistory - + Home Strona główna - + Pay Zapłać - + Receive Otrzymaj @@ -81,120 +81,126 @@ Dane kopii zapasowej - + Backup Details Szczegóły kopii zapasowej - + Wallet seed-phrase Fraza seedowa portfela - + Password Hasło - + Seed format Format seeda - - + + Starting Height height refers to block-height Wysokość początkowa - + Derivation Path Ścieżka derywacji - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + Wallet keys Klucze portfela - + Show Index toggle to show numbers Pokaż indeks - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Ukryj saldo w podsumowaniach - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet Przywróć portfel - + Archive Wallet Zarchiwizuj portfel - - Re-scan Chain - Skanuj ponownie + + Really Delete? + Really Delete? - + + Removing wallet "%1" can not be undone. + argument is the wallet name + Removing wallet "%1" can not be undone. + + + Remove Wallet Usuń portfel @@ -207,17 +213,17 @@ Twoje portfele - + last active Ostatnio aktywny - + Needs PIN to open Do otwarcia wymagany jest PIN - + Balance Total Saldo Całkowite @@ -268,6 +274,29 @@ Ukrywa portfele oznaczone jako prywatne + + BackgroundSyncConfig + + + Background Synchronization + Background Synchronization + + + + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Without background synchronization your wallets will not see new transactions until you open Flowee Pay + + + + Allow Background Synchronization + Allow Background Synchronization + + + + Every %1 hours + Every %1 hours + + CurrencySelector @@ -284,7 +313,7 @@ Eksploruj - + Open Otwórz @@ -303,44 +332,6 @@ Only with a comment - - GuiSettings - - - Display Settings - Ustawienia wyświetlania - - - - Font sizing - Rozmiar czcionki - - - - Dark Theme - Tryb ciemny - - - - Unit - Jednostka - - - - Change Currency (%1) - Zmień walutę (%1) - - - - Main View - Ekran główny - - - - Show Bitcoin Cash value - Pokazuj wartość Bitcoin Cash - - ImportWalletPage @@ -349,104 +340,104 @@ Importuj portfel - + Select import method Wybierz metodę importu - - Camera - Aparat + + Scan QR + Scan QR - + OR LUB - + Secret as text The seed-phrase or private key Sekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Next Dalej - + Address to import Adres do zaimportowania - - + + New Wallet Name Nazwa nowego portfela - + Force Single Address Wymuś pojedynczy adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - + + Start Rozpocznij - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Path Ścieżka derywacji - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Hasło - + imported wallet password hasło do importowanego portfela @@ -464,19 +455,16 @@ Change will come back to the imported key. Skonfiguruj Szybką Płatność - - Fast payments for low amounts - Szybkie płatności na małe kwoty + + Limits for %1 + argument is a name + Limits for %1 - - Not configured - Nie ustawiono - - - - Limit set to: %1 - Limit ustawiony na: %1 + + Disabled for %1 + argument is a name + Disabled for %1 @@ -573,10 +561,18 @@ Change will come back to the imported key. Find More + + MainViewBase + + + No Internet Available + No Internet Available + + MenuOverlay - + Add Wallet Dodaj portfel @@ -725,37 +721,37 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoEdytuj kwotę - + Invalid QR code Nieprawidłowy kod QR - + I don't understand the scanned code. I'm sorry, I can't start a payment. Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. - + details szczegóły - + Scanned text: <pre>%1</pre> Zeskanowany tekst: <pre>%1</pre> - + Payment description Opis płatności - + Destination Address Adres docelowy - + Unlock Wallet Odblokuj portfel @@ -803,81 +799,64 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoOtrzymaj - + Share this QR to receive Udostępnij ten QR kod, aby otrzymać - + Encrypted Wallet Zaszyfrowany portfel - + Import Running... Trwa Importowanie... - - + Description Opis - - Amount - requested amount of coin - Kwota - - - + Address Bitcoin Cash address Adres - + Clear Wyczyść - - + + Payment Seen Płatność zaobserwowana - - Checking... - Sprawdzanie... - - - + High risk transaction Transakcja o wysokim ryzyku - + Partially Paid Częściowo opłacona - + Payment Accepted Płatność zaakceptowana - + Payment Settled Płatność rozliczona - - Instant payment failed. Wait for confirmation. (double spent proof received) - Płatność błyskawiczna nie powiodła się. Poczekaj na potwierdzenie. (otrzymano dowód podwójnej płatności) - - - + Continue Kontynuuj @@ -951,6 +930,79 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoOptions + + Settings + + + Display Settings + Ustawienia wyświetlania + + + + Font sizing + Rozmiar czcionki + + + + Main View + Ekran główny + + + + Show Bitcoin Cash value + Pokazuj wartość Bitcoin Cash + + + + Background Synchronization + Background Synchronization + + + + Keep your wallets synchronized by enabling this + Keep your wallets synchronized by enabling this + + + + Unit + Jednostka + + + + Change Currency + Zmień walutę + + + + Dark Theme + Tryb ciemny + + + + Follow System + Follow System + + + + Dark + Dark + + + + Light + Light + + + + Notifications + Notifications + + + + On new block found + On new block found + + SlideToApprove @@ -962,81 +1014,99 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego StartupScreen - - Welcome! - Witaj! - - - + Continue Kontynuuj - + Moving the world towards a Bitcoin Cash economy Przesuwamy świat w kierunku ekonomii Bitcoin Cash - + + Getting Started + Getting Started + + + + All Videos + All Videos + + + Claim a Cash Stamp Odbierz Cash Stamp - - + + OR LUB - + Scan to send to your wallet Scan to send to your wallet - + Add a different wallet Dodaj inny portfel + + TextButton + + + Enabled + Enabled + + + + Disabled + Disabled + + TransactionDetails - + Transaction Details Szczegóły transakcji - + Open in Explorer Otwórz w Eksploratorze - + Transaction Hash Hasz Transakcji - + First Seen Zaobserwowano - + Rejected Odrzucona - - Unconfirmed - Niepotwierdzona + + Waiting for block + Waiting for block - + Mined at Wykopano - + %1 blocks ago %1 blok temu @@ -1046,57 +1116,57 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - + Transaction comment Komentarz do transakcji - + Size: %1 bytes Rozmiar: %1 bajtów - + Coinbase Coinbase - + Fees paid Koszt transakcji - + %1 Satoshi / 1000 bytes %1 Satoshi / 1000 bajtów - + Fused from my addresses Fuzja z moich adresów - + Sent from my addresses Wysłano z moich adresów - + Sent from addresses Wysłano z adresów - + Fused into my addresses Fuzja na mój adres - + Received at addresses Wpłynęło na adresy - + Received at my addresses Wpłynęło na moje adresy @@ -1114,12 +1184,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoPrzetwarzanie - + Mined Wykopano - + %1 blocks ago Confirmations @@ -1130,42 +1200,47 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego - + + Waiting for block + Waiting for block + + + Miner Reward Nagroda dla górnika - + Fees Opłaty - + Received Otrzymano - + Payment to self Płatność dla siebie - + Sent Wysłano - + Holds a token Przechowuje token - + Sent to Wysłano do - + Transaction Details Szczegóły transakcji @@ -1173,45 +1248,53 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego TransactionListItem - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymano - + Moved Przeniesiono - + Sent Wysłano - + Rejected Odrzucono + + UnlockApplication + + + Quick Receive + Quick Receive + + UnlockWidget - + Enter your wallet passcode Wprowadź hasło portfela - + Open open wallet with PIN Otwórz diff --git a/translations/mobile-i18n.qrc b/translations/mobile-i18n.qrc index 3c225bc..6d3c60b 100644 --- a/translations/mobile-i18n.qrc +++ b/translations/mobile-i18n.qrc @@ -30,5 +30,17 @@ module-send-sweep_pl.qm module-send-sweep_en.qm module-send-sweep_ha.qm + module-bigtransfer_nl.qm + module-bigtransfer_es.qm + module-bigtransfer_de.qm + module-bigtransfer_pl.qm + module-bigtransfer_en.qm + module-bigtransfer_ha.qm + module-social-feed_nl.qm + module-social-feed_es.qm + module-social-feed_de.qm + module-social-feed_pl.qm + module-social-feed_en.qm + module-social-feed_ha.qm diff --git a/translations/module-bigtransfer_de.ts b/translations/module-bigtransfer_de.ts new file mode 100644 index 0000000..5ea022a --- /dev/null +++ b/translations/module-bigtransfer_de.ts @@ -0,0 +1,97 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Wallet to Wallet + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + + + + Move funds to another wallet + Move funds to another wallet + + + + Main + + + Wallet to Wallet + Wallet to Wallet + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonimity preserving transactions. + + + + Spending Wallet + Spending Wallet + + + + Addresses + Addresses + + + + Coins + Coins + + + + Destination Wallet + Destination Wallet + + + + Prepare... + Prepare... + + + + ShowPrepared + + + Verify %1 Transactions + + Verify %1 Transactions + Verify %1 Transactions + + + + + Coins + Coins + + + + Send Now + Send Now + + + + Create and send all %1 transactions + + Create and send all %1 transactions + Create and send all %1 transactions + + + + + Target Coins + indicates a number + Target Coins + + + diff --git a/translations/module-bigtransfer_en.ts b/translations/module-bigtransfer_en.ts new file mode 100644 index 0000000..1aecb22 --- /dev/null +++ b/translations/module-bigtransfer_en.ts @@ -0,0 +1,97 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Wallet to Wallet + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + + + + Move funds to another wallet + Move funds to another wallet + + + + Main + + + Wallet to Wallet + Wallet to Wallet + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonimity preserving transactions. + + + + Spending Wallet + Spending Wallet + + + + Addresses + Addresses + + + + Coins + Coins + + + + Destination Wallet + Destination Wallet + + + + Prepare... + Prepare... + + + + ShowPrepared + + + Verify %1 Transactions + + Verify Transaction + Verify %1 Transactions + + + + + Coins + Coins + + + + Send Now + Send Now + + + + Create and send all %1 transactions + + Create and send the transaction + Create and send all %1 transactions + + + + + Target Coins + indicates a number + Target Coins + + + diff --git a/translations/module-bigtransfer_es.ts b/translations/module-bigtransfer_es.ts new file mode 100644 index 0000000..15f9037 --- /dev/null +++ b/translations/module-bigtransfer_es.ts @@ -0,0 +1,97 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Wallet to Wallet + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + + + + Move funds to another wallet + Move funds to another wallet + + + + Main + + + Wallet to Wallet + Wallet to Wallet + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonimity preserving transactions. + + + + Spending Wallet + Spending Wallet + + + + Addresses + Addresses + + + + Coins + Coins + + + + Destination Wallet + Destination Wallet + + + + Prepare... + Prepare... + + + + ShowPrepared + + + Verify %1 Transactions + + Verify %1 Transactions + Verify %1 Transactions + + + + + Coins + Coins + + + + Send Now + Send Now + + + + Create and send all %1 transactions + + Create and send all %1 transactions + Create and send all %1 transactions + + + + + Target Coins + indicates a number + Target Coins + + + diff --git a/translations/module-bigtransfer_ha.ts b/translations/module-bigtransfer_ha.ts new file mode 100644 index 0000000..7cdd695 --- /dev/null +++ b/translations/module-bigtransfer_ha.ts @@ -0,0 +1,97 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Wallet to Wallet + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + + + + Move funds to another wallet + Move funds to another wallet + + + + Main + + + Wallet to Wallet + Wallet to Wallet + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonimity preserving transactions. + + + + Spending Wallet + Spending Wallet + + + + Addresses + Addresses + + + + Coins + Coins + + + + Destination Wallet + Destination Wallet + + + + Prepare... + Prepare... + + + + ShowPrepared + + + Verify %1 Transactions + + Verify %1 Transactions + Verify %1 Transactions + + + + + Coins + Coins + + + + Send Now + Send Now + + + + Create and send all %1 transactions + + Create and send all %1 transactions + Create and send all %1 transactions + + + + + Target Coins + indicates a number + Target Coins + + + diff --git a/translations/module-bigtransfer_nl.ts b/translations/module-bigtransfer_nl.ts new file mode 100644 index 0000000..8fb4ba7 --- /dev/null +++ b/translations/module-bigtransfer_nl.ts @@ -0,0 +1,97 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Portemonnee naar Portemonnee + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Verplaats veel munten tussen portemonnees, optimaliseer voor anonimiteit met één transactie per adres. Het is mogelijk om ze over verschillende adressen op te splitsen. + + + + Move funds to another wallet + Verplaats saldo naar een andere portemonnee + + + + Main + + + Wallet to Wallet + Portemonnee naar Portemonnee + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Selecteer twee portemonnees om geld over te maken, met behulp van anonimiteit behoudende transacties. + + + + Spending Wallet + Uitgave portemonnee + + + + Addresses + Adressen + + + + Coins + Munten + + + + Destination Wallet + Bestemming portemonnee + + + + Prepare... + Bereid voor... + + + + ShowPrepared + + + Verify %1 Transactions + + Verifieer %1 transactie + Verifieer %1 transacties + + + + + Coins + Munten + + + + Send Now + Verstuur nu + + + + Create and send all %1 transactions + + Verstuur de transactie + Verstuur alle %1 transacties + + + + + Target Coins + indicates a number + Bestemmingsmunten + + + diff --git a/translations/module-bigtransfer_pl.ts b/translations/module-bigtransfer_pl.ts new file mode 100644 index 0000000..4f881cb --- /dev/null +++ b/translations/module-bigtransfer_pl.ts @@ -0,0 +1,101 @@ + + + + + BigTransferModuleInfo + + + + + Wallet to Wallet + Wallet to Wallet + + + + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + + + + Move funds to another wallet + Move funds to another wallet + + + + Main + + + Wallet to Wallet + Wallet to Wallet + + + + Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonimity preserving transactions. + + + + Spending Wallet + Spending Wallet + + + + Addresses + Addresses + + + + Coins + Coins + + + + Destination Wallet + Destination Wallet + + + + Prepare... + Prepare... + + + + ShowPrepared + + + Verify %1 Transactions + + Verify %1 Transactions + Verify %1 Transactions + Verify %1 Transactions + Verify %1 Transactions + + + + + Coins + Coins + + + + Send Now + Send Now + + + + Create and send all %1 transactions + + Create and send all %1 transactions + Create and send all %1 transactions + Create and send all %1 transactions + Create and send all %1 transactions + + + + + Target Coins + indicates a number + Target Coins + + + diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index d723178..1ad5c5b 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -14,12 +14,12 @@ Dieses Modul ermöglicht das Erstellen von leistungsfähigeren Transaktionen in einer einfachen Benutzeroberfläche. - + Build Transaction Transaktion erstellen - + Manually select templates Templates manuell auswählen @@ -71,109 +71,109 @@ Baue Transaktion - + Building Error error during build Fehler beim Erstellen - + Add Payment Detail page title Zahlungsdetails hinzufügen - - + + Add Destination Empfänger hinzufügen - + an address to send money to eine Adresse zum Überweisen von Geld - + Confirm Sending confirm we want to send the transaction Senden bestätigen - + TXID TXID - + Copy transaction-ID Transaktions-ID kopieren - + Fee Gebühr - + Transaction size Transaktionsgröße - + %1 bytes %1 Bytes - + Fee per byte Gebühr pro Byte - + %1 sat/byte fee %1 Sat/Byte - + Destination Empfänger - + unset indication of desination not being set nicht eingestellt - + invalid address is not correct ungültig - + Copy Address Adresse kopieren - + Drag to Edit Ziehen zum Bearbeiten - + Drag to Delete Ziehen zum Löschen - + Unlock Wallet Geldbörse entsperren - + Prepare Payment... Zahlung vorbereiten... diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index 4a27303..251dbb2 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -14,10 +14,54 @@ This module allows building more powerful transactions in one simple user interface. - + Build Transaction Build Transaction + + + Manually select templates + Manually select templates + + + + DestinationEditPage + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Send All + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Copy Address + Copy Address + + + + Warning + Warning + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + + + + I am certain + I am certain + PayToOthers @@ -27,141 +71,109 @@ Build Transaction - + Building Error error during build Building Error - + Add Payment Detail page title Add Payment Detail - - + + Add Destination Add Destination - + an address to send money to an address to send money to - + Confirm Sending confirm we want to send the transaction Confirm Sending - + TXID TXID - + Copy transaction-ID Copy transaction-ID - + Fee Fee - + Transaction size Transaction size - + %1 bytes %1 bytes - + Fee per byte Fee per byte - + %1 sat/byte fee %1 sat/byte - + Destination Destination - + unset - indication of empty + indication of desination not being set unset - + invalid address is not correct invalid - - + Copy Address Copy Address - - Edit Destination - Edit Destination - - - - Send All - all money in wallet - Send All - - - - Bitcoin Cash Address - Bitcoin Cash Address - - - - Warning - Warning - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - - - - I am certain - I am certain - - - + Drag to Edit Drag to Edit - + Drag to Delete Drag to Delete - + Unlock Wallet Unlock Wallet - + Prepare Payment... Prepare Payment... diff --git a/translations/module-build-transaction_es.ts b/translations/module-build-transaction_es.ts index 04b16b2..29ca572 100644 --- a/translations/module-build-transaction_es.ts +++ b/translations/module-build-transaction_es.ts @@ -14,16 +14,55 @@ Este módulo permite construir transacciones más potentes en una interfaz de usuario simple. - + Build Transaction Construir transacción - + Manually select templates Manually select templates + + DestinationEditPage + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Enviar Todo + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Copy Address + Copiar la dirección + + + + Warning + Advertencia + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? + + + + I am certain + I am certain + + PayToOthers @@ -32,141 +71,109 @@ Construir transacción - + Building Error error during build Error al construir - + Add Payment Detail page title Añadir detalles del pago - - + + Add Destination Añadir destino - + an address to send money to una dirección para enviar dinero a - + Confirm Sending confirm we want to send the transaction Confirmar envío - + TXID TXID - + Copy transaction-ID Copiar ID de la transacción - + Fee Comisión - + Transaction size Tamaño de la transacción - + %1 bytes %1 bytes - + Fee per byte Comisión por byte - + %1 sat/byte fee %1 sat/byte - + Destination Destino - + unset indication of desination not being set unset - + invalid address is not correct inválido - - + Copy Address Copiar dirección - - Edit Destination - Editar destino - - - - Send All - all money in wallet - Enviar Todo - - - - Bitcoin Cash Address - Dirección de Bitcoin Cash - - - - Warning - Advertencia - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Esta es una dirección BTC, que es una moneda incompatible. Tus fondos podrían perderse y Flowee no tendrá forma de recuperarlos. ¿Estás seguro de que esta es la dirección correcta? - - - - I am certain - Estoy seguro - - - + Drag to Edit Arrastre para editar - + Drag to Delete Arrastre para eliminar - + Unlock Wallet Desbloquear Monedero - + Prepare Payment... Preparar pago... diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts index 1cb6dbc..4dc8241 100644 --- a/translations/module-build-transaction_ha.ts +++ b/translations/module-build-transaction_ha.ts @@ -14,16 +14,55 @@ Wannan tsarin yana ba da damar gina ƙarin ma'amaloli masu ƙarfi a cikin sauƙin mai amfani guda ɗaya. - + Build Transaction Gina Ma'amala - + Manually select templates Manually select templates + + DestinationEditPage + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Tura duka + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Copy Address + Kwafi Adireshi + + + + Warning + Gargaɗi + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? + + + + I am certain + I am certain + + PayToOthers @@ -32,141 +71,109 @@ Gina Ma'amala - + Building Error error during build Kuskuren gini - + Add Payment Detail page title Ƙara Bayanin Biyan Kuɗi - - + + Add Destination Ƙara madakata - + an address to send money to Adireshin da za'a aika kudi zuwa - + Confirm Sending confirm we want to send the transaction Tabbatar da Aika - + TXID TXID - + Copy transaction-ID Kwafi shaidar ma'amala-ID - + Fee Kudin - + Transaction size Girman ciniki - + %1 bytes %1 bytes - + Fee per byte Kudin kowane byte - + %1 sat/byte fee %1 sat/bytes - + Destination Madakata - + unset indication of desination not being set unset - + invalid address is not correct Ba daidai ba - - + Copy Address Kwafi Adireshi - - Edit Destination - Gyara Madakata - - - - Send All - all money in wallet - Tura duka - - - - Bitcoin Cash Address - Adireshin Kuɗi na Bitcoin - - - - Warning - Gargaɗi - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - - - - I am certain - Na tabbata - - - + Drag to Edit Jawo don Gyarawa - + Drag to Delete Jawo don gogewa - + Unlock Wallet Buɗe Asusu - + Prepare Payment... Shirya Biya... diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index 448f25f..99efac6 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -14,12 +14,12 @@ Deze module maakt het mogelijk om krachtigere transacties te bouwen in één eenvoudige gebruikersinterface. - + Build Transaction Bouw transactie - + Manually select templates Selecteer templates handmatig @@ -71,109 +71,109 @@ Bouw transactie - + Building Error error during build Fout bij bouwen - + Add Payment Detail page title Voeg betalingsgegevens toe - - + + Add Destination Voeg bestemming toe - + an address to send money to een adres om geld naar te sturen - + Confirm Sending confirm we want to send the transaction Verzenden bevestigen - + TXID TXID - + Copy transaction-ID Transactie-ID kopiëren - + Fee Transactiekosten - + Transaction size Transactie grootte - + %1 bytes %1 byte - + Fee per byte Transactiekosten per byte - + %1 sat/byte fee %1 sat/byte - + Destination Bestemming - + unset indication of desination not being set niet ingesteld - + invalid address is not correct ongeldig - + Copy Address Kopieer adres - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - + Unlock Wallet Portemonnee ontgrendelen - + Prepare Payment... Betaling voorbereiden... diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 3393711..6b714b3 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -14,16 +14,55 @@ Ten moduł umożliwia tworzenie potężniejszych transakcji w jednym prostym interfejsie użytkownika. - + Build Transaction Utwórz transakcję - + Manually select templates Manually select templates + + DestinationEditPage + + + Edit Destination + Edit Destination + + + + Send All + all money in wallet + Wyślij wszystko + + + + Bitcoin Cash Address + Bitcoin Cash Address + + + + Copy Address + Skopiuj adres + + + + Warning + Uwaga! + + + + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? + Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? + + + + I am certain + I am certain + + PayToOthers @@ -32,141 +71,109 @@ Utwórz transakcję - + Building Error error during build Błąd przy tworzeniu - + Add Payment Detail page title Dodaj szczegół płatności - - + + Add Destination Dodaj Odbiorcę - + an address to send money to adres do wysłania pieniędzy - + Confirm Sending confirm we want to send the transaction Potwierdź Wysyłanie - + TXID TXID - + Copy transaction-ID Kopiuj ID transakcji - + Fee Opłata - + Transaction size Rozmiar transakcji - + %1 bytes %1 bajtów - + Fee per byte Opłata za bajt - + %1 sat/byte fee %1 sat/bajt - + Destination Odbiorca - + unset indication of desination not being set unset - + invalid address is not correct nieprawidłowy - - + Copy Address Skopiuj adres - - Edit Destination - Edytuj Odbiorcę - - - - Send All - all money in wallet - Wyślij wszystko - - - - Bitcoin Cash Address - Adres Bitcoin Cash - - - - Warning - Uwaga - - - - This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - - - - I am certain - Na pewno - - - + Drag to Edit Przeciągnij, aby edytować - + Drag to Delete Przeciągnij, aby usunąć - + Unlock Wallet Odblokuj portfel - + Prepare Payment... Przygotuj płatność... diff --git a/translations/module-peers-view_de.ts b/translations/module-peers-view_de.ts index eab2298..ac597ce 100644 --- a/translations/module-peers-view_de.ts +++ b/translations/module-peers-view_de.ts @@ -14,54 +14,59 @@ Statistiken - + + No Internet Available + No Internet Available + + + Address network address (IP) Adresse - + Start-height: %1 Starthöhe: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Öffne Verbindung - + Validating peer Validiere Knoten - + Validated Validiert - + Good Peer A useful peer Guter Knoten - + Peer for wallet: %1 Peer für Geldbörse: %1 - + Disconnect Peer Trenne Knoten - + Ban Peer Sperre Knoten diff --git a/translations/module-peers-view_en.ts b/translations/module-peers-view_en.ts index ccadf6f..f38b9b6 100644 --- a/translations/module-peers-view_en.ts +++ b/translations/module-peers-view_en.ts @@ -14,54 +14,59 @@ Statistics - + + No Internet Available + No Internet Available + + + Address network address (IP) Address - + Start-height: %1 Start-height: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Opening Connection - + Validating peer Validating peer - + Validated Validated - + Good Peer A useful peer Good Peer - + Peer for wallet: %1 Peer for wallet: %1 - + Disconnect Peer Disconnect Peer - + Ban Peer Ban Peer diff --git a/translations/module-peers-view_es.ts b/translations/module-peers-view_es.ts index b266c5a..8edb756 100644 --- a/translations/module-peers-view_es.ts +++ b/translations/module-peers-view_es.ts @@ -14,54 +14,59 @@ Estadísticas - + + No Internet Available + No Internet Available + + + Address network address (IP) Dirección - + Start-height: %1 Altura de inicio: %1 - + ban-score: %1 puntaje: %1 - + Opening Connection Abriendo conexión - + Validating peer Validando par - + Validated Validado - + Good Peer A useful peer Par bueno - + Peer for wallet: %1 Par para el monedero: %1 - + Disconnect Peer Desconectar par - + Ban Peer Banear par diff --git a/translations/module-peers-view_ha.ts b/translations/module-peers-view_ha.ts index cf47cff..e90f0fc 100644 --- a/translations/module-peers-view_ha.ts +++ b/translations/module-peers-view_ha.ts @@ -14,54 +14,59 @@ Statistics - + + No Internet Available + No Internet Available + + + Address network address (IP) Adireshi - + Start-height: %1 Fara-tsawo: %1 - + ban-score: %1 Tsame-ci: %1 - + Opening Connection Opening Connection - + Validating peer Validating peer - + Validated Validated - + Good Peer A useful peer Good Peer - + Peer for wallet: %1 Haɗin asusu %1 - + Disconnect Peer Disconnect Peer - + Ban Peer Ban Peer diff --git a/translations/module-peers-view_nl.ts b/translations/module-peers-view_nl.ts index e51dcf4..f763c2b 100644 --- a/translations/module-peers-view_nl.ts +++ b/translations/module-peers-view_nl.ts @@ -14,54 +14,59 @@ Statistieken - + + No Internet Available + Geen Internet gevonden + + + Address network address (IP) Adres - + Start-height: %1 Beginhoogte: %1 - + ban-score: %1 ban-score: %1 - + Opening Connection Openen van verbinding - + Validating peer Controleren peer - + Validated Gecontroleerd - + Good Peer A useful peer Goede Peer - + Peer for wallet: %1 Peer voor portemonnee: %1 - + Disconnect Peer Verbreek verbinding met peer - + Ban Peer Verban Peer diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts index b1ffb14..02cda3e 100644 --- a/translations/module-peers-view_pl.ts +++ b/translations/module-peers-view_pl.ts @@ -14,54 +14,59 @@ Statystyki - + + No Internet Available + No Internet Available + + + Address network address (IP) Adres - + Start-height: %1 Wysokość początkowa: %1 - + ban-score: %1 punktacja banu: %1 - + Opening Connection Otwieranie połączenia - + Validating peer Weryfikacja peera - + Validated Zweryfikowano - + Good Peer A useful peer Przydatny peer - + Peer for wallet: %1 Peer dla portfela: %1 - + Disconnect Peer Rozłącz peer - + Ban Peer Zbanuj peera diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index 9e12444..85d15fa 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -51,18 +51,18 @@ SendSweepModuleInfo - Sweep & Send - Einlesen & Senden + Claim Cash Stamp + Claim Cash Stamp - Allows sweeping a paper-wallet, moving the contents to your own wallet - Ermöglicht das Verschieben einer Papier-Geldbörse und das Verschieben des Inhalts auf Ihre eigene Geldbörse + A QR code with a CashStamp can be taken to transfer the money to your wallet. + A QR code with a CashStamp can be taken to transfer the money to your wallet. - Sweep Paper Wallet - Papier-Geldbörse einlesen + Claim a Cash Stamp + Cash Stamp beanspruchen diff --git a/translations/module-send-sweep_en.ts b/translations/module-send-sweep_en.ts index 9de41db..ccfbb2c 100644 --- a/translations/module-send-sweep_en.ts +++ b/translations/module-send-sweep_en.ts @@ -9,97 +9,69 @@ Sweep coins - - Scan QR (WIF) to find funds - Please note that WIF and QR are names - Scan QR (WIF) to find funds - - - + Sweeping from address: Sweeping from address: - + Found %1 coins on address. this is a simple number - Found %1 coins on address. + Found one coin on address. Found %1 coins on address. - + Ignoring %1 tokens. Number of CashTokens - Ignoring %1 tokens. + Ignoring one token. Ignoring %1 tokens. - + Failed to understand QR Failed to understand QR - + Indexer results invalid. Please try again. Indexer results invalid. Please try again. - + Transfer to: Transfer to: - - - Sending Payment - Sending Payment - - - - Payment Sent - Payment Sent - - - - Failed - Failed - - - - Transaction rejected by network - Transaction rejected by network - - - - The payment has been sent to: - Followed by the address - The payment has been sent to: - - - - Close - Close - SendSweepModuleInfo - - Sweep & Send - Sweep & Send + + Claim Cash Stamp + Claim Cash Stamp - - Allows sweeping a paper-wallet, moving the contents to your own wallet - Allows sweeping a paper-wallet, moving the contents to your own wallet + + A QR code with a CashStamp can be taken to transfer the money to your wallet. + A QR code with a CashStamp can be taken to transfer the money to your wallet. - - Sweep Paper Wallet - Sweep Paper Wallet + + Claim a Cash Stamp + Claim a Cash Stamp + + + + StartScan + + + Scan QR (WIF) to find funds + Please note that WIF and QR are names + Scan QR (WIF) to find funds diff --git a/translations/module-send-sweep_es.ts b/translations/module-send-sweep_es.ts index 9554523..8f9c685 100644 --- a/translations/module-send-sweep_es.ts +++ b/translations/module-send-sweep_es.ts @@ -46,54 +46,23 @@ Transfer to: Transferir a: - - - Sending Payment - Enviando Pago - - - - Payment Sent - Pago Enviado - - - - Failed - Fallido - - - - Transaction rejected by network - Transacción rechazada por la red - - - - The payment has been sent to: - Followed by the address - El pago ha sido enviado a: - - - - Close - Cerrar - SendSweepModuleInfo - Sweep & Send - Barrido y Enviar + Claim Cash Stamp + Claim Cash Stamp - Allows sweeping a paper-wallet, moving the contents to your own wallet - Permite barrer una cartera de papel, moviendo el contenido a su propia cartera + A QR code with a CashStamp can be taken to transfer the money to your wallet. + A QR code with a CashStamp can be taken to transfer the money to your wallet. - Sweep Paper Wallet - Importar billetera de papel + Claim a Cash Stamp + Reclamar un Cash Stamp diff --git a/translations/module-send-sweep_ha.ts b/translations/module-send-sweep_ha.ts index 43ad4a6..7442b4e 100644 --- a/translations/module-send-sweep_ha.ts +++ b/translations/module-send-sweep_ha.ts @@ -46,54 +46,23 @@ Transfer to: Transfer to: - - - Sending Payment - Aika Biyan Kuɗi - - - - Payment Sent - An aika Biya - - - - Failed - Ba a yi nasara ba - - - - Transaction rejected by network - hanyar sadarwa ta ƙi ciniki - - - - The payment has been sent to: - Followed by the address - The payment has been sent to: - - - - Close - Kulle - SendSweepModuleInfo - Sweep & Send - Sweep & Send + Claim Cash Stamp + Claim Cash Stamp - Allows sweeping a paper-wallet, moving the contents to your own wallet - Allows sweeping a paper-wallet, moving the contents to your own wallet + A QR code with a CashStamp can be taken to transfer the money to your wallet. + A QR code with a CashStamp can be taken to transfer the money to your wallet. - Sweep Paper Wallet - Sweep Paper Wallet + Claim a Cash Stamp + Claim a Cash Stamp diff --git a/translations/module-send-sweep_nl.ts b/translations/module-send-sweep_nl.ts index e54d17a..b77ff0a 100644 --- a/translations/module-send-sweep_nl.ts +++ b/translations/module-send-sweep_nl.ts @@ -51,18 +51,18 @@ SendSweepModuleInfo - Sweep & Send - Opvegen & Verzenden + Claim Cash Stamp + Claim een Cash Stamp - Allows sweeping a paper-wallet, moving the contents to your own wallet - Biedt het opvegen van een papieren portemonnee, en de inhoud verplaatsen naar uw eigen portemonnee + A QR code with a CashStamp can be taken to transfer the money to your wallet. + Een QR-code met een Cash Stamp kan worden gebruikt om het geld naar uw portemonnee te verplaatsen. - Sweep Paper Wallet - Papieren portemonnee opvegen + Claim a Cash Stamp + Claim een Cash Stamp diff --git a/translations/module-send-sweep_pl.ts b/translations/module-send-sweep_pl.ts index cac8c62..da9e3d6 100644 --- a/translations/module-send-sweep_pl.ts +++ b/translations/module-send-sweep_pl.ts @@ -50,54 +50,23 @@ Transfer to: Przekaż do: - - - Sending Payment - Wysyłanie Płatności - - - - Payment Sent - Płatność Wysłana - - - - Failed - Niepowodzenie - - - - Transaction rejected by network - Transakcja odrzucona przez sieć - - - - The payment has been sent to: - Followed by the address - Płatność została wysłana do: - - - - Close - Zamknij - SendSweepModuleInfo - Sweep & Send - Zgarnij i wyślij + Claim Cash Stamp + Claim Cash Stamp - Allows sweeping a paper-wallet, moving the contents to your own wallet - Pozwala na zgarnięcie środków z portfela papierowego poprzez przeniesienie ich do własnego portfela. + A QR code with a CashStamp can be taken to transfer the money to your wallet. + A QR code with a CashStamp can be taken to transfer the money to your wallet. - Sweep Paper Wallet - Wyczyść papierowy portfel + Claim a Cash Stamp + Odbierz Cash Stamp diff --git a/translations/module-social-feed_de.ts b/translations/module-social-feed_de.ts new file mode 100644 index 0000000..5b5bda5 --- /dev/null +++ b/translations/module-social-feed_de.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Run time: + + + + SocialFeedModuleInfo + + + Help and Learning + Help and Learning + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + + + diff --git a/translations/module-social-feed_en.ts b/translations/module-social-feed_en.ts new file mode 100644 index 0000000..a816f7f --- /dev/null +++ b/translations/module-social-feed_en.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Run time: + + + + SocialFeedModuleInfo + + + Help and Learning + Help and Learning + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + + + diff --git a/translations/module-social-feed_es.ts b/translations/module-social-feed_es.ts new file mode 100644 index 0000000..68ce650 --- /dev/null +++ b/translations/module-social-feed_es.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Run time: + + + + SocialFeedModuleInfo + + + Help and Learning + Help and Learning + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + + + diff --git a/translations/module-social-feed_ha.ts b/translations/module-social-feed_ha.ts new file mode 100644 index 0000000..921a735 --- /dev/null +++ b/translations/module-social-feed_ha.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Run time: + + + + SocialFeedModuleInfo + + + Help and Learning + Help and Learning + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + + + diff --git a/translations/module-social-feed_nl.ts b/translations/module-social-feed_nl.ts new file mode 100644 index 0000000..44c7dee --- /dev/null +++ b/translations/module-social-feed_nl.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Looptijd: + + + + SocialFeedModuleInfo + + + Help and Learning + Hulp en leren + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Wil je de experts je laten zien hoe je Bitcoin Cash met Flowee Pay kunt gebruiken? Vind alles wat je wilt via deze bibliotheek van video's. + + + diff --git a/translations/module-social-feed_pl.ts b/translations/module-social-feed_pl.ts new file mode 100644 index 0000000..79f5590 --- /dev/null +++ b/translations/module-social-feed_pl.ts @@ -0,0 +1,30 @@ + + + + + Listing + + + Videos + Videos + + + + Run time: + Run time: + + + + SocialFeedModuleInfo + + + Help and Learning + Help and Learning + + + + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + + + -- 2.54.0 From 079f26329601992b266075dae7d5a8af372db72e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 4 Mar 2025 22:43:21 +0100 Subject: [PATCH 1260/1428] Add a watchdog timer on the Periodic setup. Avoid the process to run more than 120 seconds, which should be plenty. If it is not, it will continue next iteration. --- src/Periodic.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Periodic.cpp b/src/Periodic.cpp index 3222f8a..df8d430 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -65,10 +65,15 @@ int Periodic::run() this, &Periodic::checkFinished); app->startNet(); // lets go! app->prices()->start(); - }); - FloweePay::instance()->startP2PInit(); + // watchdog timer. + QTimer::singleShot(120 * 1000, &qapp, []() { // no more than 2 minuts + // Watchdog kicked in, shutting down + QCoreApplication::quit(); + }); + + FloweePay::instance()->startP2PInit(); return qapp.exec(); } -- 2.54.0 From 3381e9910c7cbb9781e80f752f768afe93b36dbd Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Mar 2025 15:46:19 +0100 Subject: [PATCH 1261/1428] Lower the notification level of blocks being mined --- android/java/org/flowee/pay/PayNotifications.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index 30d63fb..b1956d0 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -70,7 +70,7 @@ public class PayNotifications // see example here on how to make these translatable: // https://developer.android.com/develop/ui/views/notifications/build-notification#java NotificationChannel newBlocksChannel = new NotificationChannel("blocks", "New Blocks", - NotificationManager.IMPORTANCE_HIGH); + NotificationManager.IMPORTANCE_DEFAULT); m_notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); m_notificationManager.createNotificationChannel(newBlocksChannel); m_blockChannelId = newBlocksChannel.getId(); -- 2.54.0 From 1e0b79ddc46ac1495c2ae2102279d9cd5adb036b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Mar 2025 15:47:22 +0100 Subject: [PATCH 1262/1428] Cleanup --- src/FloweePay.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 3762e96..7540120 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -108,9 +108,7 @@ public: enum ApplicationProtection { NoProtection, //< No application-wide protection. Notice individual wallets can be protected. AppPassword, //< A single password for the app. No encryption. - AppUnlocked, //< App-password has been provided. - WalletsPinToPay, //< All wallets are PIN-to-Pay - WalletsPinToOpe //< All wallets are PIN-to-Open + AppUnlocked //< App-password has been provided. }; Q_ENUM(ApplicationProtection) -- 2.54.0 From 1a7131074001102c653032036f3b54ece7657437 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Mar 2025 16:54:49 +0100 Subject: [PATCH 1263/1428] Close popup on client lock. When the user has had the screen locked long enough to make the app lock, we just close the popup. The unlock screen actually appears under popups (that's the concept of a popup..) so this solves a leakage of data. --- guis/mobile/PopupOverlay.qml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 255cc95..54ad2cc 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -53,6 +53,14 @@ FocusScope { thePopup.visible = false; } + Connections { + target: Pay + function onAppProtectionChanged() { + // when the app is locked, make sure we remove all popups + root.close(); + } + } + QQC2.Popup { id: thePopup width: parent.width -- 2.54.0 From d5a632170f9f95118e353a77e59bd8f7c0d2dbb1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Mar 2025 21:17:10 +0100 Subject: [PATCH 1264/1428] Drop the foreground service requirements --- android/AndroidManifest.xml | 3 --- .../java/org/flowee/pay/PeriodicService.java | 23 ------------------- 2 files changed, 26 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 21d6a98..dc6a062 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -6,8 +6,6 @@ - - - diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index bba98e5..d4373f9 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -17,34 +17,11 @@ */ package org.flowee.pay; -import android.os.Build; -import android.content.Intent; -import android.app.Notification; -import android.content.pm.ServiceInfo; import org.qtproject.qt.android.bindings.QtService; public class PeriodicService extends QtService { - private static final int NotificationId = 23614; - - @Override - public void onCreate() { - super.onCreate(); - PayNotifications notificationManager = PayNotifications.instance(this); - Notification longRun = notificationManager.buildBackgroundNotification(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) - startForeground(NotificationId, longRun, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); - else - startForeground(NotificationId, longRun); - } - // called from C++ public void done() { - stopForeground(STOP_FOREGROUND_REMOVE); stopSelf(); } - - @Override - public void onDestroy() { - super.onDestroy(); - } } -- 2.54.0 From 27c9362542bdc5a07377279896139d7cc8f0f991 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 6 Mar 2025 23:15:02 +0100 Subject: [PATCH 1265/1428] Add a bit of powermanagement for Android. The Android system is so strict in its power management that a simple request to have Pay run in the background at night for 20 seconds it is required to completely turn off powermanagement for the app. An overkill solution, but that is how it works now. A nice history: https://medium.com/its-tinkoff/android-background-restrictions- b63e73fe508 Side-effect is that if the user granted the turning off of power management we now have to add code to do it inside the app. This commit checks when the app is hidden (screen turned off, other app became front etc) then we wait for a timeout and then terminate ourselves in order to avoid using more CPU. --- src/FloweePay_android.cpp | 43 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 0bdbd2c..aaa01a8 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -18,14 +18,18 @@ #include "FloweePay.h" #include "IndexerServices.h" #include "PriceDataProvider.h" +#include "Wallet.h" #include #include #include +#include + namespace { static QTimer *g_checkOfflineTimer = nullptr; static QTimer *g_updateBackgroundTimer = nullptr; +static boost::asio::system_timer *g_freezeTimer = nullptr; void checkOnline() { QJniEnvironment env; @@ -74,6 +78,28 @@ void followBGSettings() g_updateBackgroundTimer->start(); } +void handleFreeze() +{ + auto *pay = FloweePay::instance(); + auto wallets = pay->wallets(); + for (const auto &wallet : wallets) { + assert(wallet->segment()); + if (wallet->walletIsImporting()) { // allow running in background + // check again if it is still running in a while. + g_freezeTimer->expires_after(std::chrono::seconds(100)); + g_freezeTimer->async_wait([](const boost::system::error_code &error) { + if (!error) handleFreeze(); + }); + return; + } + } + for (auto &wallet : wallets) { + wallet->saveWallet(); + } + pay->p2pNet()->saveData(); + pay->p2pNet()->setPowerMode(P2PNet::LowPower); +} + } void FloweePay::setupPlatform() @@ -121,11 +147,25 @@ void FloweePay::setupPlatform() p2pNet()->saveData(); saveData(); m_indexerServices->save(); - g_checkOfflineTimer->stop(); } + /* + * This is to do powersaving management. + * When the app is not in the foreground for 90 seconds, we may need to + * turn off all processing in order to avoid wasting CPU. + * Notice that we use a boost timer on the IoContext because Qt actually + * freezes its threads automatically. + */ + if (g_freezeTimer == nullptr) + g_freezeTimer = new boost::asio::system_timer(ioContext()); + g_freezeTimer->expires_after(std::chrono::seconds(90)); + g_freezeTimer->async_wait([](const boost::system::error_code &error) { + if (!error) handleFreeze(); + }); } else if (state == Qt::ApplicationActive) { + if (g_freezeTimer) + g_freezeTimer->cancel(); g_checkOfflineTimer->start(); checkOnline(); /* @@ -140,6 +180,7 @@ void FloweePay::setupPlatform() * What we'll do is to start the actions again which will check up on our connections * and create new ones if we need them. */ + m_downloadManager->setPowerMode(P2PNet::NormalPower); if (!m_deviceOffline && m_loadingCompleted) p2pNet()->start(); -- 2.54.0 From a204561557d781d1cf022d1c0cf837a463e19d52 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 17:44:41 +0100 Subject: [PATCH 1266/1428] Check Android powersave mode. The background service is turned off when the phone is in powersave mode. Additionally we don't run any sync in background if the app isn't active. --- android/java/org/flowee/pay/MainActivity.java | 11 +++++++-- .../java/org/flowee/pay/PeriodicService.java | 9 +++++++ src/FloweePay_android.cpp | 24 +++++++++++-------- src/main.cpp | 4 ++++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 835745c..95f1722 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -19,8 +19,7 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtActivity; import android.Manifest; -import android.os.Bundle; -import android.os.SystemClock; +import android.os.*; import android.content.Intent; import android.net.Uri; import android.app.AlarmManager; @@ -99,6 +98,14 @@ public class MainActivity extends QtActivity } } + public boolean isPowerSaveMode() + { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + if (pm != null) + return pm.isPowerSaveMode(); + return false; + } + @Override protected void onNewIntent(Intent intent) { diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index d4373f9..b383d15 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -18,10 +18,19 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtService; +import android.os.*; public class PeriodicService extends QtService { // called from C++ public void done() { stopSelf(); } + + public boolean isPowerSaveMode() + { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + if (pm != null) + return pm.isPowerSaveMode(); + return false; + } } diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index aaa01a8..6e5471a 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -82,19 +82,23 @@ void handleFreeze() { auto *pay = FloweePay::instance(); auto wallets = pay->wallets(); - for (const auto &wallet : wallets) { - assert(wallet->segment()); - if (wallet->walletIsImporting()) { // allow running in background - // check again if it is still running in a while. - g_freezeTimer->expires_after(std::chrono::seconds(100)); - g_freezeTimer->async_wait([](const boost::system::error_code &error) { - if (!error) handleFreeze(); - }); - return; + const jboolean powerSaveOn = QJniObject(QNativeInterface::QAndroidApplication::context()) + .callMethod("isPowerSaveMode", "()Z"); + if (powerSaveOn == JNI_FALSE) { + for (const auto &wallet : wallets) { + assert(wallet->segment()); + if (wallet->walletIsImporting()) { // allow running in background + // check again if it is still running in a while. + g_freezeTimer->expires_after(std::chrono::seconds(100)); + g_freezeTimer->async_wait([](const boost::system::error_code &error) { + if (!error) handleFreeze(); + }); + return; + } } } for (auto &wallet : wallets) { - wallet->saveWallet(); + wallet->saveWallet(); } pay->p2pNet()->saveData(); pay->p2pNet()->setPowerMode(P2PNet::LowPower); diff --git a/src/main.cpp b/src/main.cpp index 9086db2..0d7f37a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,6 +84,10 @@ int main(int argc, char *argv[]) { #ifdef TARGET_OS_Android if (argc > 1 && strcmp(argv[1], "--headless") == 0) { + const jboolean powerSaveOn = QJniObject(QNativeInterface::QAndroidApplication::context()) + .callMethod("isPowerSaveMode", "()Z"); + if (powerSaveOn == JNI_TRUE) + return 0; Periodic periodic; int rc = periodic.run(); QJniObject(QNativeInterface::QAndroidApplication::context()) -- 2.54.0 From 61155c137c8ebedd995c366245a5124b6d3b6e64 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 18:31:16 +0100 Subject: [PATCH 1267/1428] Import crowdin translations --- translations/floweepay-desktop_de.ts | 2 +- translations/floweepay-mobile_de.ts | 32 +++++++++++----------- translations/module-bigtransfer_de.ts | 38 +++++++++++++-------------- translations/module-peers-view_de.ts | 2 +- translations/module-send-sweep_de.ts | 4 +-- translations/module-social-feed_de.ts | 8 +++--- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index ecea656..e139d76 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -1233,7 +1233,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Already running? - Already running? + Wird bereits ausgeführt? diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index d306fee..1f00fc0 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -279,22 +279,22 @@ Background Synchronization - Background Synchronization + Hintergrund-Synchronisierung Without background synchronization your wallets will not see new transactions until you open Flowee Pay - Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Ohne Hintergrund-Synchronisierung werden Ihre Brieftaschen erst dann neue Transaktionen sehen, wenn Sie Flowee Pay öffnen Allow Background Synchronization - Allow Background Synchronization + Hintergrund-Synchronisierung erlauben Every %1 hours - Every %1 hours + Alle %1 Stunden @@ -459,13 +459,13 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Limits for %1 argument is a name - Limits for %1 + Grenzen für %1 Disabled for %1 argument is a name - Disabled for %1 + Deaktiviert für %1 @@ -567,7 +567,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. No Internet Available - No Internet Available + Keine Internetverbindung @@ -956,12 +956,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Background Synchronization - Background Synchronization + Hintergrund-Synchronisierung Keep your wallets synchronized by enabling this - Keep your wallets synchronized by enabling this + Halten Sie Ihre Brieftaschen synchronisiert, indem Sie dies aktivieren @@ -981,27 +981,27 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Follow System - Follow System + Folge Systemeinstellung Dark - Dark + Dunkel Light - Light + Hell Notifications - Notifications + Benachrichtigungen On new block found - On new block found + Bei neu gefundenem Block @@ -1061,12 +1061,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss Enabled - Enabled + Ein Disabled - Disabled + Aus diff --git a/translations/module-bigtransfer_de.ts b/translations/module-bigtransfer_de.ts index 5ea022a..0db567e 100644 --- a/translations/module-bigtransfer_de.ts +++ b/translations/module-bigtransfer_de.ts @@ -8,17 +8,17 @@ Wallet to Wallet - Wallet to Wallet + Geldbörse zu Geldbörse Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Verschieben Sie viele Münzen zwischen den Geldbörsen. Optimieren Sie die Anonymität, indem Sie nur eine Transaktion pro Ursprungsadresse machen, während Sie sie auf verschiedene Zieladressen aufteilen können. Move funds to another wallet - Move funds to another wallet + Guthaben an andere Geldbörse übertragen @@ -26,37 +26,37 @@ Wallet to Wallet - Wallet to Wallet + Geldbörse zu Geldbörse Select two wallets to transfer funds simply, using anonimity preserving transactions. - Select two wallets to transfer funds simply, using anonimity preserving transactions. + Wähle zwei Geldbörsen, um Guthaben zu übertragen, mittels Verwendung von Transaktionen, die die Anonymität wahren. Spending Wallet - Spending Wallet + Zahlende Geldbörse Addresses - Addresses + Adressen Coins - Coins + Münzen Destination Wallet - Destination Wallet + Empfangende Geldbörse Prepare... - Prepare... + Wird vorbereitet... @@ -64,34 +64,34 @@ Verify %1 Transactions - - Verify %1 Transactions - Verify %1 Transactions + + %1 Transaktion verifiziert + %1 Transaktionen verifiziert Coins - Coins + Münzen Send Now - Send Now + Jetzt senden Create and send all %1 transactions - - Create and send all %1 transactions - Create and send all %1 transactions + + %1 Transaktion erstellen und senden + Alle %1 Transaktionen erstellen und senden Target Coins indicates a number - Target Coins + Ziel Münzen diff --git a/translations/module-peers-view_de.ts b/translations/module-peers-view_de.ts index ac597ce..cf3d88a 100644 --- a/translations/module-peers-view_de.ts +++ b/translations/module-peers-view_de.ts @@ -16,7 +16,7 @@ No Internet Available - No Internet Available + Keine Internetverbindung diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index 85d15fa..1675583 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -52,12 +52,12 @@ Claim Cash Stamp - Claim Cash Stamp + Cash Stamp einfordern A QR code with a CashStamp can be taken to transfer the money to your wallet. - A QR code with a CashStamp can be taken to transfer the money to your wallet. + Ein QR-Code mit CashStamp kann benutzt werden, um das Geld auf Ihre Geldbörse zu überweisen. diff --git a/translations/module-social-feed_de.ts b/translations/module-social-feed_de.ts index 5b5bda5..c3d20f3 100644 --- a/translations/module-social-feed_de.ts +++ b/translations/module-social-feed_de.ts @@ -6,12 +6,12 @@ Videos - Videos + Videos Run time: - Run time: + Laufzeit: @@ -19,12 +19,12 @@ Help and Learning - Help and Learning + Hilfe und Lernen Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. - Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + In dieser Video-Bibliothek zeigen Ihnen Experten wie Sie Bitcoin Cash mit Flowee Pay verwenden. -- 2.54.0 From d8a72d2a4862880ed792cc87a190d2cd0fe916b1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 20:26:45 +0100 Subject: [PATCH 1268/1428] Import crowdin translations --- translations/floweepay-desktop_pl.ts | 8 +-- translations/floweepay-mobile_pl.ts | 64 ++++++++++----------- translations/module-bigtransfer_pl.ts | 46 +++++++-------- translations/module-build-transaction_pl.ts | 10 ++-- translations/module-peers-view_pl.ts | 4 +- translations/module-send-sweep_pl.ts | 6 +- translations/module-social-feed_pl.ts | 8 +-- 7 files changed, 73 insertions(+), 73 deletions(-) diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index 90d2f80..80aeece 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -173,7 +173,7 @@ Filter - Filter + Znajdź @@ -430,7 +430,7 @@ Change will come back to the imported key. Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Nic nie znaleziono dla seeda. Czy posiada hasło? @@ -974,7 +974,7 @@ Change will come back to the imported key. Waiting for block - Waiting for block + Oczekiwanie na blok @@ -1240,7 +1240,7 @@ Change will come back to the imported key. Already running? - Already running? + Już uruchomione? diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 7ffb0b0..39f30c2 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -191,13 +191,13 @@ Really Delete? - Really Delete? + Czy na pewno usunąć? Removing wallet "%1" can not be undone. argument is the wallet name - Removing wallet "%1" can not be undone. + Usunięcie portfela "%1" nie może zostać cofnięte. @@ -279,22 +279,22 @@ Background Synchronization - Background Synchronization + Synchronizacja w tle Without background synchronization your wallets will not see new transactions until you open Flowee Pay - Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Bez synchronizacji w tle portfele nie będą widzieć nowych transakcji dopóki nie otworzysz Flowee Pay Allow Background Synchronization - Allow Background Synchronization + Zezwól na synchronizację w tle Every %1 hours - Every %1 hours + Co %1 godz. @@ -323,13 +323,13 @@ Transactions Filter - Transactions Filter + Filtr transakcji Only with a comment This is a statement about a transaction - Only with a comment + Tylko z komentarzem @@ -347,7 +347,7 @@ Scan QR - Scan QR + Zeskanuj kod QR @@ -429,7 +429,7 @@ Change will come back to the imported key. Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Nic nie znaleziono dla seeda. Czy posiada hasło? @@ -458,13 +458,13 @@ Change will come back to the imported key. Limits for %1 argument is a name - Limits for %1 + Limity dla %1 Disabled for %1 argument is a name - Disabled for %1 + Wyłączone dla %1 @@ -558,7 +558,7 @@ Change will come back to the imported key. Find More - Find More + Znajdź więcej @@ -566,7 +566,7 @@ Change will come back to the imported key. No Internet Available - No Internet Available + Brak dostępu do internetu @@ -917,7 +917,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Scan QR - Scan QR + Zeskanuj kod QR @@ -927,7 +927,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Options - Options + Opcje @@ -955,12 +955,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Background Synchronization - Background Synchronization + Synchronizacja w tle Keep your wallets synchronized by enabling this - Keep your wallets synchronized by enabling this + Włącz, by zawsze mieć zsynchronizowane portfele @@ -980,27 +980,27 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Follow System - Follow System + Użyj systemowego Dark - Dark + Ciemny Light - Light + Jasny Notifications - Notifications + Powiadomienia On new block found - On new block found + o odkryciu nowego bloku @@ -1026,12 +1026,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Getting Started - Getting Started + Wprowadzenie All Videos - All Videos + Filmiki @@ -1047,7 +1047,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Scan to send to your wallet - Scan to send to your wallet + Zeskanuj, aby wysłać do portfela @@ -1060,12 +1060,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Enabled - Enabled + Aktywny Disabled - Disabled + Wyłączony @@ -1098,7 +1098,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Waiting for block - Waiting for block + Oczekiwanie na blok @@ -1202,7 +1202,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Waiting for block - Waiting for block + Oczekiwanie na blok @@ -1283,7 +1283,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Quick Receive - Quick Receive + Szybka wpłata @@ -1291,7 +1291,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego Enter your wallet passcode - Wprowadź hasło portfela + Wprowadź hasło diff --git a/translations/module-bigtransfer_pl.ts b/translations/module-bigtransfer_pl.ts index 4f881cb..57c9d39 100644 --- a/translations/module-bigtransfer_pl.ts +++ b/translations/module-bigtransfer_pl.ts @@ -8,17 +8,17 @@ Wallet to Wallet - Wallet to Wallet + Portfel do portfela Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Przenieś wiele monet między portfelami, optymalizując pod kątem anonimowości poprzez oferowanie jednej transakcji na adres, pozwalając na podział na różne adresy. Move funds to another wallet - Move funds to another wallet + Przenieś środki do innego portfela @@ -26,37 +26,37 @@ Wallet to Wallet - Wallet to Wallet + Portfel do portfela Select two wallets to transfer funds simply, using anonimity preserving transactions. - Select two wallets to transfer funds simply, using anonimity preserving transactions. + Wybierz dwa portfele, aby przenieść środki używając transakcji zapewniających anonimowość. Spending Wallet - Spending Wallet + Portfel źródłowy Addresses - Addresses + Adresy Coins - Coins + Monety Destination Wallet - Destination Wallet + Portfel docelowy Prepare... - Prepare... + Przygotuj... @@ -64,38 +64,38 @@ Verify %1 Transactions - - Verify %1 Transactions - Verify %1 Transactions - Verify %1 Transactions - Verify %1 Transactions + + Zweryfikuj %1 transakcję + Zweryfikuj %1 transakcje + Zweryfikuj %1 transakcji + Zweryfikuj %1 transakcji Coins - Coins + Monety Send Now - Send Now + Wyślij teraz Create and send all %1 transactions - - Create and send all %1 transactions - Create and send all %1 transactions - Create and send all %1 transactions - Create and send all %1 transactions + + Utwórz i wyślij %1 transakcję + Utwórz i wyślij %1 transakcje + Utwórz i wyślij %1 transakcji + Utwórz i wyślij %1 transakcji Target Coins indicates a number - Target Coins + Docelowe Monety diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 6b714b3..8a9d7d2 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -21,7 +21,7 @@ Manually select templates - Manually select templates + Wybierz szablony ręcznie @@ -29,7 +29,7 @@ Edit Destination - Edit Destination + Edytuj Odbiorcę @@ -40,7 +40,7 @@ Bitcoin Cash Address - Bitcoin Cash Address + Adres Bitcoin Cash @@ -60,7 +60,7 @@ I am certain - I am certain + Na pewno @@ -144,7 +144,7 @@ unset indication of desination not being set - unset + nie ustawiono diff --git a/translations/module-peers-view_pl.ts b/translations/module-peers-view_pl.ts index 02cda3e..33d7442 100644 --- a/translations/module-peers-view_pl.ts +++ b/translations/module-peers-view_pl.ts @@ -16,7 +16,7 @@ No Internet Available - No Internet Available + Brak dostępu do internetu @@ -81,7 +81,7 @@ This module provides a view of network servers we connect to often called 'peers'. - Ten moduł zapewnia widok serwerów sieciowych, z którymi łączymy się nazywanych 'peerami'. + Ten moduł zapewnia widok serwerów sieciowych, z którymi się łączymy, nazywanych popularnie 'peerami'. diff --git a/translations/module-send-sweep_pl.ts b/translations/module-send-sweep_pl.ts index da9e3d6..01228b9 100644 --- a/translations/module-send-sweep_pl.ts +++ b/translations/module-send-sweep_pl.ts @@ -56,12 +56,12 @@ Claim Cash Stamp - Claim Cash Stamp + Odbierz Cash Stamp A QR code with a CashStamp can be taken to transfer the money to your wallet. - A QR code with a CashStamp can be taken to transfer the money to your wallet. + Wykorzystaj kod QR z CashStamp, aby otrzymać środki. @@ -75,7 +75,7 @@ Scan QR (WIF) to find funds Please note that WIF and QR are names - Scan QR (WIF) to find funds + Zeskanuj QR (WIF), aby znaleźć środki diff --git a/translations/module-social-feed_pl.ts b/translations/module-social-feed_pl.ts index 79f5590..084f149 100644 --- a/translations/module-social-feed_pl.ts +++ b/translations/module-social-feed_pl.ts @@ -6,12 +6,12 @@ Videos - Videos + Filmy Run time: - Run time: + Długość: @@ -19,12 +19,12 @@ Help and Learning - Help and Learning + Pomoc i nauka Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. - Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Chcesz zobaczyć jak korzystać z Bitcoin Cash z pomocą Flowee Pay? Znajdź to, czego potrzebujesz, w naszych filmikach. -- 2.54.0 From 8f388f93f1b8af957956a6407471518620701b32 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 21:19:41 +0100 Subject: [PATCH 1269/1428] Fix bug reported by PL translator; wrong month This uses the form of the month for 'writing', which is different in some languages than the form used in an exact date. --- src/WalletHistoryModel.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 51f7caa..7a30327 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -258,12 +258,12 @@ QString WalletHistoryModel::groupingPeriod(int groupId) const return tr("Earlier this month"); case WalletEnums::Month: default: { - static const QString wide("MMMM yyyy"); - static const QString lean("MMMM"); const uint32_t timestamp = m_groups.at(groupId).endTime; const QDate date = QDateTime::fromSecsSinceEpoch(timestamp).date(); - return QLocale::system().toString(date, - date.year() == m_today.year() ? lean : wide); + QString result = QLocale::system().standaloneMonthName(date.month()); + if (date.year() != m_today.year()) + result += QString(" %1").arg(date.year()); + return result; } } } -- 2.54.0 From 0bb6bb0bf52173210da2dc55f23c0d2b0d587ebc Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 22:29:10 +0100 Subject: [PATCH 1270/1428] Fix includes Avoid the includes of whole modules, which is expensive and unpredictable. --- testing/priceHistory/TestPriceHistory.cpp | 5 ++++- testing/value/TestValue.cpp | 2 ++ testing/value/TestValue.h | 2 +- testing/wallet/TestWallet.cpp | 4 ++-- testing/walletHistoryModel/TestWalletHistoryModel.cpp | 5 +++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/testing/priceHistory/TestPriceHistory.cpp b/testing/priceHistory/TestPriceHistory.cpp index 17f4b19..ca81745 100644 --- a/testing/priceHistory/TestPriceHistory.cpp +++ b/testing/priceHistory/TestPriceHistory.cpp @@ -19,7 +19,10 @@ #include #include -#include +#include +#include +#include +#include void TestPriceHistory::cleanup() diff --git a/testing/value/TestValue.cpp b/testing/value/TestValue.cpp index e7fd7ce..248c02b 100644 --- a/testing/value/TestValue.cpp +++ b/testing/value/TestValue.cpp @@ -18,6 +18,8 @@ #include "TestValue.h" #include #include +#include +#include class MockBitcoinValue : public BitcoinValue { diff --git a/testing/value/TestValue.h b/testing/value/TestValue.h index 3cdf8cc..a9f516f 100644 --- a/testing/value/TestValue.h +++ b/testing/value/TestValue.h @@ -18,7 +18,7 @@ #ifndef TEST_VALUE_H #define TEST_VALUE_H -#include +#include class TestValue : public QObject { diff --git a/testing/wallet/TestWallet.cpp b/testing/wallet/TestWallet.cpp index 87d7124..bdb77cd 100644 --- a/testing/wallet/TestWallet.cpp +++ b/testing/wallet/TestWallet.cpp @@ -20,10 +20,10 @@ #include #include -#include #include #include - +#include +#include class MockWallet : public Wallet { diff --git a/testing/walletHistoryModel/TestWalletHistoryModel.cpp b/testing/walletHistoryModel/TestWalletHistoryModel.cpp index 5390008..92e55f6 100644 --- a/testing/walletHistoryModel/TestWalletHistoryModel.cpp +++ b/testing/walletHistoryModel/TestWalletHistoryModel.cpp @@ -18,8 +18,9 @@ #include "TestWalletHistoryModel.h" #include #include - -#include +#include +#include +#include class MockWalletHistoryModel : public WalletHistoryModel { -- 2.54.0 From a79e8ff1df15846263ad370d1face89208095c0a Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 23:25:52 +0100 Subject: [PATCH 1271/1428] Test and fix fiat rendering in some cases In some locales the group separator is a unicode character that does not fit in an 8 byte string, causing misrenderings. We now assume that things fit in 16 bit characters which should fit all living languages. --- src/PriceDataProvider.cpp | 14 +++++---- src/PriceDataProvider.h | 7 ++++- testing/CMakeLists.txt | 2 ++ testing/fiat/CMakeLists.txt | 26 ++++++++++++++++ testing/fiat/TestFiatUtils.cpp | 57 ++++++++++++++++++++++++++++++++++ testing/fiat/TestFiatUtils.h | 30 ++++++++++++++++++ 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 testing/fiat/CMakeLists.txt create mode 100644 testing/fiat/TestFiatUtils.cpp create mode 100644 testing/fiat/TestFiatUtils.h diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 45fa283..34ba1b9 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -38,7 +38,10 @@ static const char * YadioURLJSONRoot = "USD"; static constexpr int ReloadTimeout = 7 * 60 * 1000; -PriceDataProvider::PriceDataProvider(const QString &countryCode, QObject *parent) : QObject(parent) +PriceDataProvider::PriceDataProvider(const QString &countryCode, QObject *parent) + : QObject(parent), + m_thousandSeparator(QLocale::system().groupSeparator().at(0)), + m_decimalPoint(QLocale::system().decimalPoint().at(0)) { if (countryCode.isEmpty()) setCurrency(QLocale::system()); @@ -151,29 +154,28 @@ QString PriceDataProvider::formattedPrice(int64_t fiatValue) const const int length = centsPrice.length() - 2 - offset; QString actualPrice = centsPrice.mid(offset, length); - // group size is 3 if (length > 3 && length <= 15) { // lets insert some thousands separators. - char buf[21]; + QChar buf[21]; memset(buf, 0, sizeof(buf)); int add = 0; for (int i = 0; i < actualPrice.length(); ++i) { // insert thousands separators. if (i > 0 && actualPrice.length() % 3 == i % 3) - buf[i + add++] = QLocale::system().groupSeparator().at(0).toLatin1(); + buf[i + add++] = m_thousandSeparator; assert(actualPrice.at(i).unicode() < 127); // only digits buf[i + add] = actualPrice.at(i).unicode(); } - actualPrice = QString::fromLatin1(buf); + actualPrice = QString(buf, -1); } if (m_displayCents) { return m_currencySymbolPrefix % (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) % actualPrice - % QLocale::system().decimalPoint() + % m_decimalPoint % centsPrice.right(2) % m_currencySymbolPost; } diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 6fa6bc1..04a5d7c 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -36,7 +36,7 @@ class PriceDataProvider : public QObject Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost NOTIFY currencySymbolChanged) public: - explicit PriceDataProvider(const QString &countryCode = QString(), QObject *parent = nullptr); + explicit PriceDataProvider(const QString &countryCode, QObject *parent = nullptr); void start(); @@ -121,6 +121,11 @@ private slots: void finishedDownload(); void finishedYadioDownload(); +protected: + explicit PriceDataProvider() = default; + QChar m_thousandSeparator; + QChar m_decimalPoint; + private: struct Price { int32_t price = 0; diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 4cddcc8..97cc99f 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -22,11 +22,13 @@ if (${Qt6Test_FOUND}) add_subdirectory(walletHistoryModel) add_subdirectory(value) add_subdirectory(priceHistory) + add_subdirectory(fiat) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS test_wallet test_wallethistorymodel test_value test_price_history + test_fiat ) endif () diff --git a/testing/fiat/CMakeLists.txt b/testing/fiat/CMakeLists.txt new file mode 100644 index 0000000..ef33301 --- /dev/null +++ b/testing/fiat/CMakeLists.txt @@ -0,0 +1,26 @@ +# This file is part of the Flowee project +# Copyright (C) 2025 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(CMAKE_AUTOMOC ON) + +include_directories(${Qt6Test_INCLUDE_DIRS}) + +add_executable(test_fiat + TestFiatUtils.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp +) +target_link_libraries(test_fiat pay_lib Qt6::Test) +add_test(NAME Pay_test_fiat COMMAND test_fiat) diff --git a/testing/fiat/TestFiatUtils.cpp b/testing/fiat/TestFiatUtils.cpp new file mode 100644 index 0000000..1b01af7 --- /dev/null +++ b/testing/fiat/TestFiatUtils.cpp @@ -0,0 +1,57 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TestFiatUtils.h" +#include + +//#include +//#include +//#include +#include +#include + +class MockFiat : public PriceDataProvider +{ +public: + MockFiat() : PriceDataProvider() { + m_thousandSeparator = ','; + m_decimalPoint = '.'; + } + void setGroupSeparator(QChar k) { + m_thousandSeparator = k; + } + void setDecimalPoint(QChar k) { + m_decimalPoint = k; + } +}; + +void TestFiatUtils::testFormattedPrice() +{ + MockFiat data; + data.setGroupSeparator('{'); + data.setDecimalPoint('}'); + QCOMPARE(data.formattedPrice(1), "0}01"); + QCOMPARE(data.formattedPrice(8172412), "81{724}12"); + QCOMPARE(data.formattedPrice(100), "1}00"); + QCOMPARE(data.formattedPrice(109), "1}09"); + data.setGroupSeparator(QChar(8217)); // what de_ch uses. + QCOMPARE(data.formattedPrice(999109), "9\u2019991}09"); + data.setDecimalPoint(QChar(8364)); // what de_ch uses. + QCOMPARE(data.formattedPrice(999109), "9\u2019991\u20ac09"); +} + +QTEST_MAIN(TestFiatUtils) diff --git a/testing/fiat/TestFiatUtils.h b/testing/fiat/TestFiatUtils.h new file mode 100644 index 0000000..3fca7b5 --- /dev/null +++ b/testing/fiat/TestFiatUtils.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TEST_FIAT_UTILS_H +#define TEST_FIAT_UTILS_H + +#include + +class TestFiatUtils : public QObject +{ + Q_OBJECT +private slots: + void testFormattedPrice(); +}; + +#endif -- 2.54.0 From c21481c96f7f4d47752ea8572ac02ef97f788892 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Mar 2025 11:32:33 +0100 Subject: [PATCH 1272/1428] Give bottom more space --- guis/mobile/PopupOverlay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 54ad2cc..8840c52 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -95,7 +95,7 @@ FocusScope { anchors.fill: loader // the popup imposes a border on us, we take it back anchors.topMargin: -10 - anchors.bottomMargin: -2 + anchors.bottomMargin: -7 anchors.leftMargin: -14 anchors.rightMargin: -14 } -- 2.54.0 From 8b9cd5282eef568e7f797440b7305724f8a2bb1b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Mar 2025 16:59:38 +0100 Subject: [PATCH 1273/1428] Update the button text appropriately When the selected account or currency changes, make the text update to reflect the actual action of pressing this button. --- guis/mobile/InstaPayConfigButton.qml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/guis/mobile/InstaPayConfigButton.qml b/guis/mobile/InstaPayConfigButton.qml index 77aa12a..6b24b92 100644 --- a/guis/mobile/InstaPayConfigButton.qml +++ b/guis/mobile/InstaPayConfigButton.qml @@ -21,36 +21,36 @@ TextButton { id: root property QtObject account: portfolio.current - text: root.account.allowsInstaPay ? qsTr("Enable Instant Pay") : qsTr("Configure Instant Pay") property int limit: 0 currentValue: { - root.account.allowInstaPay if (!root.account.allowInstaPay) return false; return Fiat.formattedPrice(limit); } - function updateLimit() { + function doUpdates() { limit = root.account.fiatInstaPayLimit(Fiat.currencyName); + text = root.account.allowInstaPay ? qsTr("Configure Instant Pay") : qsTr("Enable Instant Pay") } - Component.onCompleted: updateLimit(); + onAccountChanged: doUpdates(); + Component.onCompleted: doUpdates(); Connections { target: root.account function onInstaPayLimitChanged() { - updateLimit(); + doUpdates(); } } Connections { target: Fiat function onCurrencyNameChanged() { - updateLimit(); + doUpdates(); } } subtext: { if (portfolio.singleAccountSetup) return ""; - if (root.account.allowsInstaPay) + if (root.account.allowInstaPay) return qsTr("Limits for %1", "argument is a name").arg(root.account.name); return qsTr("Disabled for %1", "argument is a name").arg(root.account.name); } -- 2.54.0 From 44063cde9de62f52d378debe905662c76561c3f4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 7 Mar 2025 15:39:48 +0100 Subject: [PATCH 1274/1428] Stop linking to Boost::chrono --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68a2678..0cc28c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ if (CMAKE_VERSION VERSION_GREATER "3.29.9") # this policy was introduced in cmake 3.30 cmake_policy(SET CMP0167 NEW) endif() -find_package(Boost 1.78.0 REQUIRED filesystem chrono thread) +find_package(Boost 1.78.0 REQUIRED filesystem thread) include_directories(${Boost_INCLUDE_DIRS}) function(download_file url path) -- 2.54.0 From b5936d3ca0e4ca826fd50bf5bdb02113e7f57926 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 8 Mar 2025 23:31:24 +0100 Subject: [PATCH 1275/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index dc6a062..e381092 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="38" android:versionName="2025.03.1"> diff --git a/src/main.cpp b/src/main.cpp index 0d7f37a..353d7de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,7 +98,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.03.0"); + qapp.setApplicationVersion("2025.03.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 3f6cab3f81262706b4b8f4c3b84eb8a9b2ab0a28 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Mar 2025 17:28:17 +0100 Subject: [PATCH 1276/1428] Disable powermanagement feature for now. The power management turns off the network layer 90 seconds after the application stops being in the foreground. At least, that is what it is programmed to do. Unfortunately the signals we listen to are not reliable across devices and many users are reporting that this is triggered 90 seconds after start. For now, turn off the saving feature in order to make sure that the app operates normally for normal people. --- src/FloweePay_android.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 6e5471a..6922343 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -160,16 +160,16 @@ void FloweePay::setupPlatform() * Notice that we use a boost timer on the IoContext because Qt actually * freezes its threads automatically. */ - if (g_freezeTimer == nullptr) - g_freezeTimer = new boost::asio::system_timer(ioContext()); - g_freezeTimer->expires_after(std::chrono::seconds(90)); - g_freezeTimer->async_wait([](const boost::system::error_code &error) { - if (!error) handleFreeze(); - }); +// if (g_freezeTimer == nullptr) +// g_freezeTimer = new boost::asio::system_timer(ioContext()); +// g_freezeTimer->expires_after(std::chrono::seconds(90)); +// g_freezeTimer->async_wait([](const boost::system::error_code &error) { +// if (!error) handleFreeze(); +// }); } else if (state == Qt::ApplicationActive) { - if (g_freezeTimer) - g_freezeTimer->cancel(); +// if (g_freezeTimer) +// g_freezeTimer->cancel(); g_checkOfflineTimer->start(); checkOnline(); /* -- 2.54.0 From fa7418d39d32971bcd46b0e112406cf5d9a3da0d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Mar 2025 16:56:47 +0100 Subject: [PATCH 1277/1428] Try adding a rounded corner --- guis/mobile.qrc | 1 + guis/mobile/MainViewBase.qml | 12 +++++++- guis/mobile/Page.qml | 10 +++++++ guis/mobile/RoundedCorner.qml | 53 +++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 guis/mobile/RoundedCorner.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index ffe3588..c16696f 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -99,5 +99,6 @@ mobile/UnlockWidget.qml mobile/VisualSeparator.qml mobile/NewIndicator.qml + mobile/RoundedCorner.qml diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index a8f3f69..81d24f5 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -213,12 +213,22 @@ QQC2.Control { onSelectedAccountChanged: portfolio.current = selectedAccount } } - Item { id: stack width: root.width anchors.top: header.bottom; anchors.bottom: tabbar.top } + RoundedCorner { + anchors.top: header.bottom + color: header.color + } + RoundedCorner { + anchors.top: header.bottom + anchors.right: parent.right + color: header.color + edge: Qt.RightEdge + } + Rectangle { anchors.fill: tabbar diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 1fed4ed..2abad22 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -136,6 +136,16 @@ QQC2.Control { } } } + RoundedCorner { + anchors.top: header.bottom + color: header.color + } + RoundedCorner { + anchors.top: header.bottom + anchors.right: parent.right + color: header.color + edge: Qt.RightEdge + } contentItem: FocusScope { id: focusScope diff --git a/guis/mobile/RoundedCorner.qml b/guis/mobile/RoundedCorner.qml new file mode 100644 index 0000000..c5b1989 --- /dev/null +++ b/guis/mobile/RoundedCorner.qml @@ -0,0 +1,53 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2022-2024 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Shapes + +Item { + id: root + property int radius: 18 + // one of either Qt.LeftEdge or Qt.RightEdge + property int edge: Qt.LeftEdge + property color color: "red" + width: root.radius + height: root.radius + + Shape { + width: parent.width + height: parent.height + transform: [ + Scale { + xScale: (root.edge === Qt.RightEdge) ? -1 : 1 + yScale: 1; + origin.x: width / 2 + } + ] + + ShapePath { + fillColor: root.color + strokeColor: "#00000000" + PathLine { y: root.radius } + PathArc { + x: root.radius + radiusX: root.radius * 1.4142 + radiusY: radiusX + } + PathLine { } + } + } +} -- 2.54.0 From 0ebc04a054e6d201e215364e20f50ea5f9dd83be Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Mar 2025 21:42:24 +0100 Subject: [PATCH 1278/1428] Add titlebar color setting on Android --- android/java/org/flowee/pay/MainActivity.java | 6 ++++++ src/FloweePay_android.cpp | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 95f1722..45a33b0 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -40,6 +40,12 @@ public class MainActivity extends QtActivity filterAndForward(getIntent()); } + // notice that the color format is Alpha + RGB to make 4 bytes. + public void setStatusBarColor(int color) + { + getWindow().setStatusBarColor(color); + } + public void enableRegularUpdates(int hours) { if (updatesInterval == hours) diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 6922343..59763fb 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -104,6 +104,14 @@ void handleFreeze() pay->p2pNet()->setPowerMode(P2PNet::LowPower); } +void updateTitleBarColor() +{ + const bool dark = FloweePay::instance()->darkSkin(); + const uint32_t color = dark ? 0xff222222 : 0xff0b1088; + QJniObject(QNativeInterface::QAndroidApplication::context()) + .callObjectMethod("setStatusBarColor", "(I)V", color); +} + } void FloweePay::setupPlatform() @@ -201,6 +209,9 @@ void FloweePay::setupPlatform() connect (this, &FloweePay::backgroundUpdatesChanged, &backgroundUpdaterToggled); if (!FloweePay::instance()->deviceOffline()) backgroundUpdaterToggled(); // make sure that we tell the android side. + + connect (this, &FloweePay::darkSkinChanged, this, &updateTitleBarColor); + updateTitleBarColor(); } bool fp_platformSkinDark() -- 2.54.0 From 890fb9e54eec9442e457bcf28649f1ebd8c35281 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 9 Mar 2025 22:03:32 +0100 Subject: [PATCH 1279/1428] Move menu button and have a subtle gradient --- guis/mobile.qrc | 1 - guis/mobile/MainViewBase.qml | 31 +++++++++----------- guis/mobile/Page.qml | 30 ++++++-------------- guis/mobile/RoundedCorner.qml | 53 ----------------------------------- 4 files changed, 23 insertions(+), 92 deletions(-) delete mode 100644 guis/mobile/RoundedCorner.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index c16696f..ffe3588 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -99,6 +99,5 @@ mobile/UnlockWidget.qml mobile/VisualSeparator.qml mobile/NewIndicator.qml - mobile/RoundedCorner.qml diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 81d24f5..4aa4127 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -86,10 +86,10 @@ QQC2.Control { Flowee.HamburgerMenu { id: menuButton - anchors.verticalCenter: parent.verticalCenter + y: 14 color: "white" wide: true - x: 8 + x: 20 } MouseArea { anchors.fill: menuButton @@ -103,8 +103,8 @@ QQC2.Control { id: logo // Here we just want the text part. So clip that out. clip: true - y: 17 - x: 20 + y: 15 + x: 36 width: 122 height: 21 baselineOffset: 16 @@ -144,9 +144,9 @@ QQC2.Control { source: "qrc:/filter-light.svg" visible: root.showFilterIcon anchors.right: parent.right - anchors.rightMargin: 6 + anchors.rightMargin: 16 anchors.bottom: parent.bottom - anchors.bottomMargin: 9 + anchors.bottomMargin: 14 smooth: true width: 25 height: 25 @@ -212,23 +212,20 @@ QQC2.Control { onSelectedAccountChanged: portfolio.current = selectedAccount } + gradient: Gradient { + GradientStop { position: 0.9; color: header.color } + GradientStop { position: 1; color: { + let c = header.color; + return Qt.rgba(c.r, c.g, c.b, 0); + } + } + } } Item { id: stack width: root.width anchors.top: header.bottom; anchors.bottom: tabbar.top } - RoundedCorner { - anchors.top: header.bottom - color: header.color - } - RoundedCorner { - anchors.top: header.bottom - anchors.right: parent.right - color: header.color - edge: Qt.RightEdge - } - Rectangle { anchors.fill: tabbar diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 2abad22..5e74101 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -81,6 +81,14 @@ QQC2.Control { color: Pay.useDarkSkin ? palette.window : mainWindow.floweeBlue visible: !root.hideHeader + gradient: Gradient { + GradientStop { position: 0.9; color: header.color } + GradientStop { position: 1; color: { + let c = header.color; + return Qt.rgba(c.r, c.g, c.b, 0); + } + } + } Image { id: backButton x: 13 @@ -98,17 +106,7 @@ QQC2.Control { QQC2.Label { id: headerLabel color: "white" - anchors.left: backButton.right - anchors.right: { - // we assume at most one of these two is in use. - if (headerButton.visible) - return headerButton.left; - if (hamburgerMenu.visible) - return hamburgerMenu.left; - return parent.right; - } - horizontalAlignment: Text.AlignHCenter - anchors.verticalCenter: parent.verticalCenter + anchors.centerIn: parent } Flowee.Button { @@ -136,16 +134,6 @@ QQC2.Control { } } } - RoundedCorner { - anchors.top: header.bottom - color: header.color - } - RoundedCorner { - anchors.top: header.bottom - anchors.right: parent.right - color: header.color - edge: Qt.RightEdge - } contentItem: FocusScope { id: focusScope diff --git a/guis/mobile/RoundedCorner.qml b/guis/mobile/RoundedCorner.qml deleted file mode 100644 index c5b1989..0000000 --- a/guis/mobile/RoundedCorner.qml +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import QtQuick -import QtQuick.Shapes - -Item { - id: root - property int radius: 18 - // one of either Qt.LeftEdge or Qt.RightEdge - property int edge: Qt.LeftEdge - property color color: "red" - width: root.radius - height: root.radius - - Shape { - width: parent.width - height: parent.height - transform: [ - Scale { - xScale: (root.edge === Qt.RightEdge) ? -1 : 1 - yScale: 1; - origin.x: width / 2 - } - ] - - ShapePath { - fillColor: root.color - strokeColor: "#00000000" - PathLine { y: root.radius } - PathArc { - x: root.radius - radiusX: root.radius * 1.4142 - radiusY: radiusX - } - PathLine { } - } - } -} -- 2.54.0 From 99adaa0a923f95fc7c57b675cc1f7de147273d79 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Mar 2025 23:41:47 +0100 Subject: [PATCH 1280/1428] Lets start a new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index e381092..07f300b 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="39" android:versionName="2025.03.2"> diff --git a/src/main.cpp b/src/main.cpp index 353d7de..43be074 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,7 +98,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.03.1"); + qapp.setApplicationVersion("2025.03.2"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 66baeab106ca2c658bb60896ed188db714d60b49 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 11 Mar 2025 23:15:47 +0100 Subject: [PATCH 1281/1428] Add check to avoid losing data. --- src/FloweePay.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 5093d6f..517b6c3 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -454,6 +454,9 @@ void FloweePay::loadingCompleted() void FloweePay::saveData() { + if (m_wallets.isEmpty() && m_createStartWallet) { + throw std::runtime_error("Save called before loading finished"); + } auto data = std::make_shared(m_wallets.size() * 100 + 50); Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { -- 2.54.0 From e9fc0c15515bd67ecea8ed874e2bcb52c2a24102 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 10 Mar 2025 23:37:22 +0100 Subject: [PATCH 1282/1428] Re-enable the powermanagement feature for Android This avoids using the Qt intermediate and just directly uses the Android side, including some extra checks like "screen off". Rework the various methods in a class. This makes the various android support features be less hacky and instead are based on a single class with Qt signals and slots. We also remove the ping feature for checking online state, since Android seems to throw a random exception (calling virtual method on null pointer java.lang.String.size) somewhere in the OS libs when you call it the second time. This also implements the AIRPLANE_MODE_CHANGED listener to instantly turn off internet the moment the user enables flight mode. --- android/java/org/flowee/pay/MainActivity.java | 56 ++- android/java/org/flowee/pay/Networks.java | 7 +- src/FloweePay.h | 5 + src/FloweePay_android.cpp | 396 ++++++++++-------- src/FloweePay_android_p.h | 60 +++ src/main_utils_android.cpp | 12 +- 6 files changed, 358 insertions(+), 178 deletions(-) create mode 100644 src/FloweePay_android_p.h diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 45a33b0..6bb0dda 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -20,19 +20,38 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtActivity; import android.Manifest; import android.os.*; -import android.content.Intent; import android.net.Uri; import android.app.AlarmManager; import android.app.PendingIntent; import android.provider.Settings; +import android.content.*; import android.content.pm.PackageManager; +import java.io.*; + public class MainActivity extends QtActivity { public native void setPaymentIntentOnCPP(String data); public native void startScan(); public native void notificationPermissionDenied(); + public native void seenAction(String string); + + private boolean nativesBound = false; + + // the C++ should call this as soon as it finishes binding the above natives + public void onQtAppLoaded() + { + // the C++ side calls this after the above native method have been + // tied up to the C++ ones. + nativesBound = true; + } + + private void seenAction_priv(String action) + { + if (nativesBound) + seenAction(action); + } // the C++ should call this one when it finished startup. public void onQtAppStarted() @@ -40,6 +59,39 @@ public class MainActivity extends QtActivity filterAndForward(getIntent()); } + @Override + protected void onResume() + { + super.onResume(); + seenAction_priv("resume"); + } + + @Override + protected void onStop() + { + super.onStop(); + seenAction_priv("stop"); + } + + private static boolean first = true; + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + // filter.addAction(Intent.ACTION_POWER_CONNECTED); + // filter.addAction(Intent.ACTION_POWER_DISCONNECTED); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + // filter.addAction(Intent.ACTION_BOOT_COMPLETED); + // filter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED); + Intent firstIntent = registerReceiver(new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + seenAction_priv(intent.getAction()); + } + }, filter, 0); + } + // notice that the color format is Alpha + RGB to make 4 bytes. public void setStatusBarColor(int color) { @@ -94,7 +146,7 @@ public class MainActivity extends QtActivity } @Override - // call-back from requestPermission + // call-back from requestPermissions public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults) { diff --git a/android/java/org/flowee/pay/Networks.java b/android/java/org/flowee/pay/Networks.java index 7dff695..1e3878d 100644 --- a/android/java/org/flowee/pay/Networks.java +++ b/android/java/org/flowee/pay/Networks.java @@ -93,11 +93,6 @@ public class Networks if (!activeNetwork.isConnected()) return false; } - try { // Quick ping to a reliable server - InetAddress address = InetAddress.getByName("8.8.8.8"); - return address.isReachable(2000); // Timeout of 2 seconds - } catch (Exception e) { - return false; - } + return true; } } diff --git a/src/FloweePay.h b/src/FloweePay.h index 7540120..514069c 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -445,6 +445,11 @@ public: int backgroundUpdateInterval() const; void setBackgroundUpdateInterval(int hours); +#ifdef TARGET_OS_Android + // save what we can, the app is about to hide. + void softSave(); +#endif + signals: void loadComplete(); /// \internal diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 59763fb..11a4e9e 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "FloweePay.h" +#include "FloweePay_android_p.h" #include "IndexerServices.h" #include "PriceDataProvider.h" #include "Wallet.h" @@ -24,85 +25,8 @@ #include #include -#include - namespace { -static QTimer *g_checkOfflineTimer = nullptr; -static QTimer *g_updateBackgroundTimer = nullptr; -static boost::asio::system_timer *g_freezeTimer = nullptr; - -void checkOnline() { - QJniEnvironment env; - jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); - const jboolean rc = QJniObject::callStaticMethod(floweeNetworks, - "isInternetAvailable", "(Landroid/content/Context;)Z", - QJniObject(QNativeInterface::QAndroidApplication::context())); - auto *fp = FloweePay::instance(); - const bool wasOffline = fp->deviceOffline(); - const bool isOffline = JNI_FALSE == rc; - fp->setDeviceOffline(isOffline); - if (wasOffline && !isOffline) { // came online - fp->startNet(); - fp->prices()->start(); - } - if (!isOffline && g_checkOfflineTimer) // we're online. Stop the timer - g_checkOfflineTimer->stop(); -} - -void backgroundUpdaterToggled() -{ - auto *pay = FloweePay::instance(); - auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); - main.callObjectMethod("enableRegularUpdates", "(I)V", - pay->backgroundUpdates() ? pay->backgroundUpdateInterval() : 0); -} - -void followBGSettings() -{ - if (g_updateBackgroundTimer == nullptr) { - g_updateBackgroundTimer = new QTimer(FloweePay::instance()); - g_updateBackgroundTimer->setTimerType(Qt::CoarseTimer); - g_updateBackgroundTimer->setInterval(2000); - QObject::connect (g_updateBackgroundTimer, &QTimer::timeout, FloweePay::instance(), [=]() { - auto *pay = FloweePay::instance(); - int h = 0; // zero is effectively a cancel - if (pay->backgroundUpdates()) - h = pay->backgroundUpdateInterval(); - auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); - main.callObjectMethod("enableRegularUpdates", "(I)V", h); - g_updateBackgroundTimer->deleteLater(); - g_updateBackgroundTimer = nullptr; - }); - } - g_updateBackgroundTimer->stop(); - g_updateBackgroundTimer->start(); -} - -void handleFreeze() -{ - auto *pay = FloweePay::instance(); - auto wallets = pay->wallets(); - const jboolean powerSaveOn = QJniObject(QNativeInterface::QAndroidApplication::context()) - .callMethod("isPowerSaveMode", "()Z"); - if (powerSaveOn == JNI_FALSE) { - for (const auto &wallet : wallets) { - assert(wallet->segment()); - if (wallet->walletIsImporting()) { // allow running in background - // check again if it is still running in a while. - g_freezeTimer->expires_after(std::chrono::seconds(100)); - g_freezeTimer->async_wait([](const boost::system::error_code &error) { - if (!error) handleFreeze(); - }); - return; - } - } - } - for (auto &wallet : wallets) { - wallet->saveWallet(); - } - pay->p2pNet()->saveData(); - pay->p2pNet()->setPowerMode(P2PNet::LowPower); -} +static FloweePayPrivate *g_fpPriv = nullptr; void updateTitleBarColor() { @@ -114,106 +38,246 @@ void updateTitleBarColor() } -void FloweePay::setupPlatform() + +// callback as registered in main_utils_android.cpp +void JNI_processAction(JNIEnv *env, jobject thiz, jstring data) { - assert(m_downloadManager.get()); - // ask the Android system which interfaces there are; + Q_UNUSED(env); + Q_UNUSED(thiz); + QJniObject obj(data); + if (g_fpPriv == nullptr) // Waiting for loading to finish. + return; + + const QString action = obj.toString(); + if (action == "android.intent.action.SCREEN_OFF" || action == "stop") + g_fpPriv->onAppHidden(); + else if (action == "resume") + g_fpPriv->onAppShown(); + else if (action == "android.intent.action.AIRPLANE_MODE") + g_fpPriv->airplaneModeToggled(); +/* + else if (action == "android.intent.action.ACTION_POWER_DISCONNECTED") + // -> if hiding, stop networking. + else if (action == "android.intent.action.ACTION_POWER_CONNECTED") + // -> add a mode of hiding but online. + // -> check if we are offline now, if so then we should shut down the net +*/ +} + +FloweePayPrivate::FloweePayPrivate(FloweePay *parent) + : QObject(parent), + m_parent(parent), + m_freezeTimer(m_parent->ioContext()), + m_checkOfflineTimer(new QTimer(this)), + m_updateBackgroundTimer(nullptr) +{ + m_checkOfflineTimer->setTimerType(Qt::VeryCoarseTimer); + m_checkOfflineTimer->setInterval(7000); + + connect (this, &FloweePayPrivate::airplaneModeCheckNeeded, + this, &FloweePayPrivate::checkAirplaneMode, Qt::QueuedConnection); + connect (m_checkOfflineTimer, &QTimer::timeout, + this, &FloweePayPrivate::checkOnline); + connect (m_parent, &FloweePay::backgroundUpdateIntervalChanged, + this, &FloweePayPrivate::followBGSettings); + connect (m_parent, &FloweePay::backgroundUpdatesChanged, + this, &FloweePayPrivate::backgroundUpdaterToggled); +} + +void FloweePayPrivate::airplaneModeToggled() +{ + emit airplaneModeCheckNeeded(); +} + +void FloweePayPrivate::checkAirplaneMode() +{ + // wait for Android to have updated all the network interfaces + // before asking them. + QTimer::singleShot(400, this, &FloweePayPrivate::checkOnline); +} + +void FloweePayPrivate::checkOnline() +{ + // ask the Android system which interfaces there are and + // check if we're online at all. QJniEnvironment env; jclass floweeNetworks = env.findClass("org/flowee/pay/Networks"); - quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); - if (flags != 0) { - logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; - auto &addressDb = m_downloadManager->connectionManager().peerAddressDb(); - addressDb.setSupportIPv4Net((flags & 1) == 1); - addressDb.setSupportIPv6Net((flags & 2) == 2); - } - checkOnline(); + const jboolean rc = QJniObject::callStaticMethod(floweeNetworks, + "isInternetAvailable", "(Landroid/content/Context;)Z", + QJniObject(QNativeInterface::QAndroidApplication::context())); + const bool wasOffline = m_parent->deviceOffline() || m_parent->p2pNet()->powerMode() != P2PNet::NormalPower; + const bool isOffline = JNI_FALSE == rc; - auto guiApp = qobject_cast(QCoreApplication::instance()); - if (guiApp == nullptr) { + m_parent->setDeviceOffline(isOffline); + if (!isOffline) { + quint32 flags = QJniObject::callStaticMethod(floweeNetworks, "networkSupport", "()I"); + if (flags != 0) { + logInfo() << "org.flowee.pay.Networks.networkSupport() returns flags:" << flags; + auto &addressDb = m_parent->p2pNet()->connectionManager().peerAddressDb(); + addressDb.setSupportIPv4Net((flags & 1) == 1); + addressDb.setSupportIPv6Net((flags & 2) == 2); + } + } + m_parent->p2pNet()->setPowerMode(isOffline ? P2PNet::LowPower : P2PNet::NormalPower); + if (wasOffline && !isOffline) { // came online + m_parent->startNet(); + m_parent->prices()->start(); + } + m_checkOfflineTimer->stop(); + if (isOffline) + m_checkOfflineTimer->start(); +} + +void FloweePayPrivate::onAppHidden() +{ + if (m_appState == AppHidden) + return; + m_appState = AppHidden; + m_sleepStart = QDateTime::currentDateTimeUtc(); + // App no longer active. Start saving data. Android might just kill us any moment. + m_parent->softSave(); + + /* + * This is to do powersaving management. + * To allow background running, the user may have granted us the right to + * run without limitations. Which is massively black and white, and stupid. + * Anyway, to avoid the user hating us for wasting their CPU, we do our own + * powermanagement. + * When the app is not in the foreground for 90 seconds, we may need to + * turn off all processing in order to avoid wasting CPU. + * Notice that we use a boost timer on the IoContext because Qt actually + * freezes its threads automatically shortly after the user navigates away. + */ + m_freezeTimer.expires_after(std::chrono::seconds(90)); + m_freezeTimer.async_wait([this](const boost::system::error_code &error) { + if (!error) handleFreeze(); + }); +} + +void FloweePayPrivate::onAppShown() +{ + if (m_appState == AppForeground) + return; + m_appState = AppForeground; + m_freezeTimer.cancel(); + checkOnline(); + /* + * We are brought back to the foreground. + * + * On different iterations of Android the behavior can differ quite substantially + * when it comes to being allowed to continue using resources while not being active. + * As such there is no real way to know what being inactive has done to our network + * connections. They may have been kept alive for whatever time we were + * not active, they may all have been removed or timed out already. + * + * What we'll do is to start the actions again which will check up on our connections + * and create new ones if we need them. + */ + if (m_parent->appProtection() == FloweePay::AppUnlocked + && m_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { + // re-lock the app after 10 minutes of not being in the front. + m_parent->setAppProtection(FloweePay::AppPassword); + } + m_sleepStart = QDateTime(); +} + +void FloweePayPrivate::handleFreeze() +{ + auto wallets = m_parent->wallets(); + const jboolean powerSaveOn = QJniObject(QNativeInterface::QAndroidApplication::context()) + .callMethod("isPowerSaveMode", "()Z"); + if (powerSaveOn == JNI_FALSE) { + for (const auto &wallet : wallets) { + assert(wallet->segment()); + if (wallet->walletIsImporting()) { // allow running in background + // check again if it is still running in a while. + m_freezeTimer.expires_after(std::chrono::seconds(100)); + m_freezeTimer.async_wait([this](const boost::system::error_code &error) { + if (!error) handleFreeze(); + }); + return; + } + } + } + m_checkOfflineTimer->stop(); // if it finds network, it starts everything.. + for (auto &wallet : wallets) { + wallet->saveWallet(); + } + m_parent->p2pNet()->saveData(); + m_parent->p2pNet()->setPowerMode(P2PNet::LowPower); // should disconnect all peers +} + +void FloweePayPrivate::followBGSettings() +{ + // so, the user may move the scrollbar around a lot and change the setup of the + // background-running design a lot, we only commit 2 seconds after they finished + // moving stuff around. + if (m_updateBackgroundTimer == nullptr) { + m_updateBackgroundTimer = new QTimer(this); + m_updateBackgroundTimer->setTimerType(Qt::CoarseTimer); + m_updateBackgroundTimer->setInterval(2000); + QObject::connect (m_updateBackgroundTimer, &QTimer::timeout, this, [=]() { + int h = 0; // zero is effectively a cancel + if (m_parent->backgroundUpdates()) + h = m_parent->backgroundUpdateInterval(); + auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); + main.callObjectMethod("enableRegularUpdates", "(I)V", h); + m_updateBackgroundTimer->deleteLater(); + m_updateBackgroundTimer = nullptr; + }); + } + m_updateBackgroundTimer->start(); // stops and starts. +} + +void FloweePayPrivate::backgroundUpdaterToggled() +{ + auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); + main.callObjectMethod("enableRegularUpdates", "(I)V", + m_parent->backgroundUpdates() ? m_parent->backgroundUpdateInterval() : 0); +} + + + +// -------------------------------------- + +void FloweePay::setupPlatform() +{ + assert(g_fpPriv == nullptr); + assert(QThread::currentThread() == thread()); // next line can't make timers otherwise + assert(!m_offline); // not available on Android + + g_fpPriv = new FloweePayPrivate(this); + if (qobject_cast(QCoreApplication::instance()) == nullptr) { // this is headless. + g_fpPriv->checkOnline(); return; } - assert(QThread::currentThread() == thread()); // can't make timers otherwise - assert(!m_offline); // not available on Android - assert(!g_checkOfflineTimer); - g_checkOfflineTimer = new QTimer(this); - g_checkOfflineTimer->setTimerType(Qt::VeryCoarseTimer); - g_checkOfflineTimer->setInterval(7000); - connect (g_checkOfflineTimer, &QTimer::timeout, &checkOnline); - g_checkOfflineTimer->start(); // but that won't happen for another 7s. + setDeviceOffline(true); + // onAppShown() is expected to check the online status of the device, + // which will start the p2pnet services if we became online. + g_fpPriv->onAppShown(); + // so now we can check if we are online and if so, we reschedule the background + // updater to start N hours from NOW (if enabled). + if (!deviceOffline()) + g_fpPriv->backgroundUpdaterToggled(); // make sure that we tell the android side. - // on Android, an app is either full screen (active) or inactive and not visible. - // It is expected we save state as we move to inactive state in order to make the app - // trivial to kill without loss of data. - // The above 'aboutToQuit' will not be given enough CPU time to actually save much. - assert(guiApp); - static QDateTime g_sleepStart; - connect(guiApp, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { - if (state == Qt::ApplicationInactive || state == Qt::ApplicationSuspended) { - if (g_sleepStart.isNull()) { - logInfo() << "App no longer active. Start saving data"; - g_sleepStart = QDateTime::currentDateTimeUtc(); - saveAll(); - p2pNet()->saveData(); - saveData(); - m_indexerServices->save(); - g_checkOfflineTimer->stop(); - } - /* - * This is to do powersaving management. - * When the app is not in the foreground for 90 seconds, we may need to - * turn off all processing in order to avoid wasting CPU. - * Notice that we use a boost timer on the IoContext because Qt actually - * freezes its threads automatically. - */ -// if (g_freezeTimer == nullptr) -// g_freezeTimer = new boost::asio::system_timer(ioContext()); -// g_freezeTimer->expires_after(std::chrono::seconds(90)); -// g_freezeTimer->async_wait([](const boost::system::error_code &error) { -// if (!error) handleFreeze(); -// }); - } - else if (state == Qt::ApplicationActive) { -// if (g_freezeTimer) -// g_freezeTimer->cancel(); - g_checkOfflineTimer->start(); - checkOnline(); - /* - * We are brought back to the foreground. - * - * On different iterations of Android the behavior can differ quite substantially - * when it comes to being allowed to continue using resources while not being active. - * As such there is no real way to know what being inactive has done to our network - * connections. They may have been kept alive for whatever time we were - * not active, they may all have been removed or timed out already. - * - * What we'll do is to start the actions again which will check up on our connections - * and create new ones if we need them. - */ - m_downloadManager->setPowerMode(P2PNet::NormalPower); - if (!m_deviceOffline && m_loadingCompleted) - p2pNet()->start(); - - if (m_appProtection == AppUnlocked - && g_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { - // re-lock the app after 10 minutes of not being in the front. - setAppProtection(AppPassword); - } - g_sleepStart = QDateTime(); - } - }); - - connect (this, &FloweePay::backgroundUpdateIntervalChanged, &followBGSettings); - connect (this, &FloweePay::backgroundUpdatesChanged, &backgroundUpdaterToggled); - if (!FloweePay::instance()->deviceOffline()) - backgroundUpdaterToggled(); // make sure that we tell the android side. - - connect (this, &FloweePay::darkSkinChanged, this, &updateTitleBarColor); + // the color of the bar above the app can be set, we copy the expected background color + // of the header. updateTitleBarColor(); + connect (this, &FloweePay::darkSkinChanged, this, &updateTitleBarColor); } +void FloweePay::softSave() +{ + assert(m_loadingCompleted); + saveAll(); + m_downloadManager->saveData(); + saveData(); + m_indexerServices->save(); +} + +// platform specific getter bool fp_platformSkinDark() { auto context = QJniObject(QNativeInterface::QAndroidApplication::context()); diff --git a/src/FloweePay_android_p.h b/src/FloweePay_android_p.h new file mode 100644 index 0000000..f206e15 --- /dev/null +++ b/src/FloweePay_android_p.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "FloweePay.h" + +#include +#include + +class QTimer; + +class FloweePayPrivate : public QObject { + Q_OBJECT +public: + explicit FloweePayPrivate(FloweePay *parent = nullptr); + + void airplaneModeToggled(); + void checkOnline(); + void onAppHidden(); + void onAppShown(); + +signals: + void airplaneModeCheckNeeded(); + +private slots: + void checkAirplaneMode(); + void followBGSettings(); // background-running + +public slots: + void backgroundUpdaterToggled(); + +private: + enum AppState { + AppHidden, + AppForeground + }; + + void handleFreeze(); + + FloweePay * const m_parent; + AppState m_appState = AppHidden; + QDateTime m_sleepStart; + + boost::asio::system_timer m_freezeTimer; + QTimer *m_checkOfflineTimer; + QTimer *m_updateBackgroundTimer; +}; diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index c9cc95a..725d7aa 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -61,6 +61,8 @@ void JNI_notificationPermissionDenied(JNIEnv *env, jobject thiz) FloweePay::instance()->setNewBlockMuted(true); } +// implementation in FloweePay_android.cpp +void JNI_processAction(JNIEnv *env, jobject thiz, jstring data); void setupCallbacks(UserIntent *pi) { @@ -68,10 +70,14 @@ void setupCallbacks(UserIntent *pi) const JNINativeMethod methods[] = { {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)}, {"startScan", "()V", reinterpret_cast(JNI_startQRScanner)}, - {"notificationPermissionDenied", "()V", reinterpret_cast(JNI_notificationPermissionDenied)} + {"notificationPermissionDenied", "()V", reinterpret_cast(JNI_notificationPermissionDenied)}, + {"seenAction", "(Ljava/lang/String;)V", reinterpret_cast(JNI_processAction)} }; QJniEnvironment env; - env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 3); + env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 4); + + auto me = QJniObject(QNativeInterface::QAndroidApplication::context()); + me.callObjectMethod("onQtAppLoaded", "()V"); // setup the notification channels and data jclass notifications = env.findClass("org/flowee/pay/PayNotifications"); @@ -158,6 +164,4 @@ void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *c auto myActivity = QJniObject(QNativeInterface::QAndroidApplication::context()); myActivity.callObjectMethod("onQtAppStarted", "()V"); } - - app->startNet(); // lets go! } -- 2.54.0 From f8e678bc9b0282ffa759bf6532c2075aa735050b Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 12 Mar 2025 15:47:23 +0100 Subject: [PATCH 1283/1428] Specifically specify some currencies --- guis/mobile/CurrencySelector.qml | 3 ++- src/PriceDataProvider.cpp | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/guis/mobile/CurrencySelector.qml b/guis/mobile/CurrencySelector.qml index fa5aa06..98263ce 100644 --- a/guis/mobile/CurrencySelector.qml +++ b/guis/mobile/CurrencySelector.qml @@ -56,6 +56,7 @@ Page { "cch", // "ngn" "no_NO", // "nok" "en_PN", // "nzd" + "es_PE", // "pen" "fil", // "php" "pl_PL", // pln "ur", // "pkr" @@ -69,7 +70,7 @@ Page { "uk", // "uah" "es_VE", // "vef" "vi", // "vnd" - "en_LS", // "zar" + "en_LS" // "zar" ] delegate: Rectangle { diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 34ba1b9..9d8ac04 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -107,8 +107,24 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) } // some currencies need conversion from USD - if (m_currency == QLatin1String("ARS") - || m_currency == QLatin1String("VES")) { + if (m_currency == "VES" || m_currency == "PEN" || m_currency == "ALL" || m_currency == "ANG" + || m_currency == "AOA" || m_currency == "AZN" || m_currency == "BDT" || m_currency == "BGN" + || m_currency == "BHD" || m_currency == "BIF" || m_currency == "BOB" || m_currency == "BWP" + || m_currency == "BYN" || m_currency == "BZD" || m_currency == "CDF" || m_currency == "COP" + || m_currency == "CRC" || m_currency == "CUP" || m_currency == "CZK" || m_currency == "DJF" + || m_currency == "DOP" || m_currency == "DZD" || m_currency == "EGP" || m_currency == "ETB" + || m_currency == "GEL" || m_currency == "GHS" || m_currency == "GMD" || m_currency == "GNF" + || m_currency == "GTQ" || m_currency == "HNL" || m_currency == "IRR" || m_currency == "IRT" + || m_currency == "ISK" || m_currency == "JMD" || m_currency == "JOD" || m_currency == "KES" + || m_currency == "KGS" || m_currency == "KRW" || m_currency == "KZT" || m_currency == "LBP" + || m_currency == "MAD" || m_currency == "MGA" || m_currency == "MLC" || m_currency == "MRU" + || m_currency == "MWK" || m_currency == "NAD" || m_currency == "NIO" || m_currency == "NPR" + || m_currency == "PAB" || m_currency == "PYG" || m_currency == "QAR" || m_currency == "RON" + || m_currency == "RSD" || m_currency == "RWF" || m_currency == "SYP" || m_currency == "TND" + || m_currency == "TTD" || m_currency == "TZS" || m_currency == "UAH" || m_currency == "UGX" + || m_currency == "USD" || m_currency == "UYU" || m_currency == "UZS" || m_currency == "VES" + || m_currency == "VND" || m_currency == "XAF" || m_currency == "XAG" || m_currency == "XAU" + || m_currency == "XOF" || m_currency == "XPT" || m_currency == "ZMW" || m_currency == "ARS") { m_yadioViaUSD = true; } -- 2.54.0 From 651cf98332a26ca5e67ecd04992c6f68b5548c36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 12 Mar 2025 21:04:07 +0100 Subject: [PATCH 1284/1428] Add a space after the prefix here --- src/PriceDataProvider.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 9d8ac04..6d5a702 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -96,6 +96,10 @@ void PriceDataProvider::setCurrency(const QLocale &countryLocale) m_currencySymbolPost = QString(" ") + m_currencySymbolPrefix; m_currencySymbolPrefix.clear(); } + else if (m_currency == QLatin1String("PEN")) { + // these require a space after prefix + m_currencySymbolPrefix += " "; + } // drop the '.00' behind the prices as this country doesn't traditionlly do that m_displayCents = !(m_currency == QLatin1String("JPY") || m_currency == QLatin1String("HUF") -- 2.54.0 From 94983670a278d4206d8b54637dcc0d2548b75ec5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Mar 2025 14:31:47 +0100 Subject: [PATCH 1285/1428] Remove commented out code --- src/CameraController.cpp | 25 ------------------------- src/CameraController.h | 6 ------ 2 files changed, 31 deletions(-) diff --git a/src/CameraController.cpp b/src/CameraController.cpp index a2d5da3..bfb85f3 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -614,31 +614,6 @@ void CameraController::setTorchEnabled(bool on) emit torchEnabledChanged(); } -#if 0 -bool CameraController::pasteData(const QString &string) -{ - if (d->scanRequest == nullptr) - return false; - if (d->scanRequest->scanType() != QRScanner::PaymentDetails - && d->scanRequest->scanType() != QRScanner::PrivateKeyWIF) - return false; - - if (!string.isEmpty()) { - d->scanRequest->finishedScan(string, QRScanner::Clipboard); - d->scanRequest = nullptr; - // stop camera - d->cameraStarted = false; - emit cameraActiveChanged(); - if (d->scanningThread == nullptr) { - // then the above emit would have no effect; - qrScanFinished(); - } - setHelpText(QString()); - } - return !string.isEmpty(); -} -#endif - void CameraController::setCamera(QObject *object) { if (object == d->camera) diff --git a/src/CameraController.h b/src/CameraController.h index e57a5a7..a5e493f 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -56,12 +56,6 @@ public: void abortRequest(QRScanner *request); Q_INVOKABLE void abort(); -#if 0 - /** - * Try to complete the current scan request by instead taking the \a string. - */ - Q_INVOKABLE bool pasteData(const QString &string); -#endif void setCamera(QObject *object); QObject *camera() const; -- 2.54.0 From 539381ce215a1abd8f829a5dfe7ed72878ee88da Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Mar 2025 15:53:11 +0100 Subject: [PATCH 1286/1428] Make Camera work the first time again This removes the Qt middle-man for requesting the camera permissions and we just code this in our own Java class. This solves the issue we observed where after the approval of the user we didn't actually manage to start the camera on some phones. --- android/java/org/flowee/pay/MainActivity.java | 22 +++++++ src/CameraController.cpp | 59 +++++++++---------- src/CameraController.h | 7 ++- src/FloweePay_android.cpp | 7 +++ src/main_utils_android.cpp | 6 +- 5 files changed, 66 insertions(+), 35 deletions(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 6bb0dda..fe59723 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -36,6 +36,7 @@ public class MainActivity extends QtActivity public native void startScan(); public native void notificationPermissionDenied(); public native void seenAction(String string); + public native void setCameraPermission(boolean yes); private boolean nativesBound = false; @@ -59,6 +60,24 @@ public class MainActivity extends QtActivity filterAndForward(getIntent()); } + /* + * Check the permissions for the camera. + * As soon as we know we'll call the setCameraPermission(bool) callback. + * Notice that this may include actually asking the user via an Android activity. + */ + public void canUseCamera() + { + int rc = getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()); + if (rc == PackageManager.PERMISSION_GRANTED) { + setCameraPermission(true); + return; + } + // that means we'll have to make Android ask the user. + String perms[] = new String[1]; + perms[0] = Manifest.permission.CAMERA; + requestPermissions(perms, 2); // 2 is a requestCode + } + @Override protected void onResume() { @@ -154,6 +173,9 @@ public class MainActivity extends QtActivity if (grantResults[0] == PackageManager.PERMISSION_DENIED) notificationPermissionDenied(); } + else if (requestCode == 2) { // Camera (see canUseCamera()) + setCameraPermission(grantResults[0] == PackageManager.PERMISSION_GRANTED); + } } public boolean isPowerSaveMode() diff --git a/src/CameraController.cpp b/src/CameraController.cpp index bfb85f3..bc9bf8a 100644 --- a/src/CameraController.cpp +++ b/src/CameraController.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * Copyright (C) 2020 Axel Waggershauser * * This program is free software: you can redistribute it and/or modify @@ -32,18 +32,14 @@ #include #include #include -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) -#include -#endif +#include enum AskingState { NotAsked, - Asking, Denied, Authorized }; -#include class QRScanningThread; class CameraControllerPrivate @@ -256,8 +252,8 @@ void CameraControllerPrivate::checkState() // -------------------------------------------------------------------- QRScanningThread::QRScanningThread(CameraControllerPrivate *parent) - : m_parent(parent), - scanType(QRScanner::InvalidType) + : scanType(QRScanner::InvalidType), + m_parent(parent) { m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode); m_decodeHints.setTryHarder(true); @@ -499,6 +495,7 @@ CameraController::CameraController(QObject *parent) // The Android permissions requesting stuff returns results in a different thread, // allow an easy way to move back to the main thread using a connection. QObject::connect(this, SIGNAL(startCheckState()), this, SLOT(checkState()), Qt::QueuedConnection); + QObject::connect(this, SIGNAL(cameraPermissionReceived()), this, SLOT(handleCameraPermission()), Qt::QueuedConnection); } void CameraController::startRequest(QRScanner *request) @@ -513,31 +510,14 @@ void CameraController::startRequest(QRScanner *request) emit visibleChanged(); } - if (d->state == NotAsked) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && QT_CONFIG(permissions) - switch (QCoreApplication::instance()->checkPermission(QCameraPermission {})) { - case Qt::PermissionStatus::Granted: d->state = Authorized; break; - case Qt::PermissionStatus::Denied: d->state = Denied; break; - case Qt::PermissionStatus::Undetermined: - d->state = Asking; - QCoreApplication::instance()->requestPermission(QCameraPermission {}, - this, [=](const QPermission &permission) { - - if (permission.status() == Qt::PermissionStatus::Granted) { - d->state = Authorized; - // move the actual turning on of the camera to the next event. - // please notice that this reply came in a different thread than the main one. - // We must move back to the main one before doing anything. - emit startCheckState(); - } else { - d->state = Denied; - } - }); - break; - } + if (d->state == NotAsked || d->state == Denied) { +#if TARGET_OS_Android + auto me = QJniObject(QNativeInterface::QAndroidApplication::context()); + me.callObjectMethod("canUseCamera", "()V"); #else - d->state = Authorized; + setCameraPermission(true); #endif + return; } // give the overlay screen time to appear before // activating the camera. @@ -691,7 +671,7 @@ void CameraController::qrScanFinished() emit torchEnabledChanged(); } // Have a bit of delay with actually turning off the camera. - QTimer::singleShot(100, [=]() { + QTimer::singleShot(100, this, [=]() { d->cameraStarted = false; emit cameraActiveChanged(); // makes the QML 'stop()' the camera. }); @@ -707,6 +687,14 @@ void CameraController::initCamera() d->initCamera(); } +void CameraController::handleCameraPermission() +{ + if (d->state == Denied) + abort(); + else + QTimer::singleShot(100, this, SLOT(checkState())); +} + QString CameraController::helpText() const { return d->helpText; @@ -719,6 +707,13 @@ QRScanner::ScanType CameraController::scanType() const return d->scanRequest->scanType(); } +void CameraController::setCameraPermission(bool allowed) +{ + // lets assume the native OS called this in a thread that is not us. + d->state = allowed ? Authorized : Denied; + emit cameraPermissionReceived(); +} + void CameraController::setHelpText(const QString &text) { if (d->helpText == text) diff --git a/src/CameraController.h b/src/CameraController.h index a5e493f..c700817 100644 --- a/src/CameraController.h +++ b/src/CameraController.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2023 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -73,6 +73,9 @@ public: QString helpText() const; QRScanner::ScanType scanType() const; + // callback for the native OS to tell us we can start using the camera. + void setCameraPermission(bool allowed); + signals: void cameraChanged(); void videoSinkChanged(); @@ -86,11 +89,13 @@ signals: // \internal (used to move thread) void startCheckState(); void helpTextChanged(); + void cameraPermissionReceived(); private slots: void qrScanFinished(); void checkState(); void initCamera(); + void handleCameraPermission(); private: void setHelpText(const QString &text); diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 11a4e9e..274ef4b 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -20,6 +20,7 @@ #include "IndexerServices.h" #include "PriceDataProvider.h" #include "Wallet.h" +#include "CameraController.h" #include #include @@ -63,6 +64,12 @@ void JNI_processAction(JNIEnv *env, jobject thiz, jstring data) // -> check if we are offline now, if so then we should shut down the net */ } +void JNI_setCameraPermission(JNIEnv *env, jobject thiz, jboolean allowed) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + FloweePay::instance()->cameraController()->setCameraPermission(allowed == JNI_TRUE); +} FloweePayPrivate::FloweePayPrivate(FloweePay *parent) : QObject(parent), diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 725d7aa..2ef180e 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -63,6 +63,7 @@ void JNI_notificationPermissionDenied(JNIEnv *env, jobject thiz) // implementation in FloweePay_android.cpp void JNI_processAction(JNIEnv *env, jobject thiz, jstring data); +void JNI_setCameraPermission(JNIEnv *env, jobject thiz, jboolean allowed); void setupCallbacks(UserIntent *pi) { @@ -71,10 +72,11 @@ void setupCallbacks(UserIntent *pi) {"setPaymentIntentOnCPP", "(Ljava/lang/String;)V", reinterpret_cast(JNI_setPaymentIntent)}, {"startScan", "()V", reinterpret_cast(JNI_startQRScanner)}, {"notificationPermissionDenied", "()V", reinterpret_cast(JNI_notificationPermissionDenied)}, - {"seenAction", "(Ljava/lang/String;)V", reinterpret_cast(JNI_processAction)} + {"seenAction", "(Ljava/lang/String;)V", reinterpret_cast(JNI_processAction)}, + {"setCameraPermission", "(Z)V", reinterpret_cast(JNI_setCameraPermission)} }; QJniEnvironment env; - env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 4); + env.registerNativeMethods("org/flowee/pay/MainActivity", methods, 5); auto me = QJniObject(QNativeInterface::QAndroidApplication::context()); me.callObjectMethod("onQtAppLoaded", "()V"); -- 2.54.0 From a69a2a2096e256e5aefb10585bcad6ed430e0ae9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 13 Mar 2025 21:22:13 +0100 Subject: [PATCH 1287/1428] Set the statusbar text to be light on Android --- android/java/org/flowee/pay/MainActivity.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index fe59723..cce3ad4 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -20,6 +20,7 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtActivity; import android.Manifest; import android.os.*; +import android.view.*; import android.net.Uri; import android.app.AlarmManager; import android.app.PendingIntent; @@ -115,6 +116,11 @@ public class MainActivity extends QtActivity public void setStatusBarColor(int color) { getWindow().setStatusBarColor(color); + if (Build.VERSION.SDK_INT >= 30) { + // in all cases we want the text to be light + WindowInsetsController wic = getWindow().getInsetsController(); + wic.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS); + } } public void enableRegularUpdates(int hours) -- 2.54.0 From 872e37b57232c742de5c8e833c52f2a35df65d7b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 22:14:20 +0100 Subject: [PATCH 1288/1428] Make notes properties more declarative This changes the notes property to be only set by the user of the BroadcastFeedback.qml, and not from inside anymore which could break the property binding causing strange things to happen. --- guis/Flowee/BroadcastFeedback.qml | 11 +++++------ guis/desktop/SendTransactionPane.qml | 10 ++++++---- guis/mobile/PayWithQR.qml | 10 +++++++--- modules/build-transaction/PayToOthers.qml | 6 ++++-- modules/send-sweep/SendPage.qml | 1 - 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index af09d36..bd1a7d3 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -43,10 +43,10 @@ QQC2.Control { required property double bitcoinAmount required property int fiatPrice - required property string targetAddress// : payment.targetAddress + required property string targetAddress required property int status; - property string personalNote: ""; - property bool showPersonalNote: true + property alias personalNote: transactionComment.text + property var processNote: undefined property bool hideHeader: false Rectangle { @@ -198,11 +198,10 @@ QQC2.Control { width: Math.min(400, parent.width - 40); color: "#26282a" palette.base: statusLabel.color - onEditingFinished: root.personalNote = totalText + onTotalTextChanged: root.processNote(totalText) placeholderText: qsTr("Add a personal note") placeholderTextColor: "#505050" - text: root.personalNote - visible: root.showPersonalNote + visible: root.processNote !== undefined } } diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 6937f4e..870266b 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -268,8 +268,9 @@ Item { onCanSendChanged: setEnabled(QQC2.DialogButtonBox.Ok, canSend) onRejected: payment.reset(); onAccepted: { - payment.markUserApproved(); - broadcastFeedback.start(); + payment.markUserApproved(); + broadcastFeedback.personalNote = payment.userComment + broadcastFeedback.start(); } } } @@ -293,8 +294,9 @@ Item { fiatPrice: payment.fiatPrice targetAddress: payment.niceAddress status: payment.broadcastStatus - personalNote: payment.userComment - onPersonalNoteChanged: payment.userComment = personalNote + processNote: function process(note) { + payment.userComment = note + } onCloseButtonPressed: { payment.reset(); diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index be0aaf9..636739f 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -329,7 +329,10 @@ Page { anchors.bottomMargin: 10 width: parent.width enabled: payment.isValid && payment.txPrepared - onActivated: payment.markUserApproved() + onActivated: { + payment.markUserApproved() + broadcastPage.personalNote = payment.userComment + } visible: payment.account.isDecrypted || !payment.account.needsPinToPay } @@ -437,8 +440,9 @@ Page { fiatPrice: payment.fiatPrice targetAddress: payment.niceAddress status: payment.broadcastStatus - personalNote: payment.userComment - onPersonalNoteChanged: payment.userComment = personalNote + processNote: function process(note) { + payment.userComment = note + } onCloseButtonPressed: { var mainView = thePile.get(0); diff --git a/modules/build-transaction/PayToOthers.qml b/modules/build-transaction/PayToOthers.qml index 10d52a2..c30bcd2 100644 --- a/modules/build-transaction/PayToOthers.qml +++ b/modules/build-transaction/PayToOthers.qml @@ -160,6 +160,7 @@ Page { width: parent.width onActivated: { payment.markUserApproved() + broadcastPage.personalNote = payment.userComment thePile.pop(); // the broadcast feedback is on the main screen. } } @@ -519,8 +520,9 @@ Page { fiatPrice: payment.fiatPrice targetAddress: payment.niceAddress status: payment.broadcastStatus - personalNote: payment.userComment - onPersonalNoteChanged: payment.userComment = personalNote + processNote: function process(note) { + payment.userComment = note + } onCloseButtonPressed: { var mainView = thePile.get(0); diff --git a/modules/send-sweep/SendPage.qml b/modules/send-sweep/SendPage.qml index bb640ab..b5b7941 100644 --- a/modules/send-sweep/SendPage.qml +++ b/modules/send-sweep/SendPage.qml @@ -170,7 +170,6 @@ Mobile.Page { bitcoinAmount: sweeper.sweepTotal fiatPrice: Fiat.price targetAddress: sweeper.targetAddress - showPersonalNote: false onCloseButtonPressed: { var mainView = thePile.get(0); -- 2.54.0 From 78b11368bc51c52dd333be4890a4d9f5337cd872 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 22:18:18 +0100 Subject: [PATCH 1289/1428] Don't reserve space for fiat in testnet --- guis/desktop/SendTransactionPane.qml | 2 +- guis/desktop/Transaction.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 870266b..6cd081e 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -604,7 +604,7 @@ Item { // only HD wallets can use this anchors.rightMargin: portfolio.current.isHDWallet ? 30 : 0 colorize: false - fiatWidth: 90 + fiatWidth: Pay.isMainChain ? 90 : 0 } Flowee.Label { id: ageLabel diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index 752950f..7b612b0 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -133,7 +133,7 @@ Rectangle { id: bitcoinAmountLabel visible: Pay.activityShowsBch || !Pay.isMainChain fiatTimestamp: model.date - fiatWidth: 90 + fiatWidth: Pay.isMainChain ? 90 : 0 value: { let inputs = model.fundsIn let outputs = model.fundsOut -- 2.54.0 From f20da2750279cd26f855737d7e6c958b83b1b101 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 22:44:07 +0100 Subject: [PATCH 1290/1428] Remove fiat entry if showing on testnet Also fix positioning of the hamburger menu --- guis/mobile/PriceInputWidget.qml | 33 ++++++++++++++++++++------------ guis/mobile/ReceiveTab.qml | 1 - 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index 7f0ddc2..c273256 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -27,7 +27,7 @@ FocusScope { // Notice that 'send all' overrules both and gets the data from the wallet-total property bool fiatFollowsSats: false // made available for the NumericKeyboardWidget - property var editor: fiatFollowsSats ? priceBch.money : priceFiat.money; + property var editor: data.fiatFollowsSats ? priceBch.money : priceFiat.money; /** * Payment Backend processes the fiat and satoshi based pair of payments. * The default is a simple backend, notice that the Payment QML type @@ -35,6 +35,12 @@ FocusScope { */ property QtObject paymentBackend : PaymentBackend {} + // if this is testnet, there is no fiat. Sats always lead. + Item { + id: data + property bool fiatFollowsSats: root.fiatFollowsSats || !Pay.isMainChain + } + onFiatFollowsSatsChanged: { if (!activeFocus) return; @@ -42,7 +48,7 @@ FocusScope { } function shake() { - if (fiatFollowsSats) + if (data.fiatFollowsSats) bchShaker.start(); else fiatShaker.start(); @@ -52,7 +58,7 @@ FocusScope { height: implicitHeight function takeFocus() { - if (fiatFollowsSats) + if (data.fiatFollowsSats) priceBch.forceActiveFocus(); else priceFiat.forceActiveFocus(); @@ -60,12 +66,12 @@ FocusScope { Flowee.BitcoinValueField { id: priceBch - y: root.fiatFollowsSats ? 5 : 68 + y: data.fiatFollowsSats ? 5 : 68 value: paymentBackend.paymentAmount focus: true fontPixelSize: size - property double size: fiatFollowsSats ? 38 : mainWindow.font.pixelSize* 0.8 - onActiveFocusChanged: if (activeFocus) fiatFollowsSats = true + property double size: data.fiatFollowsSats ? 38 : mainWindow.font.pixelSize* 0.8 + onActiveFocusChanged: if (activeFocus) data.fiatFollowsSats = true Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } Flowee.ObjectShaker { id: bchShaker } // 'shake' to give feedback on mistakes @@ -86,15 +92,16 @@ FocusScope { Flowee.FiatValueField { id: priceFiat value: paymentBackend.paymentAmountFiat - y: root.fiatFollowsSats ? 68 : 5 + y: data.fiatFollowsSats ? 68 : 5 focus: true fontPixelSize: size - property double size: !fiatFollowsSats ? 38 : mainWindow.font.pixelSize * 0.8 - onActiveFocusChanged: if (activeFocus) fiatFollowsSats = false + property double size: !data.fiatFollowsSats ? 38 : mainWindow.font.pixelSize * 0.8 + onActiveFocusChanged: if (activeFocus) data.fiatFollowsSats = false Behavior on size { NumberAnimation { } } Behavior on y { NumberAnimation { } } Flowee.ObjectShaker { id: fiatShaker } onValueEdited: paymentBackend.paymentAmountFiat = value + visible: Pay.isMainChain } Rectangle { @@ -107,19 +114,20 @@ FocusScope { color: palette.light width: inputs.width + 20 radius: 9 + visible: Pay.isMainChain Rectangle { color: palette.highlight opacity: 0.3 radius: 6 width: { - if (root.fiatFollowsSats) + if (data.fiatFollowsSats) return 35; return currencyNameShort.width + 10 } height: parent.height - 4 y: 2 - x: root.fiatFollowsSats ? 5 : 45 + x: data.fiatFollowsSats ? 5 : 45 Behavior on x { NumberAnimation { } } Behavior on width { NumberAnimation { } } @@ -163,9 +171,10 @@ FocusScope { height: parent.height color: palette.dark } + Item { width: 6; height: 1 } // spacer Flowee.HamburgerMenu { - y: 6 + y: 4 MouseArea { anchors.fill: parent anchors.margins: -12 diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 3af3a83..f8c444b 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -192,7 +192,6 @@ FocusScope { PriceInputWidget { id: priceInput - fiatFollowsSats: false width: parent.width Connections { -- 2.54.0 From 8040db971ad585029fb686e2daed765e4aba4e3b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 22:55:30 +0100 Subject: [PATCH 1291/1428] When a wallet is unlocked, show its receive QR --- src/WalletKeyView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WalletKeyView.cpp b/src/WalletKeyView.cpp index f737ec8..b57917f 100644 --- a/src/WalletKeyView.cpp +++ b/src/WalletKeyView.cpp @@ -130,6 +130,6 @@ void WalletKeyView::transactionConfirmed(int txIndex) void WalletKeyView::encryptionChanged() { assert(QThread::currentThread() == thread()); - if (m_wallet->encryption() == Wallet::FullyEncrypted && !m_wallet->isDecrypted()) + if (m_wallet->encryption() == Wallet::FullyEncrypted) emit walletEncrypted(); } -- 2.54.0 From 3bef7f43756c8e9ddeaf15516e689e298d91425b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 22:59:21 +0100 Subject: [PATCH 1292/1428] Give text more space --- guis/mobile/ReceiveTab.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index f8c444b..4710fca 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -94,6 +94,7 @@ FocusScope { id: instructions anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Share this QR to receive") + y: 10 opacity: editViewActive ? 0 : 0.5 Behavior on opacity { OpacityAnimator { } } visible: root.showInstructions @@ -101,7 +102,7 @@ FocusScope { Flowee.QRWidget { id: qr width: parent.width - y: root.showInstructions ? instructions.height : 0 + y: root.showInstructions ? instructions.y + instructions.height + 15 : 0 qrSize: { var avail = root.height - y - swipeView.height; return Math.min(256, avail); -- 2.54.0 From a03bcf91afc765cdecec2092e46d526753dafb66 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 16 Mar 2025 23:06:52 +0100 Subject: [PATCH 1293/1428] Allow two Pays running on diff networks. Make the lock file have a network specific. Testnet/Mainnet. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 517b6c3..8eed4c0 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -149,7 +149,7 @@ FloweePay::FloweePay() : m_chain(s_chain), m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)), m_indexerServices(new IndexerServices(m_basedir, ioContext(), this)), - m_lockFile(m_basedir + "/.lock") + m_lockFile(m_basedir + ((s_chain == P2PNet::Testnet4Chain) ? "/testnet4/.lock" : "/.lock")) { // make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals) LockedPoolManager::instance(); -- 2.54.0 From 442ebaf8a72210545a59bb6d0b9d814f0608e302 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 20 Mar 2025 09:54:37 +0100 Subject: [PATCH 1294/1428] Fix broken connection between pages This makes starting the app with an intent work again. --- guis/mobile/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index 1c24a05..b280e81 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -72,7 +72,7 @@ QQC2.ApplicationWindow { function onPaymentUrlChanged() { if (Intent.paymentUrl != "") { let page = thePile.pushSpecialPage("./PayWithQR.qml", true) - page.secret = Intent.paymentUrl; + page.start(Intent.paymentUrl); } } function onSweepKeyChanged() { -- 2.54.0 From 11ff1c8d879aaad1bcf03010ef0a745832c28d5a Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Mar 2025 13:33:22 +0100 Subject: [PATCH 1295/1428] Avoid calling price->start() twice on startup --- src/FloweePay.cpp | 7 ++----- src/FloweePay_android.cpp | 16 +++------------- src/FloweePay_dummy.cpp | 3 +++ 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8eed4c0..392359e 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -442,12 +442,9 @@ void FloweePay::loadingCompleted() for (auto wallet : std::as_const(m_wallets)) { wallet->performUpgrades(); } - setupPlatform(); - if (m_chain == P2PNet::MainChain) { + if (m_chain == P2PNet::MainChain) m_prices->loadPriceHistory(m_basedir); - if (!m_offline && !m_deviceOffline) - m_prices->start(); - } + setupPlatform(); m_loadingCompleted = true; emit loadComplete(); } diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 274ef4b..e24a1d7 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -64,6 +64,7 @@ void JNI_processAction(JNIEnv *env, jobject thiz, jstring data) // -> check if we are offline now, if so then we should shut down the net */ } + void JNI_setCameraPermission(JNIEnv *env, jobject thiz, jboolean allowed) { Q_UNUSED(env); @@ -128,7 +129,8 @@ void FloweePayPrivate::checkOnline() m_parent->p2pNet()->setPowerMode(isOffline ? P2PNet::LowPower : P2PNet::NormalPower); if (wasOffline && !isOffline) { // came online m_parent->startNet(); - m_parent->prices()->start(); + if (m_chain == P2PNet::MainChain && !m_offline && !m_deviceOffline) + m_prices->start(); } m_checkOfflineTimer->stop(); if (isOffline) @@ -168,18 +170,6 @@ void FloweePayPrivate::onAppShown() m_appState = AppForeground; m_freezeTimer.cancel(); checkOnline(); - /* - * We are brought back to the foreground. - * - * On different iterations of Android the behavior can differ quite substantially - * when it comes to being allowed to continue using resources while not being active. - * As such there is no real way to know what being inactive has done to our network - * connections. They may have been kept alive for whatever time we were - * not active, they may all have been removed or timed out already. - * - * What we'll do is to start the actions again which will check up on our connections - * and create new ones if we need them. - */ if (m_parent->appProtection() == FloweePay::AppUnlocked && m_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { // re-lock the app after 10 minutes of not being in the front. diff --git a/src/FloweePay_dummy.cpp b/src/FloweePay_dummy.cpp index 6aac784..4ae6cf5 100644 --- a/src/FloweePay_dummy.cpp +++ b/src/FloweePay_dummy.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "FloweePay.h" +#include "PriceDataProvider.h" // this file contains dummy implementations of // platform code to describe the default behavior @@ -28,4 +29,6 @@ bool fp_platformSkinDark() void FloweePay::setupPlatform() { + if (m_chain == P2PNet::MainChain && !m_offline && !m_deviceOffline) + m_prices->start(); } -- 2.54.0 From ecc427274555420a096a6f0f4d576487e379db3e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Mar 2025 13:35:47 +0100 Subject: [PATCH 1296/1428] Rename file to not be a dummy anymore. As we actually add code, it makes sense to name it 'basic' instead of dummy --- src/CMakeLists.txt | 4 ++-- src/{FloweePay_dummy.cpp => FloweePay_basic.cpp} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{FloweePay_dummy.cpp => FloweePay_basic.cpp} (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8166af..c9fdb08 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,13 +71,13 @@ if (ANDROID) elseif (${Qt6DBus_FOUND}) list(APPEND PAY_SOURCES NotificationManager_p_dbus.h NotificationManager_dbus.cpp - FloweePay_dummy.cpp + FloweePay_basic.cpp main_utils.cpp ) else () list(APPEND PAY_SOURCES NotificationManager_dummy.cpp - FloweePay_dummy.cpp + FloweePay_basic.cpp main_utils.cpp ) endif () diff --git a/src/FloweePay_dummy.cpp b/src/FloweePay_basic.cpp similarity index 100% rename from src/FloweePay_dummy.cpp rename to src/FloweePay_basic.cpp -- 2.54.0 From 8047fbbadb617eae1271db6c091d77de4cdd9afb Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 24 Mar 2025 20:09:07 +0100 Subject: [PATCH 1297/1428] Add auto-locking feature The application and wallets will re-lock after 10 minutes of not using them. --- src/FloweePay_basic.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/FloweePay_basic.cpp b/src/FloweePay_basic.cpp index 4ae6cf5..68b6caa 100644 --- a/src/FloweePay_basic.cpp +++ b/src/FloweePay_basic.cpp @@ -17,8 +17,13 @@ */ #include "FloweePay.h" #include "PriceDataProvider.h" +#include "Wallet.h" -// this file contains dummy implementations of +#include + +#include + +// this file contains a basic implementations of // platform code to describe the default behavior // when no platform specific implementation was found @@ -31,4 +36,33 @@ void FloweePay::setupPlatform() { if (m_chain == P2PNet::MainChain && !m_offline && !m_deviceOffline) m_prices->start(); + + static boost::asio::system_timer *g_lockTimer = nullptr; + auto *gui = qobject_cast(QCoreApplication::instance()); + if (gui) { + connect(gui, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { + if (state == Qt::ApplicationHidden || state == Qt::ApplicationInactive) { + if (g_lockTimer == nullptr) + g_lockTimer = new boost::asio::system_timer(ioContext()); + g_lockTimer->expires_after(std::chrono::minutes(10)); + g_lockTimer->async_wait([](const boost::system::error_code &error) { + if (!error) { + // after a period of time, lock the wallet. + auto *fp = FloweePay::instance(); + if (fp->appProtection() == FloweePay::AppUnlocked) + fp->setAppProtection(FloweePay::AppPassword); + for (auto *wallet : fp->wallets()) { + if (wallet->encryption() != Wallet::NotEncrypted + && wallet->isDecrypted()) { + wallet->forgetEncryptedSecrets(); + } + } + } + }); + } + else if (g_lockTimer) { + g_lockTimer->cancel(); + } + }); + } } -- 2.54.0 From a8aa8dd2e24cc62b4a9c0b378cb6b047ff41c2f9 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Mar 2025 12:52:52 +0100 Subject: [PATCH 1298/1428] remove unused code --- src/FloweePay.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/FloweePay.h b/src/FloweePay.h index 514069c..d6290c6 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -528,7 +528,6 @@ private: UnlockingKeyboard m_unlockingKeyboard = SmallNumbersKeyboard; bool m_loadingCompleted = false; // 'init()' completed bool m_loadCompleteEmitted = false; // avoid emitting this on every unlock - bool m_appUnlocked = false; bool m_headless = false; bool m_darkSkin = true; bool m_skinFollowsPlatform = false; -- 2.54.0 From 69e390ac6ce97a16dbdb6762f8335e8d0f128e61 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 25 Mar 2025 14:41:46 +0100 Subject: [PATCH 1299/1428] Add support for pasting a bip70 style url --- guis/mobile/SendTransactionsTab.qml | 3 ++- src/QMLClipboardHelper.cpp | 9 +++++++++ src/QMLClipboardHelper.h | 3 ++- src/WalletEnums.h | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 83cc752..478ac19 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -88,7 +88,8 @@ FocusScope { let t = cbh.type; if (t === ClipboardHelper.Addresses || t === ClipboardHelper.LegacyAddresses - || t === ClipboardHelper.AddressUrl) { + || t === ClipboardHelper.AddressUrl + || t === ClipboardHelper.PaymentProtocol) { var item = thePile.replace("PayWithQR.qml"); item.start(text); return; diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index 499c81b..d3c0a92 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -82,6 +82,13 @@ void QMLClipboardHelper::parseClipboard() break; } } + if (endOfAddress == prefix.size() && partial.size() > endOfAddress + 10) { + if (partial[endOfAddress + 1] == 'r') { + // likely a payment bip70 url + type = WalletEnums::PaymentProtocol; + break; + } + } // look further. partial = partial.mid(index + 1); } @@ -146,6 +153,8 @@ void QMLClipboardHelper::parseClipboard() typeFound = XPub; else if (m_filter.testAnyFlag(XPriv) && type == WalletEnums::XPriv) typeFound = XPriv; + else if (m_filter.testAnyFlag(PaymentProtocol) && type == WalletEnums::PaymentProtocol) + typeFound = PaymentProtocol; if (typeFound != NoFilter) setClipboardText(text, typeFound); diff --git a/src/QMLClipboardHelper.h b/src/QMLClipboardHelper.h index ff6900e..51b8243 100644 --- a/src/QMLClipboardHelper.h +++ b/src/QMLClipboardHelper.h @@ -56,8 +56,9 @@ public: MnemonicSeed = 0x10, XPub = 0x20, XPriv = 0x40, + PaymentProtocol = 0x80, - AnyType = 0x4F, + AnyType = 0xFF, Empty = 0x4000 }; Q_ENUM(Type) diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 21ba96b..eefb0ed 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -39,7 +39,8 @@ public: MissingLexicon, ElectrumMnemonic, XPub, - XPriv + XPriv, + PaymentProtocol }; Q_ENUM(StringType) -- 2.54.0 From 9a8809fc7006f50793081cea80dfb5bbd3c5d595 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 29 Mar 2025 22:55:08 +0100 Subject: [PATCH 1300/1428] Improve the background-running Android stuff After setting, check if it was approved by the user, and if not unset the checkbox again. Bugfix: On some Android implementations, setting this value opens a screen even if the value is already set, while on many others that is a no-op as you'd expect. So for those phones we check first before we set. --- android/java/org/flowee/pay/MainActivity.java | 17 +++++++++++---- src/FloweePay_android.cpp | 21 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index cce3ad4..b532955 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -123,6 +123,13 @@ public class MainActivity extends QtActivity } } + public boolean isIgnoringBatteryOptimizations() { + PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + if (pm == null) + return false; + return pm.isIgnoringBatteryOptimizations(getPackageName()); + } + public void enableRegularUpdates(int hours) { if (updatesInterval == hours) @@ -153,10 +160,12 @@ public class MainActivity extends QtActivity pendingIntent ); - // ask to run in the background - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - intent.setData(Uri.parse("package:" + getPackageName())); - startActivity(intent); + if (!isIgnoringBatteryOptimizations()) { + // ask to run in the background + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + } } public void requestNotificationPermission() diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index e24a1d7..b4722cc 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -129,8 +129,9 @@ void FloweePayPrivate::checkOnline() m_parent->p2pNet()->setPowerMode(isOffline ? P2PNet::LowPower : P2PNet::NormalPower); if (wasOffline && !isOffline) { // came online m_parent->startNet(); - if (m_chain == P2PNet::MainChain && !m_offline && !m_deviceOffline) - m_prices->start(); + if (m_parent->chain() == P2PNet::MainChain + && !m_parent->isOffline() && !m_parent->deviceOffline()) + m_parent->prices()->start(); } m_checkOfflineTimer->stop(); if (isOffline) @@ -231,6 +232,15 @@ void FloweePayPrivate::backgroundUpdaterToggled() auto main = QJniObject(QNativeInterface::QAndroidApplication::context()); main.callObjectMethod("enableRegularUpdates", "(I)V", m_parent->backgroundUpdates() ? m_parent->backgroundUpdateInterval() : 0); + + if (m_parent->backgroundUpdates()) { + QTimer::singleShot(100, m_parent, [main](){ + // check if user approved + auto done = main.callMethod("isIgnoringBatteryOptimizations", "()Z"); + if (!done) + FloweePay::instance()->setBackgroundUpdates(false); + }); + } } @@ -256,8 +266,11 @@ void FloweePay::setupPlatform() g_fpPriv->onAppShown(); // so now we can check if we are online and if so, we reschedule the background // updater to start N hours from NOW (if enabled). - if (!deviceOffline()) - g_fpPriv->backgroundUpdaterToggled(); // make sure that we tell the android side. + if (!deviceOffline()) { + // make sure that we tell the android side. + // the timer is there to make sure that the GUI is fully rendered. + QTimer::singleShot(2000, g_fpPriv, &FloweePayPrivate::backgroundUpdaterToggled); + } // the color of the bar above the app can be set, we copy the expected background color // of the header. -- 2.54.0 From 2278f693a6b9fffbc6c58b5b793ec24fbe4a445a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 1 Apr 2025 23:01:27 +0200 Subject: [PATCH 1301/1428] Delay checks of onAppShown until unlocked This delays checks like being online until after the user has typed the pincode should the app be locked. --- src/FloweePay_android.cpp | 45 ++++++++++++++++++++++++++++----------- src/FloweePay_android_p.h | 2 ++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index b4722cc..6d48493 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -90,6 +90,30 @@ FloweePayPrivate::FloweePayPrivate(FloweePay *parent) this, &FloweePayPrivate::followBGSettings); connect (m_parent, &FloweePay::backgroundUpdatesChanged, this, &FloweePayPrivate::backgroundUpdaterToggled); + + /* first start is a bit complex. + * We set the 'deviceOffline' to true so the first time we call + * checkOnline (via a call to onAppShown()) it will think things + * changed and thus we start the network layer. + * + * The app may be locked at the time this is executed, as such we + * will wait until the loadComplete is emitted AND the app is not + * in locked state before we do the first 'onAppShown'. + * + * FloweePay::loadComplete will be emitted twice if the app is + * initially locked. + */ + m_parent->setDeviceOffline(true); + connect(m_parent, &FloweePay::loadComplete, this, [=]() { + if (m_firstStartHandled) + return; + if (m_parent->appProtection() == FloweePay::AppPassword) + return; + m_firstStartHandled = true; + // onAppShown() is expected to check the online status of the device, + // which will start the p2pnet services if we are/became online. + onAppShown(); + }); } void FloweePayPrivate::airplaneModeToggled() @@ -171,11 +195,18 @@ void FloweePayPrivate::onAppShown() m_appState = AppForeground; m_freezeTimer.cancel(); checkOnline(); - if (m_parent->appProtection() == FloweePay::AppUnlocked + if (m_sleepStart.isValid() && m_parent->appProtection() == FloweePay::AppUnlocked && m_sleepStart.addSecs(60 * 10) < QDateTime::currentDateTimeUtc()) { // re-lock the app after 10 minutes of not being in the front. m_parent->setAppProtection(FloweePay::AppPassword); } + // so now we can check if we are online and if so, we reschedule the background + // updater to start N hours from NOW (if enabled). + if (m_parent->appProtection() != FloweePay::AppPassword && !m_parent->deviceOffline()) { + // make sure that we tell the android side. + // the timer is there to make sure that the GUI is fully rendered. + QTimer::singleShot(2000, this, &FloweePayPrivate::backgroundUpdaterToggled); + } m_sleepStart = QDateTime(); } @@ -260,18 +291,6 @@ void FloweePay::setupPlatform() return; } - setDeviceOffline(true); - // onAppShown() is expected to check the online status of the device, - // which will start the p2pnet services if we became online. - g_fpPriv->onAppShown(); - // so now we can check if we are online and if so, we reschedule the background - // updater to start N hours from NOW (if enabled). - if (!deviceOffline()) { - // make sure that we tell the android side. - // the timer is there to make sure that the GUI is fully rendered. - QTimer::singleShot(2000, g_fpPriv, &FloweePayPrivate::backgroundUpdaterToggled); - } - // the color of the bar above the app can be set, we copy the expected background color // of the header. updateTitleBarColor(); diff --git a/src/FloweePay_android_p.h b/src/FloweePay_android_p.h index f206e15..1d27b86 100644 --- a/src/FloweePay_android_p.h +++ b/src/FloweePay_android_p.h @@ -57,4 +57,6 @@ private: boost::asio::system_timer m_freezeTimer; QTimer *m_checkOfflineTimer; QTimer *m_updateBackgroundTimer; + + bool m_firstStartHandled = false; }; -- 2.54.0 From 5037cc7f2cecc616f3312402ad257808ca67d8c3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 2 Apr 2025 10:50:58 +0200 Subject: [PATCH 1302/1428] Properly initialize the slider value Do the conversion from the backend in hours and set that on the slider at construction of this screen. --- guis/mobile/BackgroundSyncConfig.qml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index cfd8706..7ab948e 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -45,6 +45,20 @@ Page { QQC2.Slider { id: slider + value: { + var ts = Pay.backgroundUpdateInterval + if (ts > 6) { + let s = 3; + while (ts > 6) { + s -= 1; + ts /= 2; + } + slider.value = s; + } + else { + slider.value = 6 - (ts - 3); + } + } property int timeSpan: { var i = slider.value; if (i < 4) { @@ -55,13 +69,12 @@ Page { } return 6 - (i - 3); } - onTimeSpanChanged: Pay.backgroundUpdateInterval = timeSpan + onTimeSpanChanged: Pay.backgroundUpdateInterval = timeSpan visible: checkbox.checked width: parent.width stepSize: 1 from: 0 - value: 3 to: 8 snapMode: QQC2.Slider.SnapAlways background: Rectangle { -- 2.54.0 From 504cb7fe9b4a7b7f25db80d2920846bd4002b916 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 2 Apr 2025 11:27:25 +0200 Subject: [PATCH 1303/1428] Convert this one too --- src/main_utils_android.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 2ef180e..a10eb0c 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -102,7 +102,7 @@ void initLogger(CommandLineParserData *cld) logger->clearChannels(); logger->clearLogLevels(defaultVerbosity); #ifdef NETWORK_LOGGER - logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioService())); + logger->addChannel(new NetworkLogClient(FloweePay::instance()->ioContext())); #endif } -- 2.54.0 From 3f7cdc7dac69cfcfd60910b059a670d547e2ede3 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Apr 2025 12:55:26 +0200 Subject: [PATCH 1304/1428] Add onBoot Android notification The background timer stops when the device reboots, as such we need to re-start the periodic service timer at device boot. --- android/AndroidManifest.xml | 7 +++ android/java/org/flowee/Pay.java | 26 +++++++++++ android/java/org/flowee/pay/MainActivity.java | 44 ++++++++----------- .../java/org/flowee/pay/OnBootHandler.java | 44 +++++++++++++++++++ .../java/org/flowee/pay/PeriodicService.java | 36 +++++++++++++++ 5 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 android/java/org/flowee/Pay.java create mode 100644 android/java/org/flowee/pay/OnBootHandler.java diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 07f300b..3706d0f 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -7,6 +7,7 @@ + + + + + + diff --git a/android/java/org/flowee/Pay.java b/android/java/org/flowee/Pay.java new file mode 100644 index 0000000..62e3225 --- /dev/null +++ b/android/java/org/flowee/Pay.java @@ -0,0 +1,26 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.flowee; + +public class Pay +{ + public static final String UserConfigPath = "foo"; + + public static final String PropBackgroundInterval = "background_interval"; +} + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index b532955..c6b7cd0 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -17,20 +17,18 @@ */ package org.flowee.pay; +import java.util.Properties; +import java.io.*; import org.qtproject.qt.android.bindings.QtActivity; import android.Manifest; import android.os.*; import android.view.*; import android.net.Uri; -import android.app.AlarmManager; import android.app.PendingIntent; import android.provider.Settings; import android.content.*; import android.content.pm.PackageManager; -import java.io.*; - - public class MainActivity extends QtActivity { public native void setPaymentIntentOnCPP(String data); @@ -130,36 +128,30 @@ public class MainActivity extends QtActivity return pm.isIgnoringBatteryOptimizations(getPackageName()); } + /** + * Request Android to start us on a timer. + * @param hours when zero, cancel timer, otherwise the interval in hours. + */ public void enableRegularUpdates(int hours) { if (updatesInterval == hours) return; updatesInterval = hours; - AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); - if (alarmManager == null) - return; + PeriodicService.startTimer(this, hours); - // They can be scheduled, but they won't do anything unless the - // user approves the previous ask. - Intent serviceIntent = new Intent(this, PeriodicService.class); - serviceIntent.setPackage("org.flowee.pay"); - PendingIntent pendingIntent = PendingIntent.getService(this, 9875, - serviceIntent, PendingIntent.FLAG_IMMUTABLE); - - if (hours < 1) { - alarmManager.cancel(pendingIntent); - return; + // we store the interval in a properties file, only touched by Java code + Properties props = new Properties(); + try { + props.load(new FileInputStream(org.flowee.Pay.UserConfigPath)); + } catch (IOException e) {} + String hoursAsString = Integer.toString(hours); + if (props.getProperty(org.flowee.Pay.PropBackgroundInterval) != hoursAsString) { + props.setProperty(org.flowee.Pay.PropBackgroundInterval, hoursAsString); + try { + props.store(new FileWriter(org.flowee.Pay.UserConfigPath), null); + } catch (IOException e) {} } - long HourInMillis = 60 * 60 * 1000; - // Schedule to run approximately every \a hours hour. - alarmManager.setInexactRepeating( - AlarmManager.ELAPSED_REALTIME_WAKEUP, // Use elapsed time, wake device if asleep - SystemClock.elapsedRealtime() + hours * HourInMillis, // first run - hours * HourInMillis, // Repeat interval - pendingIntent - ); - if (!isIgnoringBatteryOptimizations()) { // ask to run in the background Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); diff --git a/android/java/org/flowee/pay/OnBootHandler.java b/android/java/org/flowee/pay/OnBootHandler.java new file mode 100644 index 0000000..e46fe61 --- /dev/null +++ b/android/java/org/flowee/pay/OnBootHandler.java @@ -0,0 +1,44 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.flowee.pay; + +import android.content.*; +import java.util.Properties; +import java.io.FileInputStream; + +public class OnBootHandler extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + /* On Android boot, re-schedule our perdiodic service + * based on what the C++ side told us is the interval last + * time the entire wallet ran. + */ + try { + Properties props = new Properties(); + props.load(new FileInputStream(org.flowee.Pay.UserConfigPath)); + String hours = props.getProperty(org.flowee.Pay.PropBackgroundInterval); + int h = Integer.parseInt(hours); + if (h > 0) + PeriodicService.startTimer(context, h); + } catch (Exception e) { + // no config, nothing to do. + } + } + } +} diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index b383d15..81ee315 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -19,6 +19,10 @@ package org.flowee.pay; import org.qtproject.qt.android.bindings.QtService; import android.os.*; +import android.content.*; +import android.app.*; +import android.app.AlarmManager; +import android.os.SystemClock; public class PeriodicService extends QtService { // called from C++ @@ -33,4 +37,36 @@ public class PeriodicService extends QtService { return pm.isPowerSaveMode(); return false; } + + /** + * Request Android to start us on a timer. + * @param hours when zero, cancel timer, otherwise the interval in hours. + */ + public static void startTimer(Context context, int hours) + { + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + if (alarmManager == null) + return; + + // They can be scheduled, but they won't do anything unless the + // user approves the previous ask. + Intent serviceIntent = new Intent(context, PeriodicService.class); + serviceIntent.setPackage("org.flowee.pay"); + PendingIntent pendingIntent = PendingIntent.getService(context, 9875, + serviceIntent, PendingIntent.FLAG_IMMUTABLE); + + if (hours < 1) { + alarmManager.cancel(pendingIntent); + return; + } + + long HourInMillis = 60 * 60 * 1000; + // Schedule to run approximately every \a hours hour. + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME_WAKEUP, // Use elapsed time, wake device if asleep + SystemClock.elapsedRealtime() + hours * HourInMillis, // first run + hours * HourInMillis, // Repeat interval + pendingIntent + ); + } } -- 2.54.0 From db0ca5a7797827bfd063718f4543b3724502e7cb Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Apr 2025 13:09:53 +0200 Subject: [PATCH 1305/1428] Make the fiat-price show question mark if unknown. --- guis/Flowee/BitcoinAmountLabel.qml | 2 ++ guis/desktop/main.qml | 10 +++++++++- src/PriceDataProvider.cpp | 2 +- src/PriceDataProvider.h | 7 +++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index 316f3a2..e74aaa1 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -147,6 +147,8 @@ QQC2.Control { anchors.right: parent.right color: main.color text: { + if (!Fiat.hasPrice) + return ""; var fiatPrice; if (root.fiatTimestamp == undefined) fiatPrice = Fiat.price; // todays price diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 07b18d0..33726c0 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -176,6 +176,8 @@ ApplicationWindow { text: { if (Pay.hideBalance && Pay.isMainChain) return Fiat.formattedPrice(100000000, Fiat.price) + if (!Fiat.hasPrice) + return "?" return Fiat.formattedPrice(totalBalance.value, Fiat.price) } visible: balanceInHeader.visible @@ -659,7 +661,13 @@ ApplicationWindow { Label { id: fiatValue property double prevPrice: 0 - text: qsTr("1 BCH is: %1").arg(Fiat.formattedPrice(100000000, Fiat.price)) + text: { + if (Fiat.hasPrice) + var price = Fiat.formattedPrice(100000000, Fiat.price); + else + price = "?"; + qsTr("1 BCH is: %1").arg(price); + } visible: Pay.isMainChain Behavior on color { ColorAnimation { duration: 300 } } diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 6d5a702..62d2f37 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -387,7 +387,7 @@ void PriceDataProvider::loadPriceHistory(const QString &basedir) m_priceHistory.reset(new PriceHistoryDataProvider(basedir, m_currency)); // take the last known price from our historical module to have something // mostly useful until we manage to fetch the data from the life feeds. - uint32_t timestamp = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); + uint32_t timestamp = QDateTime::currentSecsSinceEpoch(); auto lastKnownPrice = m_priceHistory->historicalPrice(timestamp); if (lastKnownPrice == 0) lastKnownPrice = 10000; // if we never fetched, set to 100,- diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 04a5d7c..8f5c540 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -30,7 +30,10 @@ class PriceDataProvider : public QObject { Q_OBJECT Q_PROPERTY(int price READ price NOTIFY priceChanged) + /// when the price is not recent, this returns true. Q_PROPERTY(bool oldData READ oldData NOTIFY priceChanged) + /// when there is no price data at all, this returns true. + Q_PROPERTY(bool hasPrice READ hasPrice NOTIFY priceChanged) Q_PROPERTY(QString currencyName READ currencyName NOTIFY currencySymbolChanged) Q_PROPERTY(bool displayCents READ displayCents NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) @@ -50,6 +53,10 @@ public: } // returns true if the data is more than an hour old. bool oldData() const; + // returns true when the current price has never been fetched. + bool hasPrice() const { + return m_currentPrice.timestamp > 0; + } void setCurrency(const QLocale &countryLocale); void setCountry(const QString &countrycode); -- 2.54.0 From 10fac24f74f1ed0d303a3b2a7031c79e8587fbb2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 6 Apr 2025 16:05:13 +0200 Subject: [PATCH 1306/1428] Rewrite FloweePay::amountToString() to add groups. The group separtors (aka thousand-separtor) as used by the system locale are used to format the string now, and we upgraded this to use UTF16 in order to avoid problems when they are not ascii. --- src/FloweePay.cpp | 52 +++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 392359e..fa8eb6b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -561,15 +562,16 @@ QString FloweePay::amountToStringPretty(double price) const // static QString FloweePay::amountToString(qint64 price, UnitOfBitcoin unit) { - if (unit == Satoshis) - return QString::number(price); - QByteArray string(QByteArray::number(std::abs(price))); - + QString baseNumbers = QString::number(std::abs(price)); + const int baseLength = baseNumbers.length(); int decimals; switch (unit) { default: decimals = 8; break; + case FloweePay::Satoshis: + decimals = 0; + break; case FloweePay::MilliBCH: decimals = 5; break; @@ -578,27 +580,33 @@ QString FloweePay::amountToString(qint64 price, UnitOfBitcoin unit) decimals = 2; break; } - const char decimalPoint = QLocale::system().decimalPoint().at(0).unicode(); - int stringLength = string.size(); - int neededSize = std::max(stringLength, decimals) + 1; // 1 for the decimalPoint. - if (price < 0) // minus sign - neededSize++; - if (stringLength <= decimals) // add a zero in front of the decimalPoint too. - neededSize++; - string.resize(neededSize); - char *str = string.data(); - memcpy(str + string.size() - stringLength, str, stringLength); // move to be right-aligned - for (int i = string.size() - stringLength; i > 0; --i) { str[i - 1] = '0'; } // pad zeros on left - - // insert the actual decimal point. We need to move the part in front of it back to the left to make space. - for (int i = 0; i < string.size() - decimals; ++i) { - str[i - 1] = str[i]; + static const QChar decimalPoint = QLocale::system().decimalPoint().at(0); + if (baseLength <= decimals) { + // if we need to add a zero and a dot in front of the string. + // use a simple cheap QStringBuilder setup + return (price < 0 ? QString("-0") : QString("0")) + % decimalPoint + % baseNumbers; } - str[string.size() - decimals - 1] = decimalPoint; + + // for anything more complex, build our own string. + // this allows us to insert group separators and decimalPoints + QChar data[20]; + int i = 0; if (price < 0) - *str = '-'; - return QString::fromLatin1(str); + data[i++] = '-'; + + static const QChar groupSeparator = QLocale::system().groupSeparator().at(0); + for (int k = 0; k < baseLength; ++k) { + const int pos = baseLength - k - decimals; + if (pos == 0) + data[i++] = decimalPoint; + else if (k > 0 && pos > 0 && (pos % 3) == 0) + data[i++] = groupSeparator; + data[i++] = baseNumbers.at(k); + } + return QString(data, i); } QString FloweePay::formatDate(QDateTime date, FloweePay::DateFormatOption type) const -- 2.54.0 From b0c4545b099d957b4225d8c2c77b4d5b05a54357 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 7 Apr 2025 15:11:36 +0200 Subject: [PATCH 1307/1428] Improve typing/pasting of seedphrase This adds the ability to paste an almost correct seed phrase, for instance when the last word is cut off. We also provide a new UI to propose words while typing the seed, allowing the user to tap on the words instead of having to finish typing them. --- guis/desktop/NewAccountImportAccount.qml | 53 +++++++++------ guis/mobile/ImportWalletPage.qml | 82 ++++++++++++++++++------ src/FloweePay.cpp | 28 +++++++- src/FloweePay.h | 15 ++++- src/QMLClipboardHelper.cpp | 10 +-- src/WalletEnums.h | 3 +- 6 files changed, 143 insertions(+), 48 deletions(-) diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index 2ca0d23..c7250e0 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -172,29 +172,46 @@ Item { clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed } } - Flowee.Label { - text: { - if (inputColumn.typedData === Wallet.PartialMnemonicWithTypo) { - var bareText = secretText.text; - // if inputMethodComposing true, that makes it simple to avoid the word that - // is being edited the 'text' property of secretText omits that one. - if (!secretText.inputMethodComposing) { - // in case we're editing without there being an inputmethod we find the word - // based on the char-pos. - let cp = secretText.cursorPosition; - let startEditWord = bareText.lastIndexOf(' ', cp); - let endEditWord = bareText.indexOf(' ', cp); - let before = bareText.substr(0, startEditWord); - let after = endEditWord > 0 ? bareText.substr(endEditWord) : ""; - bareText = before + after; + Flow { + width: parent.width + spacing: 10 + + Repeater { + model: Pay.mnemonicProposals + Rectangle { + width: txt.width + 16 + height: txt.height + 6 + color: mainWindow.floweeBlue + Flowee.Label { + id: txt + text: modelData + anchors.centerIn: parent + color: "white" } - if (Pay.identifyString(bareText) === Wallet.PartialMnemonicWithTypo) - return qsTr("Unknown word(s) found"); + MouseArea { + anchors.fill: parent + onClicked: { + var widget = secretText + var bareText = widget.text; + // if inputMethodComposing true, that makes it simple to avoid the word that + // is being edited the 'text' property of secretText omits that one. + if (!secretText.inputMethodComposing) { + let lastWordPos = bareText.lastIndexOf(' '); + if (lastWordPos > 0) + bareText = bareText.substr(0, lastWordPos); + } + var newText = bareText + " " + modelData + " "; + widget.text = newText + widget.cursorPosition = newText.length + } + } } - return "" } + } + Flowee.Label { + text: inputColumn.typedData === Wallet.PartialMnemonicWithTypo ? qsTr("Unknown word(s) found") : "" color: mainWindow.errorRed visible: text !== "" } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index ef2f750..19a702b 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -148,7 +148,7 @@ Page { id: entryPage width: parent.width y: 10 - spacing: 20 + spacing: 15 property var typedData: Pay.identifyString(secretText.totalText) property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic || typedData === Wallet.ElectrumMnemonic @@ -240,30 +240,70 @@ Page { buddy: secretText clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed } - } - Flowee.Label { - text: { - if (entryPage.typedData === Wallet.PartialMnemonicWithTypo) { - var bareText = secretText.text; - // if inputMethodComposing true, that makes it simple to avoid the word that - // is being edited the 'text' property of secretText omits that one. - if (!secretText.inputMethodComposing) { - // in case we're editing without there being an inputmethod we find the word - // based on the char-pos. - let cp = secretText.cursorPosition; - let startEditWord = bareText.lastIndexOf(' ', cp); - let endEditWord = bareText.indexOf(' ', cp); + Item { + clip: true + height: Math.min(flowLayout.height + 12, textSecretBox.y); + width: parent.width + y: -height + visible: flowLayout.height > 1 + Rectangle { + anchors.fill: parent + color: palette.base + border.color: palette.midlight + border.width: 1 + radius: 5 + } - let before = bareText.substr(0, startEditWord); - let after = endEditWord > 0 ? bareText.substr(endEditWord) : ""; - bareText = before + after; + Flickable { + anchors.fill: parent + anchors.margins: 6 + contentWidth: width + contentHeight: flowLayout.height + Flow { + id: flowLayout + width: parent.width + spacing: 10 + + Repeater { + model: Pay.mnemonicProposals + Rectangle { + width: txt.width + 16 + height: txt.height + 6 + color: mainWindow.floweeBlue + radius: 5 + Flowee.Label { + id: txt + text: modelData + anchors.centerIn: parent + color: "white" + } + + MouseArea { + anchors.fill: parent + onClicked: { + var widget = secretText + var bareText = widget.text; + // if inputMethodComposing true, that makes it simple to avoid the word that + // is being edited the 'text' property of secretText omits that one. + if (!secretText.inputMethodComposing) { + let lastWordPos = bareText.lastIndexOf(' '); + if (lastWordPos > 0) + bareText = bareText.substr(0, lastWordPos); + } + var newText = bareText + " " + modelData + " "; + widget.text = newText + widget.cursorPosition = newText.length + } + } + } + } } - - if (Pay.identifyString(bareText) === Wallet.PartialMnemonicWithTypo) - return qsTr("Unknown word(s) found"); } - return "" } + } + + Flowee.Label { + text: entryPage.typedData === Wallet.PartialMnemonicWithTypo ? qsTr("Unknown word(s) found") : "" color: mainWindow.errorRed visible: text !== "" } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index fa8eb6b..8ce7a7b 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -923,6 +923,19 @@ void FloweePay::connectToWallet(Wallet *wallet) }, Qt::QueuedConnection); } +QStringList FloweePay::mnemonicProposals() const +{ + return m_mnemonicProposals; +} + +void FloweePay::setMnemonicProposals(const QStringList &proposals) +{ + if (m_mnemonicProposals == proposals) + return; + m_mnemonicProposals = proposals; + emit mnemonicProposalsChanged(); +} + int FloweePay::backgroundUpdateInterval() const { return m_backgroundUpdateInterval; @@ -1322,8 +1335,10 @@ bool FloweePay::checkDerivation(const QString &path) const } } -WalletEnums::StringType FloweePay::identifyString(const QString &string) const +WalletEnums::StringType FloweePay::identifyString(const QString &string, bool updateMnemonicProposals) { + if (updateMnemonicProposals) + setMnemonicProposals(QStringList()); // clear auto words = splitString(string); if (words.isEmpty()) { m_hdSeedValidator.clearSelectedLanguage(); @@ -1333,7 +1348,8 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const try { int firstWord = -2; // split into words. - for (const auto word : std::as_const(words)) { + for (int i = 0; i < words.size(); ++i) { + const auto &word = words.at(i); int index = m_hdSeedValidator.findWord(word.toString()); if (firstWord == -2) { bool lowerCased = false; @@ -1361,6 +1377,14 @@ WalletEnums::StringType FloweePay::identifyString(const QString &string) const } } else if (index == -1) { // a not-first-word failed the lookup. + if (i == words.size() - 1) { + auto proposals = m_hdSeedValidator.completeWords(word.toString()); + if (updateMnemonicProposals) + setMnemonicProposals(proposals); + if (!proposals.isEmpty()) + return WalletEnums::PartialMnemonicUnfinished; + } + return WalletEnums::PartialMnemonicWithTypo; } // if we get to this point in the loop then we have a real word that we found in the dictionary. diff --git a/src/FloweePay.h b/src/FloweePay.h index d6290c6..2f911bf 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -85,6 +85,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(int backgroundUpdateInterval READ backgroundUpdateInterval WRITE setBackgroundUpdateInterval NOTIFY backgroundUpdateIntervalChanged FINAL) Q_PROPERTY(QString chainPrefix READ qchainPrefix CONSTANT FINAL) + Q_PROPERTY(QStringList mnemonicProposals READ mnemonicProposals NOTIFY mnemonicProposalsChanged FINAL) public: enum UnitOfBitcoin { BCH, @@ -274,7 +275,8 @@ public: Q_INVOKABLE bool checkDerivation(const QString &path) const; /// take a bitcoin-related string and identify the type. - Q_INVOKABLE WalletEnums::StringType identifyString(const QString &string) const; + /// @see mnemonicProposals() + Q_INVOKABLE WalletEnums::StringType identifyString(const QString &string, bool updateMnemonicProposals = true); /// return a string version of the \a unit name. tBCH for instance. Q_INVOKABLE QString nameOfUnit(FloweePay::UnitOfBitcoin unit) const; @@ -450,6 +452,13 @@ public: void softSave(); #endif + /** + * When identifyString has finished, and the argument there was a partial mnemonic + * this will return a list of word proposals for the partially typed word. + * For all other types this will result in an empty list. + */ + QStringList mnemonicProposals() const; + signals: void loadComplete(); /// \internal @@ -480,6 +489,8 @@ signals: void backgroundUpdatesChanged(); void backgroundUpdateIntervalChanged(); + void mnemonicProposalsChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -502,6 +513,7 @@ private: Wallet *createWallet(const QString &name); uint32_t walletStartHeightHint() const; void connectToWallet(Wallet *wallet); + void setMnemonicProposals(const QStringList &newMnemonicProposals); mutable Mnemonic m_hdSeedValidator; @@ -521,6 +533,7 @@ private: QList m_wallets; QHash m_accountConfigs; // key is wallet-segment-id QLockFile m_lockFile; + QStringList m_mnemonicProposals; int m_dspTimeout = 5000; int m_windowWidth = 500; int m_windowHeight = 500; diff --git a/src/QMLClipboardHelper.cpp b/src/QMLClipboardHelper.cpp index d3c0a92..7591ae7 100644 --- a/src/QMLClipboardHelper.cpp +++ b/src/QMLClipboardHelper.cpp @@ -64,7 +64,7 @@ void QMLClipboardHelper::parseClipboard() text = text.replace(QLatin1String("\r"), QLatin1String(" ")); text = text.trimmed(); - auto type = FloweePay::instance()->identifyString(text); + auto type = FloweePay::instance()->identifyString(text, false); if (type == WalletEnums::Unknown || type == WalletEnums::PartialMnemonicWithTypo || type == WalletEnums::PartialMnemonic) { // try harder QString partial(text); QString prefix = QString::fromStdString(chainPrefix()) + ":"; @@ -76,7 +76,7 @@ void QMLClipboardHelper::parseClipboard() QString tillSpace = partial.mid(index, end); const int endOfAddress = tillSpace.indexOf('?'); if (endOfAddress > 0) { - type = FloweePay::instance()->identifyString(tillSpace.left(endOfAddress)); + type = FloweePay::instance()->identifyString(tillSpace.left(endOfAddress), false); if (type == WalletEnums::CashPKH || type == WalletEnums::CashSH) { text = tillSpace; break; @@ -104,7 +104,7 @@ void QMLClipboardHelper::parseClipboard() end = partial.size(); if (end - index >= 50 && end - index < 55) { QString wif = partial.mid(index, end); - type = FloweePay::instance()->identifyString(wif); + type = FloweePay::instance()->identifyString(wif, false); if (type == WalletEnums::PrivateKey) { text = wif; break; @@ -120,7 +120,7 @@ void QMLClipboardHelper::parseClipboard() for (auto &word : text.split(' ', Qt::SkipEmptyParts)) { if (word.length() > 40 && word.length() < 50) { QString address = prefix + word; - type = FloweePay::instance()->identifyString(address); + type = FloweePay::instance()->identifyString(address, false); if (type == WalletEnums::CashPKH || type == WalletEnums::CashSH) { text = address; break; @@ -147,7 +147,7 @@ void QMLClipboardHelper::parseClipboard() else if (m_filter.testAnyFlag(PrivateKey) && type == WalletEnums::PrivateKey) typeFound = PrivateKey; else if (m_filter.testAnyFlag(MnemonicSeed) - && (type == WalletEnums::CorrectMnemonic || type == WalletEnums::ElectrumMnemonic)) + && (type == WalletEnums::CorrectMnemonic || type == WalletEnums::ElectrumMnemonic || type == WalletEnums::PartialMnemonicUnfinished)) typeFound = MnemonicSeed; else if (m_filter.testAnyFlag(XPub) && type == WalletEnums::XPub) typeFound = XPub; diff --git a/src/WalletEnums.h b/src/WalletEnums.h index eefb0ed..27b4e8c 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -34,7 +34,8 @@ public: LegacyPKH, LegacySH, PartialMnemonic, - PartialMnemonicWithTypo, + PartialMnemonicWithTypo, // when a word is incorrect + PartialMnemonicUnfinished, // when the last word (in the string) is incorrect CorrectMnemonic, MissingLexicon, ElectrumMnemonic, -- 2.54.0 From 3090b74e4fb2a29d809822b9ee95980f60a3c25e Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 7 Apr 2025 20:59:40 +0200 Subject: [PATCH 1308/1428] Update amountToString to remove the stringBuilder This cleans up the code even more, unifying the different cases into one simple approach. Added a unit test to verify the result. --- src/FloweePay.cpp | 25 ++++++++-------- testing/CMakeLists.txt | 6 ++-- testing/utils/CMakeLists.txt | 26 +++++++++++++++++ testing/utils/TestUtils.cpp | 55 ++++++++++++++++++++++++++++++++++++ testing/utils/TestUtils.h | 30 ++++++++++++++++++++ 5 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 testing/utils/CMakeLists.txt create mode 100644 testing/utils/TestUtils.cpp create mode 100644 testing/utils/TestUtils.h diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 8ce7a7b..d892387 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -582,25 +581,25 @@ QString FloweePay::amountToString(qint64 price, UnitOfBitcoin unit) } static const QChar decimalPoint = QLocale::system().decimalPoint().at(0); - if (baseLength <= decimals) { - // if we need to add a zero and a dot in front of the string. - // use a simple cheap QStringBuilder setup - return (price < 0 ? QString("-0") : QString("0")) - % decimalPoint - % baseNumbers; - } + static const QChar groupSeparator = QLocale::system().groupSeparator().at(0); - // for anything more complex, build our own string. - // this allows us to insert group separators and decimalPoints - QChar data[20]; + // to avoid lots of mallocs, we avoid appending strings. Instead we just + // use a simple char array and copy the characters in one by one. + QChar data[30]; int i = 0; if (price < 0) data[i++] = '-'; - static const QChar groupSeparator = QLocale::system().groupSeparator().at(0); + if (baseLength <= decimals) { // a possible leading '0.' + data[i++] = '0'; + data[i++] = decimalPoint; + } + for (int k = decimals - baseLength; k > 0; --k) // any zeros after the decimal point + data[i++] = '0'; + for (int k = 0; k < baseLength; ++k) { const int pos = baseLength - k - decimals; - if (pos == 0) + if (pos == 0 && k > 0) data[i++] = decimalPoint; else if (k > 0 && pos > 0 && (pos % 3) == 0) data[i++] = groupSeparator; diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 97cc99f..bd428c4 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -1,5 +1,5 @@ # This file is part of the Flowee project -# Copyright (C) 2020 Tom Zander +# Copyright (C) 2020-2025 Tom Zander # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,12 +23,14 @@ if (${Qt6Test_FOUND}) add_subdirectory(value) add_subdirectory(priceHistory) add_subdirectory(fiat) + add_subdirectory(utils) - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS + add_custom_target(check COMMAND LANG=C ${CMAKE_CTEST_COMMAND} DEPENDS test_wallet test_wallethistorymodel test_value test_price_history test_fiat + test_utils ) endif () diff --git a/testing/utils/CMakeLists.txt b/testing/utils/CMakeLists.txt new file mode 100644 index 0000000..436d71b --- /dev/null +++ b/testing/utils/CMakeLists.txt @@ -0,0 +1,26 @@ +# This file is part of the Flowee project +# Copyright (C) 2025 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(CMAKE_AUTOMOC ON) + +include_directories(${Qt6Test_INCLUDE_DIRS}) + +add_executable(test_utils + TestUtils.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp +) +target_link_libraries(test_utils pay_lib Qt6::Test) +add_test(NAME Pay_test_utils COMMAND test_utils) diff --git a/testing/utils/TestUtils.cpp b/testing/utils/TestUtils.cpp new file mode 100644 index 0000000..19f2602 --- /dev/null +++ b/testing/utils/TestUtils.cpp @@ -0,0 +1,55 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TestUtils.h" +#include + +#include +#include +#include + +void TestUtils::testAmountToString() +{ + QCOMPARE(FloweePay::amountToString(12345678, FloweePay::BCH), "0.12345678"); + QCOMPARE(FloweePay::amountToString(512345678, FloweePay::BCH), "5.12345678"); + QCOMPARE(FloweePay::amountToString(12345678, FloweePay::MilliBCH), "123.45678"); + QCOMPARE(FloweePay::amountToString(512345678, FloweePay::MilliBCH), "5,123.45678"); + QCOMPARE(FloweePay::amountToString(12345678, FloweePay::MicroBCH), "123,456.78"); + QCOMPARE(FloweePay::amountToString(512345678, FloweePay::MicroBCH), "5,123,456.78"); + QCOMPARE(FloweePay::amountToString(12345678, FloweePay::Bits), "123,456.78"); + QCOMPARE(FloweePay::amountToString(512345678, FloweePay::Bits), "5,123,456.78"); + QCOMPARE(FloweePay::amountToString(12345678, FloweePay::Satoshis), "12,345,678"); + QCOMPARE(FloweePay::amountToString(512345678, FloweePay::Satoshis), "512,345,678"); + + QCOMPARE(FloweePay::amountToString(1, FloweePay::Satoshis), "1"); + QCOMPARE(FloweePay::amountToString(10, FloweePay::Satoshis), "10"); + QCOMPARE(FloweePay::amountToString(100, FloweePay::Satoshis), "100"); + QCOMPARE(FloweePay::amountToString(1000, FloweePay::Satoshis), "1,000"); + QCOMPARE(FloweePay::amountToString(10000, FloweePay::Satoshis), "10,000"); + QCOMPARE(FloweePay::amountToString(100000, FloweePay::Satoshis), "100,000"); + + QCOMPARE(FloweePay::amountToString(0, FloweePay::BCH), "0.00000000"); + QCOMPARE(FloweePay::amountToString(0, FloweePay::MilliBCH), "0.00000"); + QCOMPARE(FloweePay::amountToString(1, FloweePay::MilliBCH), "0.00001"); + QCOMPARE(FloweePay::amountToString(10, FloweePay::MilliBCH), "0.00010"); + QCOMPARE(FloweePay::amountToString(100, FloweePay::MilliBCH), "0.00100"); + QCOMPARE(FloweePay::amountToString(1000, FloweePay::MilliBCH), "0.01000"); + QCOMPARE(FloweePay::amountToString(10000, FloweePay::MilliBCH), "0.10000"); + QCOMPARE(FloweePay::amountToString(100000, FloweePay::MilliBCH), "1.00000"); +} + +QTEST_MAIN(TestUtils) diff --git a/testing/utils/TestUtils.h b/testing/utils/TestUtils.h new file mode 100644 index 0000000..2a68419 --- /dev/null +++ b/testing/utils/TestUtils.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include + +class TestUtils : public QObject +{ + Q_OBJECT +private slots: + void testAmountToString(); +}; + +#endif -- 2.54.0 From fc9f6f2658ca339aa55805baa95b299eb0c9b83d Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 12:00:05 +0200 Subject: [PATCH 1309/1428] Fix java string compare Probably the oldest gotcha on Java, strings can not be compared with the == sign. It's been several decades, so I have a bit of an excuse ;-) --- android/java/org/flowee/pay/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index c6b7cd0..cef6442 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -145,7 +145,7 @@ public class MainActivity extends QtActivity props.load(new FileInputStream(org.flowee.Pay.UserConfigPath)); } catch (IOException e) {} String hoursAsString = Integer.toString(hours); - if (props.getProperty(org.flowee.Pay.PropBackgroundInterval) != hoursAsString) { + if (!hoursAsString.equals(props.getProperty(org.flowee.Pay.PropBackgroundInterval))) { props.setProperty(org.flowee.Pay.PropBackgroundInterval, hoursAsString); try { props.store(new FileWriter(org.flowee.Pay.UserConfigPath), null); -- 2.54.0 From 58f2f8dd18e39dcceabcb8ac62d8d6c2a92e961c Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 12:13:18 +0200 Subject: [PATCH 1310/1428] not a foreground service afterall --- android/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3706d0f..1b2fa43 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -50,7 +50,6 @@ -- 2.54.0 From 18b446d6e039db75011d03cb97c812009e67c04b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 12:15:56 +0200 Subject: [PATCH 1311/1428] Don't request permissions unless needed --- android/java/org/flowee/pay/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index cef6442..1c26087 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -152,7 +152,7 @@ public class MainActivity extends QtActivity } catch (IOException e) {} } - if (!isIgnoringBatteryOptimizations()) { + if (hours > 0 && !isIgnoringBatteryOptimizations()) { // ask to run in the background Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + getPackageName())); -- 2.54.0 From dffcda40d8e64210a874843309b29a0aed347943 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 12:17:21 +0200 Subject: [PATCH 1312/1428] Use a serious name --- android/java/org/flowee/Pay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/java/org/flowee/Pay.java b/android/java/org/flowee/Pay.java index 62e3225..77123ab 100644 --- a/android/java/org/flowee/Pay.java +++ b/android/java/org/flowee/Pay.java @@ -19,7 +19,7 @@ package org.flowee; public class Pay { - public static final String UserConfigPath = "foo"; + public static final String UserConfigPath = "pay-android.conf"; public static final String PropBackgroundInterval = "background_interval"; } -- 2.54.0 From fab99d8390446da3fd80ee24fd6a81bdc8db505b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 13:00:08 +0200 Subject: [PATCH 1313/1428] Update German translations from Crowdin --- translations/floweepay-desktop_de.ts | 6 +++--- translations/floweepay-mobile_de.ts | 4 ++-- translations/module-bigtransfer_de.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index e139d76..f7b0c56 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -31,7 +31,7 @@ Protect With Pin... - Mit Pin schützen... + Mit PIN schützen... @@ -1214,12 +1214,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Pin to Open - Pin zum Öffnen + PIN zum Öffnen Pin to Pay - Pin um zu bezahlen + PIN um zu bezahlen diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 1f00fc0..f0c9814 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -347,7 +347,7 @@ Scan QR - Scan QR + QR scannen @@ -478,7 +478,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Scanning QR code with Instant Pay enabled will make the transfer go out without confirmation. As long as it does not exceed the set limit. - Das Scannen des QR-Codes mit aktivierter Sofortzahlung macht die Überweisung ohne Bestätigung. Solange er das festgelegte Limit nicht überschreitet. + Das Scannen eines QR-Codes mit aktivierter Sofortzahlung macht die Überweisung ohne Bestätigung, solange das festgelegte Limit nicht überschritten wird. diff --git a/translations/module-bigtransfer_de.ts b/translations/module-bigtransfer_de.ts index 0db567e..b897de9 100644 --- a/translations/module-bigtransfer_de.ts +++ b/translations/module-bigtransfer_de.ts @@ -56,7 +56,7 @@ Prepare... - Wird vorbereitet... + Vorbereiten... -- 2.54.0 From b450d613bc152bd7438bb65f6103090fa4c70c9a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Apr 2025 13:01:26 +0200 Subject: [PATCH 1314/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 1b2fa43..62a20f0 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="40" android:versionName="2025.04.0"> diff --git a/src/main.cpp b/src/main.cpp index 43be074..8b9ae86 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2022 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -98,7 +98,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.03.2"); + qapp.setApplicationVersion("2025.04.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 50b4ba57aa718b8f11adce050011dd9d38d2bd0e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 13 Apr 2025 12:46:28 +0200 Subject: [PATCH 1315/1428] Make finding electron servers more agressive. This moves the fetching (by dns) of the servers to the launch of Pay instead of at the start of the module. This periodically removes punishment scores in order to reassess remotes and have long health guarentee. This also works harder to find any servers even if all have a higher punishment somehow. --- src/FloweePay.cpp | 7 ++++++ src/IndexerServices.cpp | 50 ++++++++++++++++++++++++++++++++--------- src/IndexerServices.h | 3 +++ src/QMLImportHelper.cpp | 4 ---- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index d892387..3cf4812 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -447,6 +447,13 @@ void FloweePay::loadingCompleted() setupPlatform(); m_loadingCompleted = true; emit loadComplete(); + + // if online and not headless. + if (!m_offline && !m_deviceOffline && qobject_cast(QCoreApplication::instance())) { + // make sure that we'll have a list of indexer services when we + // need them later. + indexerServices()->populate(); + } } void FloweePay::saveData() diff --git a/src/IndexerServices.cpp b/src/IndexerServices.cpp index 5984c3f..e2f3882 100644 --- a/src/IndexerServices.cpp +++ b/src/IndexerServices.cpp @@ -43,7 +43,9 @@ enum ServicesTags { PortNum, SecurePortNum, ProtocolVersion, - Punishment + Punishment, + LastAdjustDate, + LastFetchDate }; @@ -63,8 +65,13 @@ IndexerServices::IndexerServices(const QString &basedir, boost::asio::io_context void IndexerServices::populate() { - m_resolver.async_resolve("ec-seed.flowee.cash", "http", std::bind(&IndexerServices::onSeedLookupComplete, - this, std::placeholders::_1, std::placeholders::_2)); + if (!m_lastFetchDate.isValid() || m_lastFetchDate.daysTo(QDateTime::currentDateTimeUtc()) > 5) { + m_resolver.async_resolve("ec-seed.flowee.cash", "http", std::bind(&IndexerServices::onSeedLookupComplete, + this, std::placeholders::_1, std::placeholders::_2)); + } + else { + emit startMaybeFindServices(); + } } void IndexerServices::load() @@ -104,14 +111,30 @@ void IndexerServices::load() case Punishment: indexItem.punishment = parser.intData(); break; + case LastAdjustDate: + m_lastAdjustDate = QDateTime::fromSecsSinceEpoch(parser.longData()); + break; + case LastFetchDate: + m_lastFetchDate = QDateTime::fromSecsSinceEpoch(parser.longData()); + break; } } + + // every month lower the punishment to avoid one time failures from sticking. + if (!m_lastAdjustDate.isValid() || m_lastAdjustDate.daysTo(QDateTime::currentDateTimeUtc()) > 30) { + for (auto i = m_knownServices.begin(); i != m_knownServices.end(); ++i) { + i->punishment = std::max(0, i->punishment - 250); + } + m_lastAdjustDate = QDateTime::currentDateTimeUtc(); + } } void IndexerServices::save() { auto buffer = std::make_shared(m_knownServices.size() * 1000); Streaming::MessageBuilder builder(buffer); + builder.add(LastAdjustDate, static_cast(m_lastAdjustDate.toSecsSinceEpoch())); + builder.add(LastFetchDate, static_cast(m_lastFetchDate.toSecsSinceEpoch())); for (const auto &service : m_knownServices) { builder.add(Hostname, service.hostname.toStdString()); builder.add(IPAddress, service.ip.toStdString()); @@ -159,16 +182,22 @@ EndPoint IndexerServices::service() const EndPoint ep; QMutexLocker locker(&m_mutex); std::vector eligible; - for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { - if (iter->punishment < 250 && iter->securePort > 0 - && iter->hostname != iter->ip - && (iter->protocolVersion == 0 || iter->protocolVersion >= 0x010502)) { - eligible.push_back(*iter); + int bar = 0; + do { + bar += 250; + // we want SOME results, if we keep raising the bar if for some reason everyone + // got a punishment score to exclude them. + for (auto iter = m_knownServices.begin(); iter != m_knownServices.end(); ++iter) { + if (iter->punishment < bar && iter->securePort > 0 + && iter->hostname != iter->ip + && (iter->protocolVersion == 0 || iter->protocolVersion >= 0x010502)) { + eligible.push_back(*iter); + } } - } + } while (eligible.size() < std::min(5, m_knownServices.size()) && bar < 2000); if (!eligible.empty()) { int best = -1; - // try 5 random ones in order to avoid picking one with a worse punishment. + // try 5 random ones in order to avoid picking one with a worst punishment. for (int attempt = 0; attempt < 5; ++attempt) { int index = GetRand(eligible.size()); if (best == -1) { @@ -207,6 +236,7 @@ void IndexerServices::onSeedLookupComplete(const boost::system::error_code &erro return; QMutexLocker locker(&m_mutex); + m_lastFetchDate = QDateTime::currentDateTimeUtc(); for (auto dnsIter = results.begin(); dnsIter != results.end(); ++dnsIter) { const QString ip = QString::fromStdString(dnsIter->endpoint().address().to_string()); bool found = false; diff --git a/src/IndexerServices.h b/src/IndexerServices.h index a3beb9b..0e5b582 100644 --- a/src/IndexerServices.h +++ b/src/IndexerServices.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,8 @@ private: tcp::resolver m_resolver; mutable QMutex m_mutex; QString m_basedir; + QDateTime m_lastAdjustDate; + QDateTime m_lastFetchDate; FetchIndexerServicePeers *m_fetcher = nullptr; }; diff --git a/src/QMLImportHelper.cpp b/src/QMLImportHelper.cpp index d4b6f39..c5d89df 100644 --- a/src/QMLImportHelper.cpp +++ b/src/QMLImportHelper.cpp @@ -27,10 +27,6 @@ QMLImportHelper::QMLImportHelper(QObject *parent) : QObject{parent}, m_importHandler(new ImportHandler(this)) { - // make sure that we'll have a list of indexer services when we - // need them later. - FloweePay::instance()->indexerServices()->populate(); - connect (m_importHandler, SIGNAL(finished()), this, SLOT(checkFinished()), Qt::QueuedConnection); -- 2.54.0 From 664e38c705c6867aac384d9bb15e49e1ef14d80f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 15 Apr 2025 12:50:38 +0200 Subject: [PATCH 1316/1428] Use more local variables. This avoids the linter complaint of having defined the same variable twice. --- guis/Flowee/ListViewKeyHandler.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guis/Flowee/ListViewKeyHandler.qml b/guis/Flowee/ListViewKeyHandler.qml index d3bf65f..4c54cf2 100644 --- a/guis/Flowee/ListViewKeyHandler.qml +++ b/guis/Flowee/ListViewKeyHandler.qml @@ -47,7 +47,7 @@ Item { event.accepted = true; } else if (event.key === Qt.Key_PageUp) { - var cy = root.target.contentY + let cy = root.target.contentY if (cy - root.target.height * 2 < 0) root.target.flick(0, root.target.height * 10000); else @@ -55,7 +55,7 @@ Item { event.accepted = true; } else if (event.key === Qt.Key_PageDown) { - var cy = root.target.contentY + let cy = root.target.contentY if (cy + root.target.height * 2 > root.target.contentHeight) root.target.flick(0, -root.target.height * 10000); else -- 2.54.0 From 42608e8b00c9190e0d4a897622e260f3981bc03a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Apr 2025 17:54:12 +0200 Subject: [PATCH 1317/1428] Improve the wallet selection widget in wallet2wallet This shows a selector with only relevant wallet choices and no unneeded details. --- guis/Flowee/Dialog.qml | 5 +- modules/big-transfer/Main.qml | 89 ++++++++++++++------- modules/big-transfer/SimpleWalletPicker.qml | 74 +++++++++++++++++ modules/big-transfer/bigtransfer.qrc | 1 + 4 files changed, 136 insertions(+), 33 deletions(-) create mode 100644 modules/big-transfer/SimpleWalletPicker.qml diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 93649bb..cce774e 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2023 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -72,7 +72,7 @@ QQC2.Popup { if (content.item) wanted = Math.max(wanted, content.item.implicitWidth) let max = window.width - let min = Math.max(buttons.implicitWidth + 20, 300) + let min = Math.max(buttons.implicitWidth + 20, 220) let ideal = Math.max(min, max / 3) if (max <= 360) // mobile width max = max * 0.9; @@ -105,6 +105,7 @@ QQC2.Popup { standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel anchors.right: parent.right anchors.rightMargin: 10 + height: standardButtons === DialogButtonBox.NoButton ? 0 : implicitHeight onAccepted: { root.accepted(); root.close() diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml index 056c125..4554e63 100644 --- a/modules/big-transfer/Main.qml +++ b/modules/big-transfer/Main.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2024 Tom Zander + * Copyright (C) 2024-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,6 @@ import Flowee.org.pay.bigtransfer; Mobile.Page { id: root headerText: qsTr("Wallet to Wallet") - property alias initialWallet: fromWalletSelector.startingAccount ColumnLayout { width: parent.width @@ -38,39 +37,66 @@ Mobile.Page { text: qsTr("Select two wallets to transfer funds simply, using anonimity preserving transactions.") } - Mobile.PageTitledBox { - title: qsTr("Spending Wallet") - - // TODO Popup with just the name - Mobile.AccountSelectorWidget { - id: fromWalletSelector - onSelectedAccountChanged: transferManager.fromAccount = selectedAccount; + Mobile.TextButton { + text: qsTr("Select Spending Wallet") + subtext: { + var from = transferManager.fromAccount; + if (from === null) + return ""; + return from.name } - GridLayout { - columns: 2 - columnSpacing: 10 - rowSpacing: 10 - Flowee.Label { - text: qsTr("Addresses") + ":" - } - Flowee.Label { - text: transferManager.addressCount - } - Flowee.Label { - text: qsTr("Coins") + ":" - } - Flowee.Label { - text: transferManager.coinCount - } + onClicked: fromWalletPicker.visible = true + SimpleWalletPicker { + id: fromWalletPicker + text: parent.text + accounts: portfolio.accounts + onSelectedChanged: transferManager.fromAccount = selected; + current: transferManager.fromAccount } } - Mobile.PageTitledBox { - title: qsTr("Destination Wallet") - // TODO Popup with just the name + GridLayout { + columns: 2 + columnSpacing: 10 + rowSpacing: 10 + enabled: transferManager.fromAccount !== null + Flowee.Label { + text: qsTr("Addresses") + ":" + } + Flowee.Label { + text: transferManager.addressCount + } + Flowee.Label { + text: qsTr("Coins") + ":" + } + Flowee.Label { + text: transferManager.coinCount + } + } + Mobile.TextButton { + text: qsTr("Destination Wallet") + subtext: { + var to = transferManager.toAccount; + if (to === null) + return ""; + return to.name + } // TODO add an entry "create new HD wallet" - Mobile.AccountSelectorWidget { - startingAccount: null - onSelectedAccountChanged: transferManager.toAccount = selectedAccount; + onClicked: toWalletPicker.visible = true; + SimpleWalletPicker { + id: toWalletPicker + text: parent.text + accounts: { + var list = []; + for (let wallet of portfolio.accounts) { + if (wallet === transferManager.fromAccount || !wallet.isHDWallet) + continue; + list.push(wallet); + } + return list; + } + + onSelectedChanged: transferManager.toAccount = selected; + current: transferManager.toAccount } } @@ -91,6 +117,7 @@ Mobile.Page { // data TransferManager { id: transferManager + fromAccount: portfolio.current } } } diff --git a/modules/big-transfer/SimpleWalletPicker.qml b/modules/big-transfer/SimpleWalletPicker.qml new file mode 100644 index 0000000..be0bbed --- /dev/null +++ b/modules/big-transfer/SimpleWalletPicker.qml @@ -0,0 +1,74 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2024-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick; +import QtQuick.Controls as QQC2; +import "../Flowee" as Flowee; + +Flowee.Dialog { + id: root + property var current: null + property var selected: null + property var accounts: [] + + contentComponent: ListView { + model: root.accounts + implicitWidth: 200 + implicitHeight: Math.min(model.length * 50, 600) + + delegate: Rectangle { + width: ListView.view.width + height: nameLabel.height + 20 + color: (index % 2) == 0 ? palette.base : palette.light + border.width: modelData === root.current ? 2 : 0 + border.color: mainWindow.floweeGreen + radius: 7 + + Flowee.ArrowPoint { + id: point + x: 1.3 + visible: root.current === modelData; + anchors.verticalCenter: parent.verticalCenter + color: parent.border.color + } + + Flowee.Label { + id: nameLabel + x: 10 + y: 10 + text: modelData.name + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.selected = modelData; + closeTimer.running = true; + } + } + } + } + standardButtons: QQC2.DialogButtonBox.NoButton + Timer { + id: closeTimer + interval: 300 + onTriggered: { + running = false; + root.close() + } + } +} diff --git a/modules/big-transfer/bigtransfer.qrc b/modules/big-transfer/bigtransfer.qrc index 4d4ced5..fac3951 100644 --- a/modules/big-transfer/bigtransfer.qrc +++ b/modules/big-transfer/bigtransfer.qrc @@ -2,5 +2,6 @@ Main.qml ShowPrepared.qml + SimpleWalletPicker.qml -- 2.54.0 From 337fb4d6e0bc36cebd40d798eaf0cc9d9f45dab4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Apr 2025 18:08:21 +0200 Subject: [PATCH 1318/1428] Fixlet for testnet4 --- guis/desktop/main.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 33726c0..0b75bbd 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -176,7 +176,7 @@ ApplicationWindow { text: { if (Pay.hideBalance && Pay.isMainChain) return Fiat.formattedPrice(100000000, Fiat.price) - if (!Fiat.hasPrice) + if (!Fiat.hasPrice && Pay.isMainChain) return "?" return Fiat.formattedPrice(totalBalance.value, Fiat.price) } -- 2.54.0 From e96a9fe129522b3c89fe21f6439ac1172e102b57 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Apr 2025 19:25:51 +0200 Subject: [PATCH 1319/1428] Fix sometimes not showing the right text When a new transaction is sent and edited directly in the backend we expect that the item is re-created and thus the on-initial- construction method to be called. But, QML caches and thus our smart solution doesn' work. --- guis/mobile/TransactionListItem.qml | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index a6cad0f..f4cc475 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -81,22 +81,19 @@ Item { return parent.height / 2 - height } elide: Text.ElideRight - - Component.onCompleted: fetchText(); - function fetchText() { + text: { var comment = model.comment if (comment !== "") - text = comment; + return comment; else if (model.isCoinbase) - text = qsTr("Miner Reward"); + return qsTr("Miner Reward"); else if (model.isFused) - text = qsTr("Fused"); + return qsTr("Fused"); else if (model.fundsIn === 0) - text = qsTr("Received"); + return qsTr("Received"); else if (isMoved) - text = qsTr("Moved"); - else - text = qsTr("Sent"); + return qsTr("Moved"); + return qsTr("Sent"); } } @@ -133,7 +130,7 @@ Item { if (editWidget.infoObject === null) editWidget.infoObject = portfolio.current.txInfo(model.walletIndex, editWidget); editWidget.infoObject.userComment = text; - commentLabel.fetchText(); + commentLabel.text = text; visible = false; } } -- 2.54.0 From 1ab360790030414d2d3dcec373b5746de837e7f7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 17 Apr 2025 20:30:53 +0200 Subject: [PATCH 1320/1428] Make notification manager aware of being visible Notifications should have the opportunity to skip the OS layer and simply be shown in-app instead in case the application has the users attention anyway. While knowing a notification should be sent to the system tray otherwise. --- src/FloweePay_android.cpp | 5 +++++ src/FloweePay_android_p.h | 1 + src/FloweePay_basic.cpp | 7 +++++-- src/NotificationManager.cpp | 7 ++++++- src/NotificationManager.h | 6 +++++- src/NotificationManager_android.cpp | 4 +--- src/NotificationManager_p_android.h | 1 + 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 6d48493..dd6c59f 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -186,6 +186,7 @@ void FloweePayPrivate::onAppHidden() m_freezeTimer.async_wait([this](const boost::system::error_code &error) { if (!error) handleFreeze(); }); + emit inForeground(false); } void FloweePayPrivate::onAppShown() @@ -208,6 +209,8 @@ void FloweePayPrivate::onAppShown() QTimer::singleShot(2000, this, &FloweePayPrivate::backgroundUpdaterToggled); } m_sleepStart = QDateTime(); + + emit inForeground(true); } void FloweePayPrivate::handleFreeze() @@ -291,6 +294,8 @@ void FloweePay::setupPlatform() return; } + connect (g_fpPriv, &FloweePayPrivate::inForeground, &m_notifications, &NotificationManager::setApplicationForeground, Qt::QueuedConnection); + // the color of the bar above the app can be set, we copy the expected background color // of the header. updateTitleBarColor(); diff --git a/src/FloweePay_android_p.h b/src/FloweePay_android_p.h index 1d27b86..1fccb46 100644 --- a/src/FloweePay_android_p.h +++ b/src/FloweePay_android_p.h @@ -34,6 +34,7 @@ public: signals: void airplaneModeCheckNeeded(); + void inForeground(bool yes); private slots: void checkAirplaneMode(); diff --git a/src/FloweePay_basic.cpp b/src/FloweePay_basic.cpp index 68b6caa..f5ef031 100644 --- a/src/FloweePay_basic.cpp +++ b/src/FloweePay_basic.cpp @@ -42,6 +42,7 @@ void FloweePay::setupPlatform() if (gui) { connect(gui, &QGuiApplication::applicationStateChanged, this, [=](Qt::ApplicationState state) { if (state == Qt::ApplicationHidden || state == Qt::ApplicationInactive) { + m_notifications.setApplicationForeground(false); if (g_lockTimer == nullptr) g_lockTimer = new boost::asio::system_timer(ioContext()); g_lockTimer->expires_after(std::chrono::minutes(10)); @@ -60,8 +61,10 @@ void FloweePay::setupPlatform() } }); } - else if (g_lockTimer) { - g_lockTimer->cancel(); + else if (state == Qt::ApplicationActive) { + m_notifications.setApplicationForeground(true); + if (g_lockTimer) + g_lockTimer->cancel(); } }); } diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 51299f8..310d214 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -45,6 +45,11 @@ void NotificationManager::headerSyncComplete() m_headerSyncComplete = true; } +void NotificationManager::setApplicationForeground(bool visible) +{ + m_inForeground = visible; +} + QString NotificationManager::describeCollated(int &txCount) const { const auto data = collatedData(); diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 0e996d7..d6073e7 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2022 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,12 +40,16 @@ public: // This is annoying and useless, so lets ignore it until we got a sync-complete. void headerSyncComplete(); +public slots: + void setApplicationForeground(bool visible); + signals: void newBlockMutedChanged(); private: bool m_headerSyncComplete = false; bool m_newBlockMuted = false; + bool m_inForeground = false; // to switch between in-app or by-OS notifications QString describeCollated(int &txCount) const; void requestNotificationPermissions(); diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index b8a1fa6..5895d7c 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -21,11 +21,9 @@ #include #include +#include #include -#if TARGET_OS_Android -# include constexpr const char *ClassName = "org/flowee/pay/PayNotifications"; -#endif NotificationManager::NotificationManager(QObject *parent) : QObject(parent), diff --git a/src/NotificationManager_p_android.h b/src/NotificationManager_p_android.h index 921be59..741b10e 100644 --- a/src/NotificationManager_p_android.h +++ b/src/NotificationManager_p_android.h @@ -51,6 +51,7 @@ private slots: private: NotificationManager * const q; + bool appVisible = false; }; -- 2.54.0 From a81267153f060132ef0f14c353332c14847beeb5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Apr 2025 18:47:41 +0200 Subject: [PATCH 1321/1428] Move placeholder text to live elsewhere On Android the placeholder text is very unfortunately positioned that most people will consider a bug. So we stop using that and instead put the text just above the actual widget, fading the text when the user starts typing. As this is on mobile, we also add a blinking fake cursor in the text field to make it super clear what the intention there is. The alternative would be to give focus to the field and have the real blinking cursor, but that would open the virtual keyboard and hide the big close button. So fake blinking cursor it is. --- guis/Flowee/BroadcastFeedback.qml | 50 ++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index bd1a7d3..a410911 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -192,16 +192,50 @@ QQC2.Control { Item { width: 1; height: 10} // spacer - TextField { - id: transactionComment + Item { + visible: root.processNote !== undefined anchors.horizontalCenter: parent.horizontalCenter width: Math.min(400, parent.width - 40); - color: "#26282a" - palette.base: statusLabel.color - onTotalTextChanged: root.processNote(totalText) - placeholderText: qsTr("Add a personal note") - placeholderTextColor: "#505050" - visible: root.processNote !== undefined + height: transactionCommentComment.height + transactionComment.height + + Label { + id: transactionCommentComment + color: statusLabel.color + opacity: transactionComment.totalText === "" ? 1 : 0 + text: qsTr("Add a personal note") + Behavior on opacity { NumberAnimation { } } + } + + TextField { + id: transactionComment + color: "#26282a" + palette.base: statusLabel.color + onTotalTextChanged: root.processNote(totalText) + width: parent.width + anchors.bottom: parent.bottom + + Rectangle { + property bool on: true + + x: 8 + width: 1.3 + height: parent.height * 0.6 + color: "black" + visible: transactionComment.totalText === "" && !transactionComment.activeFocus + anchors.verticalCenter: parent.verticalCenter + + Behavior on opacity { NumberAnimation { duration: 150 } } + Timer { + interval: 600 + running: parent.visible + repeat: true + onTriggered: { + parent.on = !parent.on; + parent.opacity = parent.on ? 1 : 0 + } + } + } + } } } -- 2.54.0 From 4fa68be6993404a4cd570ecfa3404e86900d48ce Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 18 Apr 2025 19:03:17 +0200 Subject: [PATCH 1322/1428] Make edit button UX a big better This makes the label a bit shorter to avoid it overlapping the fiat text in more cases. We also now place the edit widget on top of the pencil button to get as much space for editing as we can get. --- guis/mobile/TransactionListItem.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index f4cc475..4732c9b 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -72,7 +72,7 @@ Item { Flowee.Label { id: commentLabel anchors.right: price.visible ? price.left : price.right - anchors.rightMargin: 10 + anchors.rightMargin: 30 anchors.left: parent.left anchors.leftMargin: 72 y: { @@ -123,6 +123,7 @@ Item { anchors.left: parent.left anchors.leftMargin: 10 anchors.right: commentLabel.right + anchors.rightMargin: -25 // to go over the edit button visible: false focus: visible text: model.comment @@ -133,6 +134,7 @@ Item { commentLabel.text = text; visible = false; } + Component.onCompleted: { cursorPosition = 0; } } Flowee.Label { -- 2.54.0 From 2a713bcd45165399172d7a8732e14c130f473f3e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Apr 2025 17:24:10 +0200 Subject: [PATCH 1323/1428] Remove null flag enum value. --- src/WalletEnums.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 27b4e8c..55c22e9 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -46,7 +46,6 @@ public: Q_ENUM(StringType) enum Include { - IncludeNothing = 0, IncludeRejected = 1, IncludeUnconfirmed = 2, IncludeConfirmed = 4, -- 2.54.0 From da5ad4d9d08323aa11597916275fda28b0fb12be Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 19 Apr 2025 17:27:18 +0200 Subject: [PATCH 1324/1428] Improve notifications Notifications are now filtered better to avoid showing events on transactions that came in which are filtered from view. --- src/FloweePay.cpp | 28 ++++++++++++++++++++++----- src/FloweePay.h | 3 --- src/NotificationManager.cpp | 7 +------ src/NotificationManager.h | 2 +- src/NotificationManager_android.cpp | 19 +++++++++++++++--- src/NotificationManager_dbus.cpp | 7 ++++++- src/NotificationManager_dummy.cpp | 7 ++++++- src/NotificationManager_p_android.h | 3 ++- src/Wallet.cpp | 30 +++++++++++++++++++++++++++-- src/Wallet.h | 4 ++++ src/WalletHistoryModel.cpp | 14 +------------- 11 files changed, 88 insertions(+), 36 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 3cf4812..521b2c6 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -332,6 +332,29 @@ void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ifica if (me->privateMode() && (i == configs.end() || i->privateWallet)) return; + const auto wallets = FloweePay::instance()->wallets(); + for (auto *wallet : wallets) { + if (wallet->segment()->segmentId() == notification.privacySegment) { + // Skip notice if wallet is importing. + if (wallet->walletIsImporting()) + return; + + // filter based on wallet filters. + QFile in(QString::fromStdString((wallet->walletDir() / "history.conf").string())); + if (in.open(QIODevice::ReadOnly)) { + auto pool = Streaming::pool(in.size()); + in.read(pool->data(), in.size()); + + int flagData = 0; + pool->parse(in.size()).bind(1, &flagData); + QFlags flags = QFlags::fromInt(flagData); + if (!wallet->transactionMatch(flags, notification.walletTxId)) + return; + } + break; + } + } + me->p2pNet()->notifications().notifyNewTransaction(notification); } @@ -1011,11 +1034,6 @@ void FloweePay::setNotification(QObject *n) emit notificationChanged(); } -bool FloweePay::headless() const -{ - return m_headless; -} - FloweePay::UnlockingKeyboard FloweePay::unlockingKeyboard() const { return m_unlockingKeyboard; diff --git a/src/FloweePay.h b/src/FloweePay.h index 2f911bf..4301882 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -425,8 +425,6 @@ public: UnlockingKeyboard unlockingKeyboard() const; void setUnlockingKeyboard(UnlockingKeyboard newUnlockingKeyboard); - bool headless() const; - QObject *notification() const; void setNotification(QObject *newNotification); @@ -541,7 +539,6 @@ private: UnlockingKeyboard m_unlockingKeyboard = SmallNumbersKeyboard; bool m_loadingCompleted = false; // 'init()' completed bool m_loadCompleteEmitted = false; // avoid emitting this on every unlock - bool m_headless = false; bool m_darkSkin = true; bool m_skinFollowsPlatform = false; bool m_createStartWallet = false; diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 310d214..4cfd9c4 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -45,11 +45,6 @@ void NotificationManager::headerSyncComplete() m_headerSyncComplete = true; } -void NotificationManager::setApplicationForeground(bool visible) -{ - m_inForeground = visible; -} - QString NotificationManager::describeCollated(int &txCount) const { const auto data = collatedData(); @@ -62,7 +57,7 @@ QString NotificationManager::describeCollated(int &txCount) const for (const auto &item : data) { deposited += item.deposited; spent += item.spent; - txCount += item.txCount; + ++txCount; } const auto gained = deposited - spent; diff --git a/src/NotificationManager.h b/src/NotificationManager.h index d6073e7..1e9386e 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -41,6 +41,7 @@ public: void headerSyncComplete(); public slots: + /// visible is set to true if the app is active and not in the background void setApplicationForeground(bool visible); signals: @@ -49,7 +50,6 @@ signals: private: bool m_headerSyncComplete = false; bool m_newBlockMuted = false; - bool m_inForeground = false; // to switch between in-app or by-OS notifications QString describeCollated(int &txCount) const; void requestNotificationPermissions(); diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 5895d7c..4e6b398 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,6 +55,11 @@ void NotificationManager::requestNotificationPermissions() japp.callObjectMethod("requestNotificationPermission", "()V"); } +void NotificationManager::setApplicationForeground(bool visible) +{ + d->appVisible = visible; +} + // --------------------------------------------------------- NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) @@ -79,6 +84,13 @@ void NotificationManagerPrivate::segmentUpdated_fromNetwork() // on the local thread. void NotificationManagerPrivate::newBlockSeen(int blockHeight) { + /* + * TODO + * Have some sort of list of transactions recently sent by the user. + * Then when a block comes in, create a notification of the transaction + * getting another confirmation. Stop at 3. + */ + QString message; if (FloweePay::instance()->chain() == P2PNet::MainChain) message = tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); @@ -99,7 +111,7 @@ void NotificationManagerPrivate::newBlockSeen(int blockHeight) // on the local thread. void NotificationManagerPrivate::segmentUpdated() { - if (FloweePay::instance()->headless()) + if (!appVisible) createAndroidTxNotification(); else createQmlNotification(); @@ -121,12 +133,13 @@ void NotificationManagerPrivate::createQmlNotification() connect (n, &QObject::destroyed, this, [=]{ q->flushCollate(); }); - // TODO does this not need a title? n->setMessage(message); FloweePay::instance()->setNotification(n); } +// --------------------------------------------------------- + Notification::Notification(QObject *parent) : QObject(parent) { diff --git a/src/NotificationManager_dbus.cpp b/src/NotificationManager_dbus.cpp index c99dd63..577c801 100644 --- a/src/NotificationManager_dbus.cpp +++ b/src/NotificationManager_dbus.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,6 +49,11 @@ void NotificationManager::requestNotificationPermissions() { } +void NotificationManager::setApplicationForeground(bool visible) +{ + Q_UNUSED(visible); +} + // ///////////////////////////////// NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) diff --git a/src/NotificationManager_dummy.cpp b/src/NotificationManager_dummy.cpp index 1fefc69..f8017d2 100644 --- a/src/NotificationManager_dummy.cpp +++ b/src/NotificationManager_dummy.cpp @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,3 +38,8 @@ void NotificationManager::segmentUpdated(const P2PNet::Notification&) void NotificationManager::requestNotificationPermissions() { } + +void NotificationManager::setApplicationForeground(bool visible) +{ +} + diff --git a/src/NotificationManager_p_android.h b/src/NotificationManager_p_android.h index 741b10e..30a8ebd 100644 --- a/src/NotificationManager_p_android.h +++ b/src/NotificationManager_p_android.h @@ -39,6 +39,8 @@ public: void createAndroidTxNotification(); void createQmlNotification(); + bool appVisible = false; // to switch between in-app or by-OS notifications + signals: // emitted by notifyNewBlock_fromNetwork, in order to move the call to the Qt thread. void newBlockSeenSignal(int blockHeight); @@ -51,7 +53,6 @@ private slots: private: NotificationManager * const q; - bool appVisible = false; }; diff --git a/src/Wallet.cpp b/src/Wallet.cpp index dfdc28d..62ceb41 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -117,8 +117,6 @@ Wallet::~Wallet() Wallet::WalletTransaction Wallet::createWalletTransactionFromTx(const Tx &tx, const uint256 &txid, std::map &types, P2PNet::Notification *notifier) const { - if (notifier) - notifier->txCount++; WalletTransaction wtx; wtx.txid = txid; @@ -357,6 +355,7 @@ void Wallet::newTransaction(const Tx &tx) int firstNewTransaction; P2PNet::Notification notification; notification.privacySegment = int(m_segment->segmentId()); + notification.walletTxId = m_nextWalletTransactionId; { QMutexLocker locker(&m_lock); if (m_walletIsImporting) { @@ -480,6 +479,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: continue; walletTransactionId = oldTx->second; } + notification.walletTxId = walletTransactionId; if (insertBeforeData.get() == nullptr) { // remove all transactions after this block, in order to replay them at the end // of this insert. @@ -748,6 +748,32 @@ void Wallet::setTransactionComment(int txIndex, const QString &comment) } } +bool Wallet::transactionMatch(const QFlags &includeFlags, int txIndex) const +{ + QMutexLocker locker(&m_lock); + auto iter = m_walletTransactions.find(txIndex); + if (iter == m_walletTransactions.end()) + return false; + const auto &wtx = iter->second; + + if (!includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeCFs) && wtx.isCashFusionTx) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeRejected) && wtx.isRejected()) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeConfirmed) && !wtx.isUnconfirmed()) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeSentTransactions) && !wtx.inputToWTX.empty()) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions) && wtx.inputToWTX.empty()) + return false; + if (!includeFlags.testFlag(WalletEnums::IncludeTxWithoutComment) && wtx.userComment.isEmpty()) + return false; + + return true; +} + std::map Wallet::walletSecrets() const { QMutexLocker locker(&m_lock); diff --git a/src/Wallet.h b/src/Wallet.h index 982e5c7..bce4180 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -18,6 +18,8 @@ #ifndef FLOWEE_WALLET_H #define FLOWEE_WALLET_H +#include "WalletEnums.h" + #include #include @@ -310,6 +312,8 @@ public: */ void setTransactionComment(int txIndex, const QString &comment); + bool transactionMatch(const QFlags &includeFlags, int txIndex) const; + struct WalletSecret { PrivateKey privKey; std::vector encryptedPrivKey; diff --git a/src/WalletHistoryModel.cpp b/src/WalletHistoryModel.cpp index 7a30327..856baba 100644 --- a/src/WalletHistoryModel.cpp +++ b/src/WalletHistoryModel.cpp @@ -476,19 +476,7 @@ void WalletHistoryModel::savePreferences() bool WalletHistoryModel::filterTransaction(int txIndex, const Wallet::WalletTransaction &wtx) { assert(QThread::currentThread() == thread()); - if (!m_includeFlags.testFlag(WalletEnums::IncludeUnconfirmed) && wtx.isUnconfirmed()) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeCFs) && wtx.isCashFusionTx) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeRejected) && wtx.isRejected()) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeConfirmed) && !wtx.isUnconfirmed()) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeSentTransactions) && !wtx.inputToWTX.empty()) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeReceivedTransactions) && wtx.inputToWTX.empty()) - return false; - if (!m_includeFlags.testFlag(WalletEnums::IncludeTxWithoutComment) && wtx.userComment.isEmpty()) + if (!m_wallet->transactionMatch(m_includeFlags, txIndex)) return false; if (!m_filterStringParsed) { -- 2.54.0 From 6049d942e59a1ea2a35a7260cc9efbf8043f670f Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 21 Apr 2025 17:37:53 +0200 Subject: [PATCH 1325/1428] Ensure the transaction keeps broadcasting If the user closes the UI before the broadcast has completed, we would stop broadcasting. Which is unfortunate and not what users expect. As such we simply hold on to the object for a while (10 min) before we delete it when it is quite likely already sent. --- modules/big-transfer/QMLTransferManager.cpp | 2 +- modules/send-sweep/QMLSweepHandler.cpp | 2 +- src/FloweePay.cpp | 13 +++++++++++++ src/FloweePay.h | 3 +++ src/Payment.cpp | 2 +- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index cd409ee..a65fb9a 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -132,7 +132,7 @@ void QMLTransferManager::send(QObject *previewTx) toWallet->setTransactionComment(tx, tr("Migrated Coin")); // and broadcast it. auto txInfo = std::make_shared(m_toAccount->wallet(), tx); - FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(txInfo); + FloweePay::instance()->broadcastTransaction(txInfo); // the txInfo is a sharedPtr, we need to store it somewhere to not get deleted // when it goes out of scope at the end of this method. data->setFinalTx(txInfo); diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index d2fc871..4a40647 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -162,7 +162,7 @@ void QMLSweepHandler::markUserApproved() m_infoObject = std::make_shared(m_account->wallet(), tx); connect(m_infoObject.get(), SIGNAL(broadcastStatusChanged()), this, SIGNAL(broadcastStatusChanged())); - FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); + FloweePay::instance()->broadcastTransaction(m_infoObject); m_txBroadcastStarted = true; emit broadcastStatusChanged(); diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 521b2c6..9ab94c9 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -21,6 +21,7 @@ #include "AddressInfo.h" #include "PriceDataProvider.h" #include "IndexerServices.h" +#include "TxInfoObject.h" #include #include @@ -981,6 +982,18 @@ void FloweePay::setBackgroundUpdateInterval(int hours) appConfig.setValue(BG_INTERVAL, hours); } +void FloweePay::broadcastTransaction(const std::shared_ptr &txOwner) +{ + p2pNet()->connectionManager().broadcastTransaction(txOwner); + // we keep a copy around since broadcast stops when the object is deleted. + // This way we ensure that if the user presses 'close' very quickly, the + // most likley unintended effect of deleting the "txOwner" object is not + // the cancelling of the broadcast. + QTimer::singleShot(10 * 60 * 1000, txOwner.get(), [txOwner]() { + logDebug() << "Dropping broadcast object"; + }); +} + // notice, setter is in platform specific file. bool FloweePay::backgroundUpdates() const { diff --git a/src/FloweePay.h b/src/FloweePay.h index 4301882..21d6511 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -43,6 +43,7 @@ class KeyId; class PriceDataProvider; class CameraController; class IndexerServices; +class TxInfoObject; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); @@ -445,6 +446,8 @@ public: int backgroundUpdateInterval() const; void setBackgroundUpdateInterval(int hours); + void broadcastTransaction(const std::shared_ptr &txOwner); + #ifdef TARGET_OS_Android // save what we can, the app is about to hide. void softSave(); diff --git a/src/Payment.cpp b/src/Payment.cpp index d9ca1b6..ea8351c 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -414,7 +414,7 @@ void Payment::broadcast() m_infoObject = std::make_shared(m_wallet, m_tx); connect(m_infoObject.get(), SIGNAL(broadcastStatusChanged()), this, SIGNAL(broadcastStatusChanged())); // notice that rejections are automatically forwarded to the wallet from the TxInfoObject - FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject); + FloweePay::instance()->broadcastTransaction(m_infoObject); m_txBroadcastStarted = true; emit broadcastStatusChanged(); } -- 2.54.0 From a1b18d3f0299ff8d0d4833af13faee8c1fdd9561 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Apr 2025 18:34:01 +0200 Subject: [PATCH 1326/1428] Replace block notifications with confirmations We remove the block notification feature, as that was nice but useless. This instead introduces a way for a transaction we created to be marked as needing monitoring and when a block comes in we create a notification explaining it has been mined. --- android/java/org/flowee/pay/MainActivity.java | 3 +- .../java/org/flowee/pay/PayNotifications.java | 121 +++++------------- guis/desktop/SettingsPane.qml | 12 -- guis/mobile/Settings.qml | 6 - src/FloweePay.cpp | 13 +- src/FloweePay.h | 8 -- src/FloweePay_android.cpp | 15 ++- src/FloweePay_android_p.h | 3 +- src/NotificationManager.cpp | 85 +++++++++--- src/NotificationManager.h | 27 ++-- src/NotificationManager_android.cpp | 52 +++----- src/NotificationManager_dbus.cpp | 53 ++++---- src/NotificationManager_dummy.cpp | 1 + src/NotificationManager_p_android.h | 6 +- src/NotificationManager_p_dbus.h | 8 +- src/main_utils_android.cpp | 2 +- 16 files changed, 187 insertions(+), 228 deletions(-) diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 1c26087..19a4335 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -162,8 +162,7 @@ public class MainActivity extends QtActivity public void requestNotificationPermission() { - PayNotifications me = PayNotifications.instance(null); - if (me.areNotificationsEnabled()) + if (PayNotifications.areNotificationsEnabled()) return; String perms[] = new String[1]; diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index b1956d0..255321d 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -30,121 +30,64 @@ import org.flowee.pay.test.R; public class PayNotifications { - private static final int BlockNotificationId = 9839874; + private static final int ConfirmedNotificationId = 6134828; private static PayNotifications g_singleton = null; - public static PayNotifications instance(Context context) + private Notification.Builder m_confirmedMessageBuilder = null; + private NotificationManager m_notificationManager = null; + private String m_confirmedChannelId = "unset"; + + + public static PayNotifications instance() { - if (g_singleton == null) - g_singleton = new PayNotifications(context); return g_singleton; } - public boolean areNotificationsEnabled() + public static boolean areNotificationsEnabled() { - return instance(null).m_notificationManager.areNotificationsEnabled(); + return instance().m_notificationManager.areNotificationsEnabled(); } - - private Notification.Builder m_blockMessageBuilder = null; - private NotificationManager m_notificationManager = null; - private String m_blockChannelId = null; - private String m_bgSyncChannelId = null; - private Vector m_blockFoundMessages = new Vector(); - - private int m_walletMessageId = 1; - private String m_walletChannelId = null; - + // To be called before first usage. public static void setup(Context context) { - // create the signleton as it creates our channels - PayNotifications.instance(context); + // create the singleton as it creates our channels + if (g_singleton == null) + g_singleton = new PayNotifications(context); } - public static void notifyBlock(String message) - { - PayNotifications.instance(null).notifyBlock_priv(message); - } + // constructor public PayNotifications(Context context) { // see example here on how to make these translatable: // https://developer.android.com/develop/ui/views/notifications/build-notification#java - NotificationChannel newBlocksChannel = new NotificationChannel("blocks", "New Blocks", + NotificationChannel confirmedChannel = new NotificationChannel("confs", "Mined", NotificationManager.IMPORTANCE_DEFAULT); m_notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - m_notificationManager.createNotificationChannel(newBlocksChannel); - m_blockChannelId = newBlocksChannel.getId(); - m_blockMessageBuilder = new Notification.Builder(context, m_blockChannelId); - - NotificationChannel bgChannel = new NotificationChannel("bgsync", "Sync", - NotificationManager.IMPORTANCE_LOW); - m_notificationManager.createNotificationChannel(bgChannel); - m_bgSyncChannelId = bgChannel.getId(); + m_notificationManager.createNotificationChannel(confirmedChannel); + m_confirmedChannelId = confirmedChannel.getId(); + m_confirmedMessageBuilder = new Notification.Builder(context, m_confirmedChannelId); } - private void notifyBlock_priv(String message) + // static methods to be called from C++ + public static void setTxConfirmedNotification(String title, String message) { - // We split this into the title and the message since that is how the anatomy of android - // notifications work. - int dot = message.indexOf('.'); - String title = ""; - if (dot > 0) { - title = message.substring(0, dot + 1); - message = message.substring(dot + 1); - message = message.trim(); // likely a space behind the dot. - } + g_singleton.setTxConfirmedNotification_priv(title, message); + } - // We append the new text to the active notification if exists. - StatusBarNotification[] notifications = m_notificationManager.getActiveNotifications(); - boolean stillActive = false; - for (StatusBarNotification n : notifications) { - if (n.getId() == BlockNotificationId) { - stillActive = true; - break; - } - } - if (stillActive) { - m_blockFoundMessages.add(0, message); - int lines = 0; - message = null; - ListIterator iter = m_blockFoundMessages.listIterator(); - while (iter.hasNext()) { - String m = iter.next(); - if (lines++ < 6) { - if (message == null) - message = m; - else - message += "\n" + m; - } - else { // lets keep only some - iter.remove(); - } - } - } else { - m_blockFoundMessages.clear(); - m_blockFoundMessages.add(message); - } + + private void setTxConfirmedNotification_priv(String title, String message) + { // https://developer.android.com/reference/android/app/Notification.Builder?hl=en - m_blockMessageBuilder.setSmallIcon(R.drawable.icon) - .setContentTitle(title) - .setContentText(message) - .setColor(0xA0F87) // flowee blue - .setAutoCancel(true); + m_confirmedMessageBuilder.setSmallIcon(R.drawable.icon) + .setContentTitle(title) + .setContentText(message) + .setColor(0xA0F87) // flowee blue + .setAutoCancel(true); - // we always use the one BlockNotificationId for this as we never want to have more than one + // we always use the one ConfirmedNotificationId for this as we never want to have more than one // notification in the air of this type. - m_notificationManager.notify(BlockNotificationId, m_blockMessageBuilder.build()); - } - - public Notification buildBackgroundNotification(Context context) - { - Notification.Builder builder = new Notification.Builder(context, m_bgSyncChannelId); - builder.setContentTitle("Background Updator") - .setSmallIcon(R.drawable.icon) - .setContentText("Wallet sync") - .setColor(0xA0F87) // flowee blue - .setOngoing(true); - return builder.build(); + m_notificationManager.notify(ConfirmedNotificationId, m_confirmedMessageBuilder.build()); } } diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index 67ab172..727c0ad 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -108,18 +108,6 @@ Item { visible: Pay.isMainChain } - Flowee.CheckBox { - id: showBlockNotificationsChooser - Layout.alignment: Qt.AlignRight - checked: !Pay.newBlockMuted - onCheckedChanged: Pay.newBlockMuted = !checked; - } - Flowee.CheckBoxLabel { - Layout.columnSpan: 2 - buddy: showBlockNotificationsChooser - text: qsTr("Show Block Notifications") - toolTipText: qsTr("When a new block is mined, Flowee Pay shows a desktop notification") - } Flowee.CheckBox { id: darkSkinChooser Layout.alignment: Qt.AlignRight diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index d6355cb..482b1a9 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -190,12 +190,6 @@ Page { PageTitledBox { title: qsTr("Notifications") - Flowee.CheckBox { - width: parent.width - text: qsTr("On new block found") - checked: !Pay.newBlockMuted - onCheckedChanged: Pay.newBlockMuted = !checked - } } } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 9ab94c9..fe66421 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -261,7 +261,6 @@ FloweePay::FloweePay() // forward signals connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); - connect (&m_notifications, SIGNAL(newBlockMutedChanged()), this, SIGNAL(newBlockMutedChanged())); connect (this, &FloweePay::startSaveData_priv, this, [=]() { // As Qt does not allow starting a timer from any thread, we first use a signal // to move the request to save to the main thread, after which we schedule it with @@ -992,6 +991,8 @@ void FloweePay::broadcastTransaction(const std::shared_ptr &txOwne QTimer::singleShot(10 * 60 * 1000, txOwner.get(), [txOwner]() { logDebug() << "Dropping broadcast object"; }); + + m_notifications.addConfirmationTx(txOwner->privSegment(), txOwner->txIndex()); } // notice, setter is in platform specific file. @@ -1186,16 +1187,6 @@ void FloweePay::startNet() p2pNet()->start(); // lets go! } -bool FloweePay::newBlockMuted() const -{ - return m_notifications.newBlockMuted(); -} - -void FloweePay::setNewBlockMuted(bool mute) -{ - m_notifications.setNewBlockMuted(mute); -} - CameraController *FloweePay::cameraController() { return m_cameraController; diff --git a/src/FloweePay.h b/src/FloweePay.h index 21d6511..bf273aa 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -76,7 +76,6 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) // notifications Q_PROPERTY(QObject *notification READ notification WRITE setNotification NOTIFY notificationChanged FINAL) - Q_PROPERTY(bool newBlockMuted READ newBlockMuted WRITE setNewBlockMuted NOTIFY newBlockMutedChanged) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) Q_PROPERTY(bool offline READ isOffline CONSTANT) Q_PROPERTY(bool deviceOffline READ deviceOffline WRITE setDeviceOffline NOTIFY deviceOfflineChanged FINAL) @@ -393,11 +392,6 @@ public: */ void startNet(); - /// If true, no notifications about new blocks will be shown - bool newBlockMuted() const; - /// If true, no notifications about new blocks will be shown - void setNewBlockMuted(bool mute); - // return the CameraController previously set on this app singleton CameraController *cameraController(); // set the cameraController for the singleton to take ownership of. @@ -475,7 +469,6 @@ signals: void expectedChainHeightChanged(); void dspTimeoutChanged(); void hideBalanceChanged(); - void newBlockMutedChanged(); void fontScalingChanged(); void activityShowsBchChanged(); void totalBalanceConfigChanged(); @@ -489,7 +482,6 @@ signals: void deviceOfflineChanged(); void backgroundUpdatesChanged(); void backgroundUpdateIntervalChanged(); - void mnemonicProposalsChanged(); private slots: diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index dd6c59f..f241f46 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -72,9 +72,10 @@ void JNI_setCameraPermission(JNIEnv *env, jobject thiz, jboolean allowed) FloweePay::instance()->cameraController()->setCameraPermission(allowed == JNI_TRUE); } -FloweePayPrivate::FloweePayPrivate(FloweePay *parent) - : QObject(parent), - m_parent(parent), +FloweePayPrivate::FloweePayPrivate(NotificationManager *nm, FloweePay *qq) + : QObject(qq), + m_parent(qq), + m_notifications(nm), m_freezeTimer(m_parent->ioContext()), m_checkOfflineTimer(new QTimer(this)), m_updateBackgroundTimer(nullptr) @@ -82,6 +83,8 @@ FloweePayPrivate::FloweePayPrivate(FloweePay *parent) m_checkOfflineTimer->setTimerType(Qt::VeryCoarseTimer); m_checkOfflineTimer->setInterval(7000); +// nm->requestNotificationPermissions(); // TODO remove + connect (this, &FloweePayPrivate::airplaneModeCheckNeeded, this, &FloweePayPrivate::checkAirplaneMode, Qt::QueuedConnection); connect (m_checkOfflineTimer, &QTimer::timeout, @@ -219,9 +222,11 @@ void FloweePayPrivate::handleFreeze() const jboolean powerSaveOn = QJniObject(QNativeInterface::QAndroidApplication::context()) .callMethod("isPowerSaveMode", "()Z"); if (powerSaveOn == JNI_FALSE) { + if (m_notifications->isMonitoringConfirmations()) + return; for (const auto &wallet : wallets) { assert(wallet->segment()); - if (wallet->walletIsImporting()) { // allow running in background + if (wallet->walletIsImporting()) { // ok, important task. Let's allow running in background. // check again if it is still running in a while. m_freezeTimer.expires_after(std::chrono::seconds(100)); m_freezeTimer.async_wait([this](const boost::system::error_code &error) { @@ -287,7 +292,7 @@ void FloweePay::setupPlatform() assert(QThread::currentThread() == thread()); // next line can't make timers otherwise assert(!m_offline); // not available on Android - g_fpPriv = new FloweePayPrivate(this); + g_fpPriv = new FloweePayPrivate(&m_notifications, this); if (qobject_cast(QCoreApplication::instance()) == nullptr) { // this is headless. g_fpPriv->checkOnline(); diff --git a/src/FloweePay_android_p.h b/src/FloweePay_android_p.h index 1fccb46..e18b480 100644 --- a/src/FloweePay_android_p.h +++ b/src/FloweePay_android_p.h @@ -25,7 +25,7 @@ class QTimer; class FloweePayPrivate : public QObject { Q_OBJECT public: - explicit FloweePayPrivate(FloweePay *parent = nullptr); + explicit FloweePayPrivate(NotificationManager *nm, FloweePay *qq); void airplaneModeToggled(); void checkOnline(); @@ -52,6 +52,7 @@ private: void handleFreeze(); FloweePay * const m_parent; + NotificationManager * const m_notifications; AppState m_appState = AppHidden; QDateTime m_sleepStart; diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 4cfd9c4..4a459cb 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -18,33 +18,26 @@ #include "FloweePay.h" #include "NotificationManager.h" #include "PriceDataProvider.h" +#include "Wallet.h" #include #include -bool NotificationManager::newBlockMuted() const -{ - return m_newBlockMuted; -} - -void NotificationManager::setNewBlockMuted(bool mute) -{ - if (m_newBlockMuted == mute) - return; - m_newBlockMuted = mute; - QSettings appConfig; - appConfig.setValue(KEY_MUTE, m_newBlockMuted); - emit newBlockMutedChanged(); - - if (!mute) - requestNotificationPermissions(); -} - void NotificationManager::headerSyncComplete() { m_headerSyncComplete = true; } +void NotificationManager::addConfirmationTx(uint16_t segmentId, int txIndex) +{ + m_confirmationTransactions.push_back({ segmentId, txIndex }); +} + +bool NotificationManager::isMonitoringConfirmations() const +{ + return !m_confirmationTransactions.empty(); +} + QString NotificationManager::describeCollated(int &txCount) const { const auto data = collatedData(); @@ -81,3 +74,59 @@ QString NotificationManager::describeCollated(int &txCount) const return tr("A payment of %1 has been sent").arg(gainedStr.mid(1)); return tr("%1 new transactions found (%2)", "", txCount).arg(txCount).arg(gainedStr); } + +QString NotificationManager::describeConfirmations() +{ + /* + * so, here the idea is that if a user has sent a transaction recently, we give a notification + * when it got confirmed. + * There are various forms for this: + * 1. your transaction(s) got mined + * -> also when some got more then one confirmation. + * 2. your transaction(s) got %1 confirmations. + * -> when all of them were mined in the same block. + * 3. your transaction(s) got another confirmation. + * -> otherwise + */ + int txCount = 0; + bool firstMine = true; + int numConfirmations = 0; // set to -1 if not all the same. + + auto *app = FloweePay::instance(); + auto i = m_confirmationTransactions.begin(); + while (i != m_confirmationTransactions.end()) { + const auto &txInfo = *i; + int height = -3; + for (auto wallet : app->wallets()) { + if (txInfo.segmentId == wallet->segment()->segmentId()) { + height = wallet->transactionMined(txInfo.txIndex); + break; + } + } + if (height > 0) { + int confirmations = app->headerChainHeight() - height + 1; + if (confirmations > 1) + firstMine = false; + if (numConfirmations == 0) + numConfirmations = confirmations; + else if (numConfirmations != confirmations) + numConfirmations = -1; + ++txCount; + + if (confirmations >= 3) { + i = m_confirmationTransactions.erase(i); + continue; + } + } + ++i; + } + + if (txCount < 1) + return QString(); + if (firstMine) + return tr("Your transaction got mined", nullptr, txCount); + if (numConfirmations > 0) + return tr("Your transaction got %1 confirmations", nullptr, txCount).arg(numConfirmations); + return tr("Your transaction got another confirmation", nullptr, txCount); +} + diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 1e9386e..076bf9e 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -22,6 +22,7 @@ #include class NotificationManagerPrivate; +class TxInfoObject; class NotificationManager : public QObject, public NotificationListener { @@ -32,29 +33,37 @@ public: void notifyNewBlock(const P2PNet::Notification ¬ification) override; void segmentUpdated(const P2PNet::Notification ¬ification) override; - bool newBlockMuted() const; - void setNewBlockMuted(bool mute); - // When Flowee Pay first starts it synchronizes with the network // and we get a notifyNewBlock call almost every time. // This is annoying and useless, so lets ignore it until we got a sync-complete. void headerSyncComplete(); + // add transaction details of a transaction we want to follow and give + // number of confirmations notifications on. + void addConfirmationTx(uint16_t segmentId, int txIndex); + + /// returns true if we expect notifications on new block. + // Items added using addConfirmationTx(), until some confirmations later, make this return true. + bool isMonitoringConfirmations() const; + void requestNotificationPermissions(); + public slots: /// visible is set to true if the app is active and not in the background void setApplicationForeground(bool visible); -signals: - void newBlockMutedChanged(); - private: bool m_headerSyncComplete = false; - bool m_newBlockMuted = false; QString describeCollated(int &txCount) const; - void requestNotificationPermissions(); - static constexpr const char *KEY_MUTE = "notificationNewblockMute"; + // (just) after a block has been mined, this will return a nice text. + QString describeConfirmations(); + struct ConfirmationTxInfo { + uint16_t segmentId = 0; + int txIndex = -1; + }; + std::vector m_confirmationTransactions; + friend class NotificationManagerPrivate; NotificationManagerPrivate *d; }; diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 4e6b398..a9ea49d 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -23,6 +23,7 @@ #include #include #include + constexpr const char *ClassName = "org/flowee/pay/PayNotifications"; NotificationManager::NotificationManager(QObject *parent) @@ -30,18 +31,13 @@ NotificationManager::NotificationManager(QObject *parent) d(new NotificationManagerPrivate(this)) { setCollation(true); - QSettings appConfig; - m_newBlockMuted = appConfig.value(KEY_MUTE, true).toBool(); } -void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) +void NotificationManager::notifyNewBlock(const P2PNet::Notification &) { - if (m_newBlockMuted) - return; if (!m_headerSyncComplete) return; - - d->newBlockSeen_fromNetwork(notification.blockHeight); + d->newBlockSeen_fromNetwork(); } void NotificationManager::segmentUpdated(const P2PNet::Notification&) @@ -71,9 +67,9 @@ NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) this, &NotificationManagerPrivate::segmentUpdated, Qt::QueuedConnection); } -void NotificationManagerPrivate::newBlockSeen_fromNetwork(int height) +void NotificationManagerPrivate::newBlockSeen_fromNetwork() { - emit newBlockSeenSignal(height); + emit newBlockSeenSignal(); } void NotificationManagerPrivate::segmentUpdated_fromNetwork() @@ -82,30 +78,22 @@ void NotificationManagerPrivate::segmentUpdated_fromNetwork() } // on the local thread. -void NotificationManagerPrivate::newBlockSeen(int blockHeight) +void NotificationManagerPrivate::newBlockSeen() { - /* - * TODO - * Have some sort of list of transactions recently sent by the user. - * Then when a block comes in, create a notification of the transaction - * getting another confirmation. Stop at 3. - */ - - QString message; - if (FloweePay::instance()->chain() == P2PNet::MainChain) - message = tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); - else - message = tr("tBCH (testnet4) block mined: %1").arg(blockHeight); - - auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { - QJniEnvironment env; - auto jmessage = QJniObject::fromString(message); - jclass notifications = env.findClass(ClassName); - QJniObject::callStaticMethod(notifications, "notifyBlock", - "(Ljava/lang/String;)V", jmessage); - }); - // The java side will handle everything from here, so we just ignore the task. - Q_UNUSED(task); + auto message = q->describeConfirmations(); + if (!message.isEmpty()) { + QString title = q->tr("New Block Found"); + auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([title, message]() { + QJniEnvironment env; + auto jtitle = QJniObject::fromString(title); + auto jmessage = QJniObject::fromString(message); + jclass notifications = env.findClass(ClassName); + QJniObject::callStaticMethod(ClassName, "setTxConfirmedNotification", + "(Ljava/lang/String;Ljava/lang/String;)V", jtitle, jmessage); + }); + // The java side will handle everything from here, so we just ignore the task. + Q_UNUSED(task); + } } // on the local thread. diff --git a/src/NotificationManager_dbus.cpp b/src/NotificationManager_dbus.cpp index 577c801..90b451a 100644 --- a/src/NotificationManager_dbus.cpp +++ b/src/NotificationManager_dbus.cpp @@ -18,6 +18,7 @@ #include "NotificationManager.h" #include "NotificationManager_p_dbus.h" #include "FloweePay.h" +#include "Wallet.h" #include @@ -31,13 +32,11 @@ NotificationManager::NotificationManager(QObject *parent) d(new NotificationManagerPrivate(this)) { setCollation(true); - QSettings appConfig; - m_newBlockMuted = appConfig.value(KEY_MUTE, false).toBool(); } -void NotificationManager::notifyNewBlock(const P2PNet::Notification ¬ification) +void NotificationManager::notifyNewBlock(const P2PNet::Notification&) { - d->newBlockSeen_fromNetwork(notification.blockHeight); + d->newBlockSeen_fromNetwork(); } void NotificationManager::segmentUpdated(const P2PNet::Notification&) @@ -80,9 +79,9 @@ NotificationManagerPrivate::NotificationManagerPrivate(NotificationManager *qq) this, &NotificationManagerPrivate::segmentUpdated, Qt::QueuedConnection); } -void NotificationManagerPrivate::newBlockSeen_fromNetwork(int height) +void NotificationManagerPrivate::newBlockSeen_fromNetwork() { - emit newBlockSeenSignal(height); + emit newBlockSeenSignal(); } void NotificationManagerPrivate::segmentUpdated_fromNetwork() @@ -90,32 +89,31 @@ void NotificationManagerPrivate::segmentUpdated_fromNetwork() emit segmentUpdatedSignal(); } -void NotificationManagerPrivate::newBlockSeen(int blockHeight) +void NotificationManagerPrivate::newBlockSeen() { assert(QThread::currentThread() == thread()); - if (q->m_newBlockMuted) - return; if (!q->m_headerSyncComplete) return; auto iface = remote(); if (!iface->isValid()) return; - QVariantList args; - args << QVariant("Flowee Pay"); // app-name - args << QVariant(m_blockNotificationId); // replaces-id - args << QString(); // app_icon (not needed since we say which desktop file we are) - args << QString(); // body-text - if (FloweePay::instance()->chain() == P2PNet::MainChain) - args << tr("Bitcoin Cash block mined. Height: %1").arg(blockHeight); // summary text - else - args << tr("tBCH (testnet4) block mined: %1").arg(blockHeight); // summary text - QStringList actions; // actions - actions << "mute" << tr("Mute"); - args << actions; - args << m_newBlockHints; - args << -1; // timeout (ms) -1 means to let the server decide - if (!iface->callWithCallback("Notify", args, this, - SLOT(newBlockNotificationShown(uint)))) { - logWarning() << "dbus down, can't show notifications"; + auto message = q->describeConfirmations(); + if (!message.isEmpty()) { + QVariantList args; + args << QVariant("Flowee Pay"); // app-name + args << QVariant(m_blockNotificationId); // replaces-id + args << QString(); // app_icon (not needed since we say which desktop file we are) + args << QString(); // body-text + args << message; + QStringList actions; // actions + actions << "mute" << tr("Mute"); + args << actions; + args << m_newBlockHints; + args << -1; // timeout (ms) -1 means to let the server decide + if (!iface->callWithCallback("Notify", args, this, + SLOT(newBlockNotificationShown(uint)))) { + logWarning() << "dbus down, can't show notifications"; + } + } } @@ -185,11 +183,12 @@ void NotificationManagerPrivate::walletUpdateNotificationShown(uint id) void NotificationManagerPrivate::actionInvoked(uint, const QString &actionKey) { if (actionKey == "mute") - q->setNewBlockMuted(true); + q->m_confirmationTransactions.clear(); } void NotificationManagerPrivate::notificationClosed(uint32_t id, uint32_t reason) { + Q_UNUSED(reason); if (m_blockNotificationId == id) { m_blockNotificationId = 0; } diff --git a/src/NotificationManager_dummy.cpp b/src/NotificationManager_dummy.cpp index f8017d2..082e61f 100644 --- a/src/NotificationManager_dummy.cpp +++ b/src/NotificationManager_dummy.cpp @@ -29,6 +29,7 @@ NotificationManager::NotificationManager(QObject *parent) void NotificationManager::notifyNewBlock(const P2PNet::Notification&) { + m_confirmationTransactions.clear(); } void NotificationManager::segmentUpdated(const P2PNet::Notification&) diff --git a/src/NotificationManager_p_android.h b/src/NotificationManager_p_android.h index 30a8ebd..c174055 100644 --- a/src/NotificationManager_p_android.h +++ b/src/NotificationManager_p_android.h @@ -28,7 +28,7 @@ class NotificationManagerPrivate : public QObject public: explicit NotificationManagerPrivate(NotificationManager *qq); - void newBlockSeen_fromNetwork(int height); + void newBlockSeen_fromNetwork(); void segmentUpdated_fromNetwork(); /* transaction notifications are either routed to the Android @@ -43,11 +43,11 @@ public: signals: // emitted by notifyNewBlock_fromNetwork, in order to move the call to the Qt thread. - void newBlockSeenSignal(int blockHeight); + void newBlockSeenSignal(); void segmentUpdatedSignal(); private slots: - void newBlockSeen(int blockHeight); + void newBlockSeen(); // the wallet has updated. void segmentUpdated(); diff --git a/src/NotificationManager_p_dbus.h b/src/NotificationManager_p_dbus.h index 0de3117..21e1cd0 100644 --- a/src/NotificationManager_p_dbus.h +++ b/src/NotificationManager_p_dbus.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,17 +33,17 @@ class NotificationManagerPrivate : public QObject public: explicit NotificationManagerPrivate(NotificationManager *qq); - void newBlockSeen_fromNetwork(int height); + void newBlockSeen_fromNetwork(); void segmentUpdated_fromNetwork(); signals: // emitted by notifyNewBlock_fromNetwork, in order to move the call to the Qt thread. - void newBlockSeenSignal(int blockHeight); + void newBlockSeenSignal(); void segmentUpdatedSignal(); private slots: // newBlockSeen on the local thread. - void newBlockSeen(int blockHeight); + void newBlockSeen(); // the wallet has updated. void segmentUpdated(); diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index a10eb0c..0b22c6f 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -58,7 +58,7 @@ void JNI_notificationPermissionDenied(JNIEnv *env, jobject thiz) // turn this off if the user doesn't allow android notifications. // The UI will reflect this and enabling it will request the // permission again. - FloweePay::instance()->setNewBlockMuted(true); + // TODO } // implementation in FloweePay_android.cpp -- 2.54.0 From 41deb2b6dd5824fb4261f9451495609eb678552a Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 22 Apr 2025 19:31:40 +0200 Subject: [PATCH 1327/1428] Make transactions I send not show a notification. The in-app notification is rather silly to show when it is a user initiated action. --- modules/big-transfer/QMLTransferManager.cpp | 4 ++-- modules/send-sweep/QMLSweepHandler.cpp | 2 +- src/Payment.cpp | 2 +- src/Wallet.cpp | 8 +++++++- src/Wallet.h | 15 +++++++++++++-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/modules/big-transfer/QMLTransferManager.cpp b/modules/big-transfer/QMLTransferManager.cpp index a65fb9a..d7a35d7 100644 --- a/modules/big-transfer/QMLTransferManager.cpp +++ b/modules/big-transfer/QMLTransferManager.cpp @@ -124,11 +124,11 @@ void QMLTransferManager::send(QObject *previewTx) // call to fromWallet to mark outputs locked and save tx. auto fromWallet = m_fromAccount->wallet(); - fromWallet->newTransaction(tx); + fromWallet->addTransaction(tx, Wallet::NoNotification); fromWallet->setTransactionComment(tx, tr("Migrated Coin")); // call to toWallet to have it too! auto toWallet = m_toAccount->wallet(); - toWallet->newTransaction(tx); + toWallet->addTransaction(tx, Wallet::NoNotification); toWallet->setTransactionComment(tx, tr("Migrated Coin")); // and broadcast it. auto txInfo = std::make_shared(m_toAccount->wallet(), tx); diff --git a/modules/send-sweep/QMLSweepHandler.cpp b/modules/send-sweep/QMLSweepHandler.cpp index 4a40647..5a6705d 100644 --- a/modules/send-sweep/QMLSweepHandler.cpp +++ b/modules/send-sweep/QMLSweepHandler.cpp @@ -157,7 +157,7 @@ void QMLSweepHandler::markUserApproved() setTargetAddress(renderAddress(address)); const auto tx = m_builder.createTransaction(); - m_account->wallet()->newTransaction(tx); + m_account->wallet()->addTransaction(tx, Wallet::NoNotification); m_account->wallet()->setTransactionComment(tx, tr("Swept funds")); m_infoObject = std::make_shared(m_account->wallet(), tx); diff --git a/src/Payment.cpp b/src/Payment.cpp index ea8351c..acc0181 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -407,7 +407,7 @@ void Payment::broadcast() return; // call to wallet to mark outputs locked and save tx. - m_wallet->newTransaction(m_tx); + m_wallet->addTransaction(m_tx, Wallet::NoNotification); if (!m_userComment.isEmpty()) m_wallet->setTransactionComment(m_tx, m_userComment); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 62ceb41..ecd1dc7 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -351,6 +351,11 @@ void Wallet::updateSignatureTypes(const std::map &txDat } void Wallet::newTransaction(const Tx &tx) +{ + addTransaction(tx, WithNotification); +} + +void Wallet::addTransaction(const Tx &tx, NotificationNeeded notificationNeeded) { int firstNewTransaction; P2PNet::Notification notification; @@ -423,7 +428,8 @@ void Wallet::newTransaction(const Tx &tx) emit utxosChanged(); emit appendedTransactions(firstNewTransaction, 1); - FloweePay::instance()->sendTransactionNotification(notification); + if (notificationNeeded == WithNotification) + FloweePay::instance()->sendTransactionNotification(notification); rebuildBloom(MaybeBuild); } diff --git a/src/Wallet.h b/src/Wallet.h index bce4180..d2bc9e0 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2020-2024 Tom Zander + * Copyright (C) 2020-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -113,7 +113,18 @@ public: */ void newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) override; // notify about unconfirmed Tx. + // simply calls addTransaction(tx, WithNotification); void newTransaction(const Tx &tx) override; + enum NotificationNeeded { + WithNotification, + NoNotification + }; + /** + * Add a transaction to wallet. + * @param notification indicates if we should notify the rest of + * the application about the new transaction. + */ + void addTransaction(const Tx &tx, NotificationNeeded notification); void rebuildFilter() override; /// Let the wallet know that it is up-to-date to \a height void setLastSynchedBlockHeight(int height) override; @@ -469,7 +480,7 @@ private: // returns true if any of the transactions are unknown to the wallet bool anythingNew(int blockHeight, const std::deque &transactions) const; - // helper method called from both newTransaction and newTransactions + // helper method called from both addTransaction and newTransactions void updateSignatureTypes(const std::map &txData); /// Helper method for findInputsFor -- 2.54.0 From b12cc10a31e9d90c51f178c356a0cf66f69a6474 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Apr 2025 14:27:19 +0200 Subject: [PATCH 1328/1428] Reorder header file into groups. The public methods have been changed to group the types of method together. There is a group for transaction properties, a group of other properties, a group of utility functions and a group of wallet management functions (for things like private keys and the like). --- src/Wallet.h | 340 ++++++++++++++++++++++++++------------------------- 1 file changed, 176 insertions(+), 164 deletions(-) diff --git a/src/Wallet.h b/src/Wallet.h index d2bc9e0..a506e5f 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -105,64 +105,28 @@ public: NotUsedYet, }; - /** - * @brief newTransactions announces a list of transactions pushed to us from a peer. - * @param header the block header these transactions appeared in. - * @param blockHeight the blockheight we know the header under. - * @param blockTransactions The actual transactions. - */ - void newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) override; - // notify about unconfirmed Tx. - // simply calls addTransaction(tx, WithNotification); - void newTransaction(const Tx &tx) override; - enum NotificationNeeded { - WithNotification, - NoNotification + enum PrivKeyType { + ChangePath, ///< The private keys created from a HD wallets change derivation + ReceivePath ///< All the other types of private keys }; - /** - * Add a transaction to wallet. - * @param notification indicates if we should notify the rest of - * the application about the new transaction. - */ - void addTransaction(const Tx &tx, NotificationNeeded notification); - void rebuildFilter() override; - /// Let the wallet know that it is up-to-date to \a height - void setLastSynchedBlockHeight(int height) override; - void updateBackupBlockHeight() override; - void headerSyncComplete() override; - /** - * Check if the headers are high enough for our usage. - */ - void checkHeaderSyncComplete(const Blockchain &blockchain); - std::shared_ptr segment() const; + enum EncryptionLevel { + NotEncrypted, + /** + * This will encrypt the private keys of the wallet, which effectively + * removes only one functio of the wallet, to sign transactions spending funds. + */ + SecretsEncrypted, + /** + * This level of encryption aims to encrypt all functionality of + * the wallet to the level that even reading the on-disk files + * an attacker will not be able to figure out which addresses are ours. + */ + FullyEncrypted + }; - /// Create a new private key, can not be called on a HD wallet. - void createNewPrivateKey(uint32_t currentBlockheight); - /// import an existing private key. - bool addPrivateKey(const QString &privKey, uint32_t startBlockHeight); - /// Save changed in historical wallet - void saveWallet(); - qint64 balanceConfirmed() const { - return m_balanceConfirmed; - } - qint64 balanceImmature() const { - return m_balanceImmature; - } - qint64 balanceUnconfirmed() const { - return m_balanceUnconfirmed; - } - - /// return the amount of UTXOs that hold money - int unspentOutputCount() const; - /// return the amount of UTXOs ever created for this account. - int historicalOutputCount() const; - - /// the user-visible name for the wallet. - QString name() const; - /// set the user-visible name for the wallet. - void setName(const QString &name); + /// ---- transactions and getters / setters /** * returns the txIndex, or -1 if no hit. @@ -186,21 +150,78 @@ public: /// return the blockheight a transaction is mined at. int transactionMined(int txIndex) const; - struct PrivKeyData { - int privKeyId = 0; - PrivateKey key; - SignatureType sigType; + /** + * @brief newTransactions announces a list of transactions pushed to us from a peer. + * @param header the block header these transactions appeared in. + * @param blockHeight the blockheight we know the header under. + * @param blockTransactions The actual transactions. + */ + void newTransactions(const uint256 &blockId, int blockHeight, const std::deque &blockTransactions) override; + // notify about unconfirmed Tx. + // simply calls addTransaction(tx, WithNotification); + void newTransaction(const Tx &tx) override; + enum NotificationNeeded { + WithNotification, + NoNotification }; - /// Fetch private key that can unlock (for spending) the \a ref utxo. - PrivKeyData unlockKey(OutputRef ref) const; + /** + * Add a transaction to wallet. + * @param notification indicates if we should notify the rest of + * the application about the new transaction. + */ + void addTransaction(const Tx &tx, NotificationNeeded notification); + + // Fill the transactionInfo object from walletTransaction \a txIndex + void fetchTransactionInfo(TransactionInfo *info, int txIndex); /** - * Register the signature type for the given private key. - * The unlockKey() method will return the sigType stored for - * a specific private key. By calling this method we - * store the signature type for this key, for future requests. + * Set comment field on this transaction, assuming it has been accepted by this wallet. */ - void updateSignatureType(const PrivKeyData &data); + void setTransactionComment(const Tx &transaction, const QString &comment); + /** + * Set comment field on this transaction, assuming it has been accepted by this wallet. + */ + void setTransactionComment(int txIndex, const QString &comment); + + bool transactionMatch(const QFlags &includeFlags, int txIndex) const; + + + /// ---- non-transaction properties getters and setters + + /// returns if fusions were detected in this wallet. + /// see signal: walletStoresCFsChanged(); + bool walletStoresCashFusions() const; + + boost::filesystem::path walletDir() const; + + /** + * Return the importing state. + * A wallet that starts far behind the chain-tip is seen as importing until + * it has received all block data until the tip. + * This boolean is true at most once in the lifetime of a wallet. + * This boolean is persisted to disk. + */ + bool walletIsImporting() const; + + qint64 balanceConfirmed() const { + return m_balanceConfirmed; + } + qint64 balanceImmature() const { + return m_balanceImmature; + } + qint64 balanceUnconfirmed() const { + return m_balanceUnconfirmed; + } + + /// return the amount of UTXOs that hold money + int unspentOutputCount() const; + /// return the amount of UTXOs ever created for this account. + int historicalOutputCount() const; + + /// the user-visible name for the wallet. + QString name() const; + /// set the user-visible name for the wallet. + void setName(const QString &name); /// Return a bitcoin address (160 bits ripe key) for deposit. KeyId nextUnusedAddress(); @@ -208,17 +229,69 @@ public: /// Return a bitcoin address (160 bits ripe key) for change. KeyId nextUnusedChangeAddress(); - enum PrivKeyType { - ChangePath, ///< The private keys created from a HD wallets change derivation - ReceivePath ///< All the other types of private keys - }; - /// Return a private-key-index for deposits and reserve it from re-use. int reserveUnusedAddress(KeyId &keyId, PrivKeyType pkt = ReceivePath); /// The opposite of reserveUnusedAddress, free for usage an address. void unreserveAddress(int index); + /// a simple boolean that indicates only address zero will ever be used. + bool isSingleAddressWallet() const; + void setSingleAddressWallet(bool on); + + /** + * When true, then this wallet is the result of a user-initiated action. + * Otherwise it was created by the system for some reason and is empty. + */ + bool userOwnedWallet() const; + /** + * Changes a wallet to be user-owned or not. + */ + void setUserOwnedWallet(bool userOwnedWallet); + + /// Returns true if this wallet is backed by a Hierarchically Deterministic seed. + bool isHDWallet() const; + /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) + QString hdWalletMnemonic() const; + /// Returns true if the wallet mnemonic is in Electrum format + bool isElectrumMnemonic() const; + /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) passphrase + QString hdWalletMnemonicPwd() const; + /// Provided that this is a HD wallet, return the derivation path used for this wallet. + QString derivationPath() const; + /// Provided that this is a HD wallet, return the xpub for this wallet. + QString xpub() const; + + /// return the height of the last seen transaction that is mined + int lastTransactionTimestamp() const; + + /** + * Lock a the output from being spent in auto-generated transactions. + * @returns true if successful, false if the UTXO did not exist. + */ + bool lockUTXO(OutputRef outputRef); + bool unlockUTXO(OutputRef outputRef); + bool isLocked(OutputRef outputRef) const; + + /// Return the blockheight at which this wallet was created + int walletCreatedHeight() const; + + + /// ---- Utility functions + + void rebuildFilter() override; + /// Let the wallet know that it is up-to-date to \a height + void setLastSynchedBlockHeight(int height) override; + void updateBackupBlockHeight() override; + void headerSyncComplete() override; + /** + * Check if the headers are high enough for our usage. + */ + void checkHeaderSyncComplete(const Blockchain &blockchain); + + /// Check the loaded wallet version Id and make internal changes to upgrade it to current. + void performUpgrades(); + struct OutputSet { std::vector outputs; qint64 totalSats = 0; @@ -235,12 +308,39 @@ public: */ OutputSet findInputsFor(qint64 output, int feePerByte, int txSize, int64_t &change) const; - /// a simple boolean that indicates only address zero will ever be used. - bool isSingleAddressWallet() const; - void setSingleAddressWallet(bool on); +#ifdef IN_TESTS + /** + * Unit tests can call this to populate a wallet with some dummy transactions. + */ + void addTestTransactions(); +#endif - // Fill the transactionInfo object from walletTransaction \a txIndex - void fetchTransactionInfo(TransactionInfo *info, int txIndex); + /// ---- Wallet management + + std::shared_ptr segment() const; + + /// Create a new private key, can not be called on a HD wallet. + void createNewPrivateKey(uint32_t currentBlockheight); + /// import an existing private key. + bool addPrivateKey(const QString &privKey, uint32_t startBlockHeight); + /// Save changed in historical wallet + void saveWallet(); + + struct PrivKeyData { + int privKeyId = 0; + PrivateKey key; + SignatureType sigType; + }; + /// Fetch private key that can unlock (for spending) the \a ref utxo. + PrivKeyData unlockKey(OutputRef ref) const; + + /** + * Register the signature type for the given private key. + * The unlockKey() method will return the sigType stored for + * a specific private key. By calling this method we + * store the signature type for this key, for future requests. + */ + void updateSignatureType(const PrivKeyData &data); /** * Returns a 'seed' to add to the user password on a (partially) encrypted @@ -262,21 +362,6 @@ public: */ void setEncryptionSeed(uint32_t newEncryptionSeed); - enum EncryptionLevel { - NotEncrypted, - /** - * This will encrypt the private keys of the wallet, which effectively - * removes only one functio of the wallet, to sign transactions spending funds. - */ - SecretsEncrypted, - /** - * This level of encryption aims to encrypt all functionality of - * the wallet to the level that even reading the on-disk files - * an attacker will not be able to figure out which addresses are ours. - */ - FullyEncrypted - }; - /** * Set the level of encryption applied to this wallet's saved state. * @@ -297,34 +382,6 @@ public: // forget all encrypted secrets. void forgetEncryptedSecrets(); -#ifdef IN_TESTS - /** - * Unit tests can call this to populate a wallet with some dummy transactions. - */ - void addTestTransactions(); -#endif - - /** - * When true, then this wallet is the result of a user-initiated action. - * Otherwise it was created by the system for some reason and is empty. - */ - bool userOwnedWallet() const; - /** - * Changes a wallet to be user-owned or not. - */ - void setUserOwnedWallet(bool userOwnedWallet); - - /** - * Set comment field on this transaction, assuming it has been accepted by this wallet. - */ - void setTransactionComment(const Tx &transaction, const QString &comment); - /** - * Set comment field on this transaction, assuming it has been accepted by this wallet. - */ - void setTransactionComment(int txIndex, const QString &comment); - - bool transactionMatch(const QFlags &includeFlags, int txIndex) const; - struct WalletSecret { PrivateKey privKey; std::vector encryptedPrivKey; @@ -358,24 +415,10 @@ public: KeyDetails fetchKeyDetails(int privKeyId) const; int findPrivKeyId(const KeyId &address) const; /** - * Return all the utxo's by ref that would be unlocked with the argument private key id. + * Return all the utxo's by ref that could be unlocked with the argument private key id. */ std::vector unspentOutputsForKey(int privKeyId) const; - /// Returns true if this wallet is backed by a Hierarchically Deterministic seed. - bool isHDWallet() const; - /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) - QString hdWalletMnemonic() const; - /// Returns true if the wallet mnemonic is in Electrum format - bool isElectrumMnemonic() const; - /// Provided that this is a HD wallet, return the seed-words (aka mnemonic) passphrase - QString hdWalletMnemonicPwd() const; - /// Provided that this is a HD wallet, return the derivation path used for this wallet. - QString derivationPath() const; - /// Provided that this is a HD wallet, return the xpub for this wallet. - QString xpub() const; - - /** * Create a HD masterkey that will be used for future creation of new private keys * @@ -389,37 +432,6 @@ public: void createHDMasterKey(const QString &mnemonic, const QString &pwd, const std::vector &derivationPath, uint32_t startHeight, bool isImport, HDMasterKey::MnemonicType format = HDMasterKey::BIP39Mnemonic); - /// return the height of the last seen transaction that is mined - int lastTransactionTimestamp() const; - - /** - * Lock a the output from being spent in auto-generated transactions. - * @returns true if successful, false if the UTXO did not exist. - */ - bool lockUTXO(OutputRef outputRef); - bool unlockUTXO(OutputRef outputRef); - bool isLocked(OutputRef outputRef) const; - - /// Return the blockheight at which this wallet was created - int walletCreatedHeight() const; - - /// Check the loaded wallet version Id and make internal changes to upgrade it to current. - void performUpgrades(); - - /** - * Return the importing state. - * A wallet that starts far behind the chain-tip is seen as importing until - * it has received all block data until the tip. - * This boolean is true at most once in the lifetime of a wallet. - * This boolean is persisted to disk. - */ - bool walletIsImporting() const; - - boost::filesystem::path walletDir() const; - - /// returns if fusions were detected in this wallet. - /// see signal: walletStoresCFsChanged(); - bool walletStoresCashFusions() const; public slots: void delayedSave(); -- 2.54.0 From e162bd5f807414f9282cad330b1ee32ef59877c7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Apr 2025 15:54:12 +0200 Subject: [PATCH 1329/1428] Improve text for notification of new transactions The text now special cases several common occasions that gave confusing texts. We additionally fix the position of the "+" sign to be behind the currency symbol, like it was always supposed to be. --- src/NotificationManager.cpp | 69 ++++++++++++++++++++++++++++++++----- src/PriceDataProvider.cpp | 21 ++++++++--- src/PriceDataProvider.h | 13 ++++--- src/Wallet.cpp | 9 +++++ src/Wallet.h | 4 +++ 5 files changed, 99 insertions(+), 17 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 4a459cb..4a1e4b9 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -23,6 +23,22 @@ #include +namespace { +const uint256 &txidFromWallet(uint16_t segmentId, int txIndex) +{ + static uint256 dummy; + const auto wallets = FloweePay::instance()->wallets(); + for (auto wallet : wallets) { + if (segmentId == wallet->segment()->segmentId()) { + return wallet->txid(txIndex); + } + } + + return dummy; +} +} + + void NotificationManager::headerSyncComplete() { m_headerSyncComplete = true; @@ -45,33 +61,70 @@ QString NotificationManager::describeCollated(int &txCount) const if (data.empty()) return QString(); + /* + * The basic feature is simple; a given number of wallets (data.size()) have each + * seen a certain amount of bch added or subtracted. + * + * The common case, however, is that only one transaction is seen at a time. + * (Even if it might touch two wallets). + * As such we do some checks for the common cases in order to have a nicer explanation + * of what happened to the user. + * + * 1. We detect one transaction touching two wallets. + * 2. We see (one or more) transactions arrive in a wallet that have special markers + * we know about. + */ + + if (txCount == 2 && data.size() == 2) { + // is this an intra-wallet transfer? + const auto &txid1 = txidFromWallet(data.at(0).privacySegment, data.at(0).walletTxId); + const auto &txid2 = txidFromWallet(data.at(1).privacySegment, data.at(1).walletTxId); + if (!txid1.IsNull() && txid1 == txid2) { + return tr("Transaction between two wallets found.", "the wallets are both yours"); + } + } + int64_t deposited = 0; int64_t spent = 0; + int cfFound = 0; + const auto wallets = FloweePay::instance()->wallets(); for (const auto &item : data) { + for (auto wallet : wallets) { + if (wallet->segment()->segmentId() == item.privacySegment) { + if (wallet->isCFTransaction(item.walletTxId)) + ++cfFound; + break; + } + } + deposited += item.deposited; spent += item.spent; ++txCount; } + if (txCount == cfFound) { + return tr("We received %1 anonimity transactions.", nullptr, txCount).arg(txCount); + } const auto gained = deposited - spent; auto pricesOracle = FloweePay::instance()->prices(); QString gainedStr; - if (pricesOracle->price()) - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price()); - if (gainedStr.isEmpty()) { + if (pricesOracle->oldData()) { // no price data available (yet). Display crypto units gainedStr = QString("%1 %2") .arg(FloweePay::instance()->amountToStringPretty((double) gained), FloweePay::instance()->unitName()); } - if (gained > 0) // since we indicate adding, we always want the plus there - gainedStr = QString("+%1").arg(gainedStr); - // body-text + if (gained < 0 && txCount == 1) { + if (gainedStr.isEmpty()) + gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign); + return tr("A payment of %1 has been sent").arg(gainedStr); + } + + if (gainedStr.isEmpty()) + gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::AlwaysAddSign); if (data.size() > 1) return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); - if (gained < 0 && txCount == 1) - return tr("A payment of %1 has been sent").arg(gainedStr.mid(1)); return tr("%1 new transactions found (%2)", "", txCount).arg(txCount).arg(gainedStr); } diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 62d2f37..5748caa 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -149,11 +149,11 @@ void PriceDataProvider::setCountry(const QString &countrycode) setCurrency(QLocale(countrycode)); } -QString PriceDataProvider::formattedPrice(double amountSats, int64_t price) const +QString PriceDataProvider::formattedPrice(double amountSats, int64_t price, SignOption signOption) const { if (price == 0) return QString(); - return formattedPrice(priceFor(amountSats, price)); + return formattedPrice(priceFor(amountSats, price), signOption); } int64_t PriceDataProvider::priceFor(double amountSats, int64_t price) const @@ -164,7 +164,7 @@ int64_t PriceDataProvider::priceFor(double amountSats, int64_t price) const return (fiatValue + (amountSats > 0 ? 50000000: -50000000)) / qint64(100000000); } -QString PriceDataProvider::formattedPrice(int64_t fiatValue) const +QString PriceDataProvider::formattedPrice(int64_t fiatValue, PriceDataProvider::SignOption signOption) const { // convert cheaply (low number of mallocs) to a price. // since our fiat is in cents, we assume we may add up to two leading zeros. @@ -190,17 +190,28 @@ QString PriceDataProvider::formattedPrice(int64_t fiatValue) const } actualPrice = QString(buf, -1); } + constexpr const char* Minus = "-"; + constexpr const char* Plus = "+"; + constexpr const char* Empty = ""; + + const char *sign = Empty; + if (signOption != NoSign) { + if (fiatValue < 0) + sign = Minus; + else if (signOption == AlwaysAddSign) + sign = Plus; + } if (m_displayCents) { return m_currencySymbolPrefix - % (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) + % QLatin1String(sign) % actualPrice % m_decimalPoint % centsPrice.right(2) % m_currencySymbolPost; } else { - return (fiatValue < 0 ? QLatin1String("-") : QLatin1String("")) + return QLatin1String(sign) % m_currencySymbolPrefix % actualPrice % m_currencySymbolPost; diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 8f5c540..a9b5a79 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021-2024 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,6 +39,12 @@ class PriceDataProvider : public QObject Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost NOTIFY currencySymbolChanged) public: + enum SignOption { + NoSign, ///< Never include a minus or plus sign. + OnlyMinus, ///< Prefix a minus, do not prefix a plus. + AlwaysAddSign ///< Prefix either a plus or minus. + }; + explicit PriceDataProvider(const QString &countryCode, QObject *parent = nullptr); void start(); @@ -65,14 +71,13 @@ public: * This takes the BCH amount (in sats) and renders it into a price as the current locale defines, * based on \a price. */ - Q_INVOKABLE QString formattedPrice(double amountSats, int64_t price) const; + Q_INVOKABLE QString formattedPrice(double amountSats, int64_t price, SignOption signOption = OnlyMinus) const; /// Return the price as int (in cents) for the number of sats and the given price. Q_INVOKABLE int64_t priceFor(double amountSats, int64_t price) const; - /** * Return a formatted string with the locale-defined price of a fiat price from \a cents. */ - Q_INVOKABLE QString formattedPrice(int64_t cents) const; + Q_INVOKABLE QString formattedPrice(int64_t cents, SignOption signOption = OnlyMinus) const; /** * Return the price at a certain time in the past. diff --git a/src/Wallet.cpp b/src/Wallet.cpp index ecd1dc7..913a26b 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -1304,6 +1304,15 @@ const uint256 &Wallet::txid(int txIndex) const return iter->second.txid; } +bool Wallet::isCFTransaction(int txIndex) const +{ + QMutexLocker locker(&m_lock); + auto iter = m_walletTransactions.find(txIndex); + if (m_walletTransactions.end() == iter) + throw std::runtime_error("Invalid tx-index"); + return iter->second.isCashFusionTx; +} + Tx::Output Wallet::txOutput(Wallet::OutputRef ref) const { uint256 txid; diff --git a/src/Wallet.h b/src/Wallet.h index a506e5f..dec713c 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -138,7 +138,11 @@ public: return txid(ref.txIndex()); } /// Fetch UTXO txid + /// throws if txIndex is not known. const uint256 &txid(int txIndex) const; + /// returns true if the transaction is found and is a cash fusion transaction. + /// throws if txIndex is not known. + bool isCFTransaction(int txIndex) const; /// Fetch UTXO output Tx::Output txOutput(OutputRef ref) const; /// Fetch UTXO value (in sats) -- 2.54.0 From 31a8abce10a1dd216fad32e0c6ac4b380f5a4391 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 23 Apr 2025 20:24:57 +0200 Subject: [PATCH 1330/1428] Tie tx-notifications to Android system. We re-introduced a checkbox to enable notifications. We now have a second type of notification, one for incoming transactions that may happen when the app isn't in the foreground. --- .../java/org/flowee/pay/PayNotifications.java | 31 +++++++++++++++++++ guis/mobile/Settings.qml | 7 ++++- src/FloweePay.cpp | 14 +++++++++ src/FloweePay.h | 6 ++++ src/NotificationManager.cpp | 14 +++++++++ src/NotificationManager.h | 5 +++ src/NotificationManager_android.cpp | 19 ++++++++++-- src/NotificationManager_dummy.cpp | 2 +- src/Periodic.cpp | 10 ++++++ src/main_utils_android.cpp | 2 +- 10 files changed, 104 insertions(+), 6 deletions(-) diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index 255321d..0fdf80f 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -33,8 +33,11 @@ public class PayNotifications private static final int ConfirmedNotificationId = 6134828; private static PayNotifications g_singleton = null; private Notification.Builder m_confirmedMessageBuilder = null; + private Notification.Builder m_txMessageBuilder = null; private NotificationManager m_notificationManager = null; private String m_confirmedChannelId = "unset"; + private String m_transactionsChannelId = "unset"; + private int m_lastTxId = 0; public static PayNotifications instance() @@ -67,6 +70,19 @@ public class PayNotifications m_notificationManager.createNotificationChannel(confirmedChannel); m_confirmedChannelId = confirmedChannel.getId(); m_confirmedMessageBuilder = new Notification.Builder(context, m_confirmedChannelId); + + NotificationChannel txFoundChannel = new NotificationChannel("transactions", "Payments", + NotificationManager.IMPORTANCE_MAX); + m_notificationManager.createNotificationChannel(txFoundChannel); + m_transactionsChannelId = txFoundChannel.getId(); + m_txMessageBuilder = new Notification.Builder(context, m_transactionsChannelId); + + // avoid replacing existing notifications. + StatusBarNotification[] notifications = m_notificationManager.getActiveNotifications(); + for (StatusBarNotification n : notifications) { + if (n.getId() != ConfirmedNotificationId) + m_lastTxId = Math.max(m_lastTxId, n.getId()); + } } // static methods to be called from C++ @@ -75,6 +91,12 @@ public class PayNotifications g_singleton.setTxConfirmedNotification_priv(title, message); } + // static methods to be called from C++ + public static void openTxNotification(String message) + { + g_singleton.openTxNotification_priv(message); + } + private void setTxConfirmedNotification_priv(String title, String message) @@ -90,4 +112,13 @@ public class PayNotifications // notification in the air of this type. m_notificationManager.notify(ConfirmedNotificationId, m_confirmedMessageBuilder.build()); } + + private void openTxNotification_priv(String message) + { + m_txMessageBuilder.setSmallIcon(R.drawable.icon) + .setContentText(message) + .setColor(0xA0F87); // flowee blue + + m_notificationManager.notify(5 /*++m_lastTxId*/, m_txMessageBuilder.build()); + } } diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index 482b1a9..80cddc8 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -189,7 +189,12 @@ Page { PageTitledBox { title: qsTr("Notifications") - + Flowee.CheckBox { + width: parent.width + text: qsTr("Show Payment Notifications") + checked: Pay.externalNotifications + onCheckedChanged: Pay.externalNotifications = checked + } } } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index fe66421..a382fd0 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -965,6 +965,20 @@ void FloweePay::setMnemonicProposals(const QStringList &proposals) emit mnemonicProposalsChanged(); } +bool FloweePay::externalNotifications() const +{ + return m_notifications.externalNotifications(); +} + +void FloweePay::setExternalNotifications(bool on) +{ + if (m_notifications.externalNotifications() == on) + return; + m_notifications.setExternalNotifications(on); + emit externalNotificationsChanged(); + +} + int FloweePay::backgroundUpdateInterval() const { return m_backgroundUpdateInterval; diff --git a/src/FloweePay.h b/src/FloweePay.h index bf273aa..8404e12 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -69,6 +69,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(bool activityShowsBch READ activityShowsBch WRITE setActivityShowsBch NOTIFY activityShowsBchChanged) Q_PROPERTY(int fontScaling READ fontScaling WRITE setFontScaling NOTIFY fontScalingChanged) Q_PROPERTY(int dspTimeout READ dspTimeout WRITE setDspTimeout NOTIFY dspTimeoutChanged) + Q_PROPERTY(bool externalNotifications READ externalNotifications WRITE setExternalNotifications NOTIFY externalNotificationsChanged FINAL) // User setting based on which unit (milli/sats/etc) the user choose Q_PROPERTY(int unitAllowedDecimals READ unitAllowedDecimals NOTIFY unitChanged) Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) @@ -454,6 +455,9 @@ public: */ QStringList mnemonicProposals() const; + bool externalNotifications() const; + void setExternalNotifications(bool newExternalNotifications); + signals: void loadComplete(); /// \internal @@ -484,6 +488,8 @@ signals: void backgroundUpdateIntervalChanged(); void mnemonicProposalsChanged(); + void externalNotificationsChanged(); + private slots: void loadingCompleted(); void saveData(); diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 4a1e4b9..fa0a06b 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -54,6 +54,20 @@ bool NotificationManager::isMonitoringConfirmations() const return !m_confirmationTransactions.empty(); } +bool NotificationManager::externalNotifications() const +{ + return m_externalNotifications; +} + +void NotificationManager::setExternalNotifications(bool on) +{ + m_externalNotifications = on; + if (m_externalNotifications) + requestNotificationPermissions(); + QSettings appConfig; + appConfig.setValue(KEY_EXTERNAL_NOTIFICATIONS, m_externalNotifications); +} + QString NotificationManager::describeCollated(int &txCount) const { const auto data = collatedData(); diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 076bf9e..33fd678 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -47,15 +47,20 @@ public: bool isMonitoringConfirmations() const; void requestNotificationPermissions(); + bool externalNotifications() const; + void setExternalNotifications(bool on); + public slots: /// visible is set to true if the app is active and not in the background void setApplicationForeground(bool visible); private: bool m_headerSyncComplete = false; + bool m_externalNotifications = false; // do we send notifications to the OS? QString describeCollated(int &txCount) const; + static constexpr const char *KEY_EXTERNAL_NOTIFICATIONS = "external-notifications"; // (just) after a block has been mined, this will return a nice text. QString describeConfirmations(); struct ConfirmationTxInfo { diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index a9ea49d..2fddf64 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -31,6 +31,8 @@ NotificationManager::NotificationManager(QObject *parent) d(new NotificationManagerPrivate(this)) { setCollation(true); + QSettings appConfig; + m_externalNotifications = appConfig.value(KEY_EXTERNAL_NOTIFICATIONS, false).toBool(); } void NotificationManager::notifyNewBlock(const P2PNet::Notification &) @@ -84,10 +86,8 @@ void NotificationManagerPrivate::newBlockSeen() if (!message.isEmpty()) { QString title = q->tr("New Block Found"); auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([title, message]() { - QJniEnvironment env; auto jtitle = QJniObject::fromString(title); auto jmessage = QJniObject::fromString(message); - jclass notifications = env.findClass(ClassName); QJniObject::callStaticMethod(ClassName, "setTxConfirmedNotification", "(Ljava/lang/String;Ljava/lang/String;)V", jtitle, jmessage); }); @@ -107,7 +107,20 @@ void NotificationManagerPrivate::segmentUpdated() void NotificationManagerPrivate::createAndroidTxNotification() { - // TODO + if (q->m_externalNotifications) { + int txCount; + QString message = q->describeCollated(txCount); + if (!message.isEmpty()) { + auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { + auto jmessage = QJniObject::fromString(message); + QJniObject::callStaticMethod(ClassName, "openTxNotification", + "(Ljava/lang/String;)V", jmessage); + }); + // The java side will handle everything from here, so we just ignore the task. + Q_UNUSED(task); + } + } + q->flushCollate(); } void NotificationManagerPrivate::createQmlNotification() diff --git a/src/NotificationManager_dummy.cpp b/src/NotificationManager_dummy.cpp index 082e61f..a90cba0 100644 --- a/src/NotificationManager_dummy.cpp +++ b/src/NotificationManager_dummy.cpp @@ -42,5 +42,5 @@ void NotificationManager::requestNotificationPermissions() void NotificationManager::setApplicationForeground(bool visible) { + Q_UNUSED(visible); } - diff --git a/src/Periodic.cpp b/src/Periodic.cpp index df8d430..41ec9b7 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -22,6 +22,10 @@ #include #include +#ifdef TARGET_OS_Android +# include +constexpr const char *ClassName = "org/flowee/pay/PayNotifications"; +#endif struct CommandLineParserData; std::unique_ptr handleStaticChain(CommandLineParserData *cld); @@ -56,6 +60,12 @@ int Periodic::run() QCoreApplication::quit(); return; } + +#ifdef TARGET_OS_Android + QJniObject::callStaticMethod(ClassName, "setup", + "(Landroid/content/Context;)V", QNativeInterface::QAndroidApplication::context()); +#endif + // for each wallet check if it is up to date yet. for (Wallet *wallet : app->wallets()) { QObject::connect(wallet, &Wallet::lastBlockSynchedChanged, diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 0b22c6f..12e9df2 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -58,7 +58,7 @@ void JNI_notificationPermissionDenied(JNIEnv *env, jobject thiz) // turn this off if the user doesn't allow android notifications. // The UI will reflect this and enabling it will request the // permission again. - // TODO + FloweePay::instance()->setExternalNotifications(false); } // implementation in FloweePay_android.cpp -- 2.54.0 From d692c31a5bdbe566d947de3c0a7d3f13d4ac2317 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Apr 2025 19:09:24 +0200 Subject: [PATCH 1331/1428] Fixes to make the android notifications work. --- .../java/org/flowee/pay/PayNotifications.java | 19 ++++++++-------- src/FloweePay_android.cpp | 5 ++--- src/NotificationManager_android.cpp | 22 ++++++------------- src/Periodic.cpp | 10 ++++----- src/main.cpp | 4 ++++ src/main_utils_android.cpp | 2 +- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/android/java/org/flowee/pay/PayNotifications.java b/android/java/org/flowee/pay/PayNotifications.java index 0fdf80f..d46eceb 100644 --- a/android/java/org/flowee/pay/PayNotifications.java +++ b/android/java/org/flowee/pay/PayNotifications.java @@ -37,7 +37,6 @@ public class PayNotifications private NotificationManager m_notificationManager = null; private String m_confirmedChannelId = "unset"; private String m_transactionsChannelId = "unset"; - private int m_lastTxId = 0; public static PayNotifications instance() @@ -76,13 +75,6 @@ public class PayNotifications m_notificationManager.createNotificationChannel(txFoundChannel); m_transactionsChannelId = txFoundChannel.getId(); m_txMessageBuilder = new Notification.Builder(context, m_transactionsChannelId); - - // avoid replacing existing notifications. - StatusBarNotification[] notifications = m_notificationManager.getActiveNotifications(); - for (StatusBarNotification n : notifications) { - if (n.getId() != ConfirmedNotificationId) - m_lastTxId = Math.max(m_lastTxId, n.getId()); - } } // static methods to be called from C++ @@ -119,6 +111,15 @@ public class PayNotifications .setContentText(message) .setColor(0xA0F87); // flowee blue - m_notificationManager.notify(5 /*++m_lastTxId*/, m_txMessageBuilder.build()); + int lastId = 1; + // avoid replacing existing notifications. + StatusBarNotification[] notifications = m_notificationManager.getActiveNotifications(); + if (notifications.length >= 6) // we simply stop bothering the user. + return; + for (StatusBarNotification n : notifications) { + if (n.getId() != ConfirmedNotificationId) + lastId = Math.max(lastId, n.getId()); + } + m_notificationManager.notify(lastId + 1, m_txMessageBuilder.build()); } } diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index f241f46..580e7e5 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -83,8 +83,6 @@ FloweePayPrivate::FloweePayPrivate(NotificationManager *nm, FloweePay *qq) m_checkOfflineTimer->setTimerType(Qt::VeryCoarseTimer); m_checkOfflineTimer->setInterval(7000); -// nm->requestNotificationPermissions(); // TODO remove - connect (this, &FloweePayPrivate::airplaneModeCheckNeeded, this, &FloweePayPrivate::checkAirplaneMode, Qt::QueuedConnection); connect (m_checkOfflineTimer, &QTimer::timeout, @@ -272,7 +270,8 @@ void FloweePayPrivate::backgroundUpdaterToggled() main.callObjectMethod("enableRegularUpdates", "(I)V", m_parent->backgroundUpdates() ? m_parent->backgroundUpdateInterval() : 0); - if (m_parent->backgroundUpdates()) { + if (m_parent->backgroundUpdates() + && qobject_cast(QCoreApplication::instance())) { // skip if no gui QTimer::singleShot(100, m_parent, [main](){ // check if user approved auto done = main.callMethod("isIgnoringBatteryOptimizations", "()Z"); diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 2fddf64..8b7e706 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -85,14 +85,10 @@ void NotificationManagerPrivate::newBlockSeen() auto message = q->describeConfirmations(); if (!message.isEmpty()) { QString title = q->tr("New Block Found"); - auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([title, message]() { - auto jtitle = QJniObject::fromString(title); - auto jmessage = QJniObject::fromString(message); - QJniObject::callStaticMethod(ClassName, "setTxConfirmedNotification", - "(Ljava/lang/String;Ljava/lang/String;)V", jtitle, jmessage); - }); - // The java side will handle everything from here, so we just ignore the task. - Q_UNUSED(task); + auto jtitle = QJniObject::fromString(title); + auto jmessage = QJniObject::fromString(message); + QJniObject::callStaticMethod(ClassName, "setTxConfirmedNotification", + "(Ljava/lang/String;Ljava/lang/String;)V", jtitle, jmessage); } } @@ -111,13 +107,9 @@ void NotificationManagerPrivate::createAndroidTxNotification() int txCount; QString message = q->describeCollated(txCount); if (!message.isEmpty()) { - auto task = QNativeInterface::QAndroidApplication::runOnAndroidMainThread([message]() { - auto jmessage = QJniObject::fromString(message); - QJniObject::callStaticMethod(ClassName, "openTxNotification", - "(Ljava/lang/String;)V", jmessage); - }); - // The java side will handle everything from here, so we just ignore the task. - Q_UNUSED(task); + auto jmessage = QJniObject::fromString(message); + QJniObject::callStaticMethod(ClassName, "openTxNotification", + "(Ljava/lang/String;)V", jmessage); } } q->flushCollate(); diff --git a/src/Periodic.cpp b/src/Periodic.cpp index 41ec9b7..3884c23 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -61,11 +61,6 @@ int Periodic::run() return; } -#ifdef TARGET_OS_Android - QJniObject::callStaticMethod(ClassName, "setup", - "(Landroid/content/Context;)V", QNativeInterface::QAndroidApplication::context()); -#endif - // for each wallet check if it is up to date yet. for (Wallet *wallet : app->wallets()) { QObject::connect(wallet, &Wallet::lastBlockSynchedChanged, @@ -83,6 +78,11 @@ int Periodic::run() QCoreApplication::quit(); }); +#ifdef TARGET_OS_Android + QJniObject::callStaticMethod(ClassName, "setup", + "(Landroid/content/Context;)V", QNativeInterface::QAndroidApplication::context()); +#endif + FloweePay::instance()->startP2PInit(); return qapp.exec(); } diff --git a/src/main.cpp b/src/main.cpp index 8b9ae86..70e617a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,6 +88,10 @@ int main(int argc, char *argv[]) .callMethod("isPowerSaveMode", "()Z"); if (powerSaveOn == JNI_TRUE) return 0; + + UserIntent paymentIntent; + setupCallbacks(&paymentIntent); + Periodic periodic; int rc = periodic.run(); QJniObject(QNativeInterface::QAndroidApplication::context()) diff --git a/src/main_utils_android.cpp b/src/main_utils_android.cpp index 12e9df2..f7f4297 100644 --- a/src/main_utils_android.cpp +++ b/src/main_utils_android.cpp @@ -153,7 +153,7 @@ std::unique_ptr handleStaticChain(CommandLineParserData*) return blockheaders; } -void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData *cld) +void loadCompleteHandler(QQmlApplicationEngine &engine, CommandLineParserData * /* cld */) { NetDataProvider *netData = new NetDataProvider(&engine); FloweePay *app = FloweePay::instance(); -- 2.54.0 From b527b2bf1d4be8a56efd95a75a2c9a51aa7fbff8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 24 Apr 2025 19:46:58 +0200 Subject: [PATCH 1332/1428] Improve look of fiat rendering In the case of AlwaysAddSign, the plus or minus is now always the first character of the string. Which is more in line with bookkeeping standards. --- src/PriceDataProvider.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 5748caa..633cbb2 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -194,25 +194,33 @@ QString PriceDataProvider::formattedPrice(int64_t fiatValue, PriceDataProvider:: constexpr const char* Plus = "+"; constexpr const char* Empty = ""; - const char *sign = Empty; + const char *sign1 = Empty; + const char *sign2 = Empty; if (signOption != NoSign) { - if (fiatValue < 0) - sign = Minus; - else if (signOption == AlwaysAddSign) - sign = Plus; + if (fiatValue < 0) { + if (signOption == AlwaysAddSign) + sign1 = Minus; + else + sign2 = Minus; + } + else if (signOption == AlwaysAddSign) { + sign1 = Plus; + } } if (m_displayCents) { - return m_currencySymbolPrefix - % QLatin1String(sign) + return QLatin1String(sign1) + % m_currencySymbolPrefix + % QLatin1String(sign2) % actualPrice % m_decimalPoint % centsPrice.right(2) % m_currencySymbolPost; } else { - return QLatin1String(sign) + return QLatin1String(sign1) % m_currencySymbolPrefix + % QLatin1String(sign2) % actualPrice % m_currencySymbolPost; } -- 2.54.0 From ef674547f3b39ca5649373f720269bd9998e7ab8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 1 May 2025 11:08:49 +0200 Subject: [PATCH 1333/1428] Make the on background service restart on reboot. The on boot complete android feature is tricky, it allows very little in the actual callback and the strategy that works best now is to make that callback schedule a single background run, which will in turn plan the normal schedule. Or insta-exit if the user had no wish to do background running. This additionally also triggers on the my-package-replaced signal in order to ensure that an upgrade doesn't disrupt the background running schedule. --- android/AndroidManifest.xml | 1 + android/java/org/flowee/pay/MainActivity.java | 19 ++++-------- .../java/org/flowee/pay/OnBootHandler.java | 18 ++--------- .../java/org/flowee/pay/PeriodicService.java | 30 +++++++++++++------ src/FloweePay.cpp | 1 - src/Periodic.cpp | 2 ++ 6 files changed, 32 insertions(+), 39 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 62a20f0..de662b1 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -58,6 +58,7 @@ android:exported="true"> + diff --git a/android/java/org/flowee/pay/MainActivity.java b/android/java/org/flowee/pay/MainActivity.java index 19a4335..33c7604 100644 --- a/android/java/org/flowee/pay/MainActivity.java +++ b/android/java/org/flowee/pay/MainActivity.java @@ -17,7 +17,6 @@ */ package org.flowee.pay; -import java.util.Properties; import java.io.*; import org.qtproject.qt.android.bindings.QtActivity; import android.Manifest; @@ -45,6 +44,10 @@ public class MainActivity extends QtActivity // the C++ side calls this after the above native method have been // tied up to the C++ ones. nativesBound = true; + + String perms[] = new String[1]; + perms[0] = Manifest.permission.RECEIVE_BOOT_COMPLETED; + requestPermissions(perms, 3); // 3 is a requestCode } private void seenAction_priv(String action) @@ -139,19 +142,6 @@ public class MainActivity extends QtActivity updatesInterval = hours; PeriodicService.startTimer(this, hours); - // we store the interval in a properties file, only touched by Java code - Properties props = new Properties(); - try { - props.load(new FileInputStream(org.flowee.Pay.UserConfigPath)); - } catch (IOException e) {} - String hoursAsString = Integer.toString(hours); - if (!hoursAsString.equals(props.getProperty(org.flowee.Pay.PropBackgroundInterval))) { - props.setProperty(org.flowee.Pay.PropBackgroundInterval, hoursAsString); - try { - props.store(new FileWriter(org.flowee.Pay.UserConfigPath), null); - } catch (IOException e) {} - } - if (hours > 0 && !isIgnoringBatteryOptimizations()) { // ask to run in the background Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); @@ -182,6 +172,7 @@ public class MainActivity extends QtActivity else if (requestCode == 2) { // Camera (see canUseCamera()) setCameraPermission(grantResults[0] == PackageManager.PERMISSION_GRANTED); } + // 3 is boot completed. It should alwasy be granted (0) } public boolean isPowerSaveMode() diff --git a/android/java/org/flowee/pay/OnBootHandler.java b/android/java/org/flowee/pay/OnBootHandler.java index e46fe61..2a77309 100644 --- a/android/java/org/flowee/pay/OnBootHandler.java +++ b/android/java/org/flowee/pay/OnBootHandler.java @@ -24,21 +24,9 @@ import java.io.FileInputStream; public class OnBootHandler extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - /* On Android boot, re-schedule our perdiodic service - * based on what the C++ side told us is the interval last - * time the entire wallet ran. - */ - try { - Properties props = new Properties(); - props.load(new FileInputStream(org.flowee.Pay.UserConfigPath)); - String hours = props.getProperty(org.flowee.Pay.PropBackgroundInterval); - int h = Integer.parseInt(hours); - if (h > 0) - PeriodicService.startTimer(context, h); - } catch (Exception e) { - // no config, nothing to do. - } + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) + || Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) { + PeriodicService.startSingleTimer(context); } } } diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index 81ee315..dd2b787 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -43,6 +43,19 @@ public class PeriodicService extends QtService { * @param hours when zero, cancel timer, otherwise the interval in hours. */ public static void startTimer(Context context, int hours) + { + long HourInMillis = 60 * 60 * 1000; + + startTimerMs(context, + (hours < 1 ? -1 : hours * HourInMillis), hours * HourInMillis); + } + + public static void startSingleTimer(Context context) + { + startTimerMs(context, 60 * 60 * 1000, 0); + } + + private static void startTimerMs(Context context, long start, long interval) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager == null) @@ -55,18 +68,17 @@ public class PeriodicService extends QtService { PendingIntent pendingIntent = PendingIntent.getService(context, 9875, serviceIntent, PendingIntent.FLAG_IMMUTABLE); - if (hours < 1) { + if (start <= 0) { alarmManager.cancel(pendingIntent); return; } - long HourInMillis = 60 * 60 * 1000; - // Schedule to run approximately every \a hours hour. - alarmManager.setInexactRepeating( - AlarmManager.ELAPSED_REALTIME_WAKEUP, // Use elapsed time, wake device if asleep - SystemClock.elapsedRealtime() + hours * HourInMillis, // first run - hours * HourInMillis, // Repeat interval - pendingIntent - ); + if (interval > 0) { + alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, + start, interval, pendingIntent); + } else { + alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + start, pendingIntent); + } } } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index a382fd0..dc82a20 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1009,7 +1009,6 @@ void FloweePay::broadcastTransaction(const std::shared_ptr &txOwne m_notifications.addConfirmationTx(txOwner->privSegment(), txOwner->txIndex()); } -// notice, setter is in platform specific file. bool FloweePay::backgroundUpdates() const { return m_backgroundUpdates; diff --git a/src/Periodic.cpp b/src/Periodic.cpp index 3884c23..e9a2119 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -52,6 +52,8 @@ int Periodic::run() auto app = FloweePay::instance(); if (app->lockFailed()) return 1; + if (!app->backgroundUpdates()) // nothing to do + return 0; auto blockheaders = handleStaticChain(nullptr); QObject::connect(app, &FloweePay::loadComplete, app, [=]() { -- 2.54.0 From ca08b9d284491650e7a4d1d334e26f5138e7e414 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 19:33:19 +0200 Subject: [PATCH 1334/1428] Move notifications to background page Since notifications no longer make any sense for non-background running situations, the setting should move too. --- guis/mobile/BackgroundSyncConfig.qml | 16 ++++++++++++++-- guis/mobile/Settings.qml | 10 ---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index 7ab948e..00a30cc 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -71,7 +71,7 @@ Page { } onTimeSpanChanged: Pay.backgroundUpdateInterval = timeSpan - visible: checkbox.checked + opacity: checkbox.checked ? 1 : 0 width: parent.width stepSize: 1 from: 0 @@ -112,11 +112,23 @@ Page { radius: implicitWidth / 2 color: Pay.useDarkSkin ? slider.palette.windowText : slider.palette.highlight } + Behavior on opacity { NumberAnimation { } } } Flowee.Label { - visible: checkbox.checked + opacity: slider.opacity anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Every %1 hours").arg(slider.timeSpan) } + + PageTitledBox { + title: qsTr("Notifications") + width: parent.width + Flowee.CheckBox { + width: parent.width + text: qsTr("On Payments", "in relation to notifications") + checked: Pay.externalNotifications + onCheckedChanged: Pay.externalNotifications = checked + } + } } } diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index 80cddc8..1fc5fd8 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -186,16 +186,6 @@ Page { } } } - - PageTitledBox { - title: qsTr("Notifications") - Flowee.CheckBox { - width: parent.width - text: qsTr("Show Payment Notifications") - checked: Pay.externalNotifications - onCheckedChanged: Pay.externalNotifications = checked - } - } } } } -- 2.54.0 From 40a52b6abf1e5dfc8b689e84fbcc3f34a7c62819 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 20:13:09 +0200 Subject: [PATCH 1335/1428] Move 'xpub' out from the backup page. The xpub is not a backup detail, as such it is better to let it live on its own, maybe sharing a better mental model. --- guis/mobile/AccountPageListItem.qml | 160 +++++++++++++++------------- 1 file changed, 85 insertions(+), 75 deletions(-) diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index 4c40258..a3ee7b4 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -71,11 +71,95 @@ QQC2.Control { visible: !root.account.isArchived account: root.account } + TextButton { + text: qsTr("Addresses and keys") + visible: root.account.isHDWallet + enabled: !root.account.needsPinToOpen || root.account.isDecrypted + pageButton: true + onClicked: thePile.push(backupDetails); + } + PageTitledBox { + title: qsTr("Sync Status") + Flowee.Label { + id: syncLabel + width: parent.width + property string time: "" + text: { + if (root.account.isArchived) + return root.account.timeBehind; + + var height = root.account.lastBlockSynched + if (height < 1) + return "" + var time = ""; + if (syncLabel.time !== "") + time = " (" + syncLabel.time + ")"; + return height + " / " + Pay.chainHeight + time; + } + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + + Timer { + // the lastBlockSynchedTime does not change, + // but since we render it as '12 minutes ago' + // we need to actually re-interpret that + // ever so often to keep the relative time. + running: !root.account.isArchived + interval: 30000 // 30 sec + repeat: true + triggeredOnStart: true + onTriggered: { + syncLabel.time = Pay.formatDateTime( + root.account.lastBlockSynchedTime); + } + } + } + } + TextButton { + text: qsTr("xpub") + pageButton: true + subtext: qsTr("To connect this wallet") + visible: root.account.isHDWallet + enabled: !root.account.needsPinToOpen || root.account.isDecrypted + onClicked: thePile.push(xpubComponent); + Component { + id: xpubComponent + Page { + id: detailsPage + headerText: qsTr("Your XPub details") + + ColumnLayout { + width: parent.width + spacing: 20 + Flowee.Label { + id: titleText + text: qsTr("Be careful who you share the xpub with!") + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + + Flowee.QRWidget { + id: seedQr + qrSize: 250 + textVisible: false + useRawString: true + qrText: root.account.xpub + Layout.alignment: Qt.AlignHCenter + } + Flowee.Label { + text: root.account.xpub + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + font.pixelSize: titleText.font.pixelSize * 0.9 + horizontalAlignment: Qt.AlignHCenter + } + } + } + } + } TextButton { text: qsTr("Backup information") pageButton: true - Layout.fillWidth: true enabled: root.account.isDecrypted onClicked: thePile.push(root.account.isHDWallet ? hdBackupDetails : backupDetails); buttonId: 92387 @@ -176,35 +260,6 @@ QQC2.Control { } } - PageTitledBox { - title: qsTr("xpub") - - Item { - implicitHeight: xpubLabel.implicitHeight - width: parent.width - Flowee.LabelWithClipboard { - id: xpubLabel - text: root.account.xpub - width: parent.width - 36 - wrapMode: Text.WrapAnywhere - } - - Image { - width: 20 - height: 20 - anchors.right: parent.right - source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - MouseArea { - anchors.fill: parent - onClicked: { - seedQr.qrText = root.account.xpub - qrPopup.open(); - } - } - } - } - } - Flowee.Label { Layout.fillWidth: true text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile.") @@ -305,51 +360,6 @@ QQC2.Control { } } - TextButton { - text: qsTr("Addresses and keys") - visible: root.account.isHDWallet - enabled: !root.account.needsPinToOpen || root.account.isDecrypted - pageButton: true - Layout.fillWidth: true - onClicked: thePile.push(backupDetails); - } - PageTitledBox { - title: qsTr("Sync Status") - - Flowee.Label { - id: syncLabel - width: parent.width - property string time: "" - text: { - if (root.account.isArchived) - return root.account.timeBehind; - - var height = root.account.lastBlockSynched - if (height < 1) - return "" - var time = ""; - if (syncLabel.time !== "") - time = " (" + syncLabel.time + ")"; - return height + " / " + Pay.chainHeight + time; - } - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - - Timer { - // the lastBlockSynchedTime does not change, - // but since we render it as '12 minutes ago' - // we need to actually re-interpret that - // ever so often to keep the relative time. - running: !root.account.isArchived - interval: 30000 // 30 sec - repeat: true - triggeredOnStart: true - onTriggered: { - syncLabel.time = Pay.formatDateTime( - root.account.lastBlockSynchedTime); - } - } - } - } Flowee.CheckBox { Layout.fillWidth: true visible: !singleAccountSetup && !root.account.isArchived -- 2.54.0 From fc5aa9dc421e37830f3dcb0e153a261a5f1878df Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 22:14:35 +0200 Subject: [PATCH 1336/1428] drop support for Qt6.3 This feature was added in 6.4, which is already very old. --- src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 70e617a..ff7b4cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -160,13 +160,11 @@ int main(int argc, char *argv[]) NewIndicatorProvider newIndicatorProvider; MenuModel menuModel(&modules); QQmlApplicationEngine engine; -#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) // quit on error in the QMLs QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &qapp, [=]() { logFatal() << "QML has errors, aborting on purpose now"; abort(); }, Qt::QueuedConnection); -#endif auto app = FloweePay::instance(); engine.rootContext()->setContextProperty("Pay", app); -- 2.54.0 From 5f1b91de659161c53967a2ddb2e5a27b6cf797ba Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 22:17:43 +0200 Subject: [PATCH 1337/1428] Increase install date default --- src/NewIndicatorProvider.cpp | 2 +- src/NewIndicatorProvider.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/NewIndicatorProvider.cpp b/src/NewIndicatorProvider.cpp index 71d7971..d92a8f2 100644 --- a/src/NewIndicatorProvider.cpp +++ b/src/NewIndicatorProvider.cpp @@ -47,7 +47,7 @@ NewIndicatorProvider::NewIndicatorProvider(QObject *parent) load(); if (m_appInstallDate == 0) { // update this one when a new enum is introduced - m_appInstallDate = NewSince202502; + m_appInstallDate = NewSince202505; m_dirty = true; } } diff --git a/src/NewIndicatorProvider.h b/src/NewIndicatorProvider.h index 24ab1a7..314d1d8 100644 --- a/src/NewIndicatorProvider.h +++ b/src/NewIndicatorProvider.h @@ -43,6 +43,7 @@ private: // Users that started using the software after YYYYMM date won't see a new indicator. NewSince202501, NewSince202502, + NewSince202505, }; int m_appInstallDate = 0; -- 2.54.0 From 25e91abca9f0620b3debdb8619c7b7f17147a867 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 22:18:33 +0200 Subject: [PATCH 1338/1428] New month, new version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index de662b1..ac72040 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="41" android:versionName="2025.05.0"> diff --git a/src/main.cpp b/src/main.cpp index ff7b4cb..bf9e115 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.04.0"); + qapp.setApplicationVersion("2025.05.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 2924d3cca92b7487c6c2ba0f5a3d023fcfebb00d Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 2 May 2025 22:26:58 +0200 Subject: [PATCH 1339/1428] UX tweak; make filter icon fade --- guis/mobile/MainViewBase.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4aa4127..ae8bd1d 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -142,7 +142,8 @@ QQC2.Control { Image { id: filterIcon source: "qrc:/filter-light.svg" - visible: root.showFilterIcon + opacity: root.showFilterIcon ? 1 : 0 + visible: opacity > 0.1 anchors.right: parent.right anchors.rightMargin: 16 anchors.bottom: parent.bottom @@ -203,6 +204,12 @@ QQC2.Control { color: "#fcfcfc" } } + Behavior on opacity { + NumberAnimation { + easing.type: Easing.OutExpo + duration: 250 + } + } } AccountSelectorPopup { -- 2.54.0 From d2d95fa8427aaca05bcee80a6adcd6b49e74a57b Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 19:42:50 +0200 Subject: [PATCH 1340/1428] Fix close icon color For most places we want it to actually follow the dark/light skin --- guis/Flowee/CloseIcon.qml | 8 +++++--- guis/desktop/AccountDetails.qml | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/guis/Flowee/CloseIcon.qml b/guis/Flowee/CloseIcon.qml index 982f5c4..f4d7904 100644 --- a/guis/Flowee/CloseIcon.qml +++ b/guis/Flowee/CloseIcon.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2021 Tom Zander + * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ Item { id: root width: 30 height: 30 + property alias color: line1.color signal clicked; MouseArea { @@ -30,7 +31,8 @@ Item { } Rectangle { - color: "#bbbbbb" + id: line1 + color: Pay.useDarkSkin ? "#bbbbbb" : "#585858" width: 30 height: 3 radius: 3 @@ -38,7 +40,7 @@ Item { anchors.centerIn: parent } Rectangle { - color: "#bbbbbb" + color: line1.color width: 30 height: 3 radius: 3 diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 8dd2ce1..04d3291 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -85,6 +85,7 @@ Item { anchors.margins: 6 anchors.right: parent.right onClicked: accountOverlay.state = "showTransactions" + color: "#bbbbbb" } GridLayout { -- 2.54.0 From 4b143fc0c3d12314add17bfc97aecd691ced6a35 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 19:43:36 +0200 Subject: [PATCH 1341/1428] Show user the concept of background updates. --- guis/mobile/MainViewBase.qml | 79 ++++++++++++++++++++++++++++++++++++ src/FloweePay.cpp | 61 ++++++++++++++++++++++++++++ src/FloweePay.h | 11 ++++- 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index ae8bd1d..c390cc6 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -18,6 +18,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts +import QtQuick.Effects import "../Flowee" as Flowee QQC2.Control { @@ -299,6 +300,84 @@ QQC2.Control { } } + Item { + id: floatingCard + width: mainText.width + 32 + height: 16 + mainText.height + 10 + hideButtonLabel.height + 16 + y: 330 + x: 10 + opacity: Pay.showBGSyncCard ? 1 : 0 + visible: opacity > 0.1 + onXChanged: if (x < -160 || x < 40 - width) visible = false; + + Rectangle { + radius: 5 + width: parent.width - 20 + height: parent.height - 20 + anchors.centerIn: parent + color: palette.window + } + Rectangle { + color: palette.highlight + opacity: 0.15 + anchors.fill: parent + radius: 8 + + // soft shadow + layer.enabled: true + layer.effect: MultiEffect { + blurEnabled: true + blur: 1 + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.AllButtons + hoverEnabled: true // eat all mouse activity + onClicked: (mouseEvent)=> { + floatingCard.opacity = 0; + if (mouseEvent.y > height - hideButtonLabel.height - 26) { + // the hide button was hit. + return; + } + thePile.push("./BackgroundSyncConfig.qml"); + Pay.backgroundUpdates = true; + if (true === Pay.backgroundUpdates) + Pay.externalNotifications = true; + } + } + DragHandler { + id: dragHandler + yAxis.minimum: header.height + yAxis.maximum: root.height - parent.height + xAxis.minimum: -200 + xAxis.maximum: root.width - parent.width + } + + Flowee.Label { + id: mainText + text: qsTr("For a better experience, enable Flowee Pay background synchronization.") + x: 16 + y: 16 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + width: Math.min(160, contentWidth) + } + Flowee.Label { + id: hideButtonLabel + text: qsTr("hide") + color: Pay.useDarkSkin ? "#2079ff" : mainWindow.floweeBlue + anchors.top: mainText.bottom + anchors.topMargin: 10 + anchors.right: parent.right + anchors.rightMargin: 16 + } + + Behavior on opacity { NumberAnimation { } } + } + + /* * Fully encrypted wallet are useless to inspect or use until they are opened. * This is an overlay that covers the entire screen except for the header to make diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index dc82a20..5f166d6 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -75,6 +75,8 @@ constexpr const char *CURRENCY_COUNTRY = "countryCode"; // current constexpr const char *PRIVATE_MODE = "private-mode"; constexpr const char *BACKGROUND_GROUP = "background"; constexpr const char *BG_INTERVAL = "interval"; +constexpr const char *BG_LAST_REMINDER = "last-reminder"; +constexpr const char *BG_NUM_LAUNCHES = "number-of-launches"; constexpr const char *AppdataFilename = "/appdata"; // used for the default wallet @@ -191,6 +193,8 @@ FloweePay::FloweePay() appConfig.beginGroup(BACKGROUND_GROUP); m_backgroundUpdates = appConfig.value("enabled", false).toBool(); m_backgroundUpdateInterval = appConfig.value(BG_INTERVAL, 6).toInt(); + const QDateTime lastReminder = appConfig.value(BG_LAST_REMINDER, QDateTime()).toDateTime(); + const int numLaunches = appConfig.value(BG_NUM_LAUNCHES, 100).toInt(); appConfig.endGroup(); // Update expected chain-height every 5 minutes @@ -259,6 +263,36 @@ FloweePay::FloweePay() m_lockFile.setStaleLockTime(0); m_lockFailed = !m_lockFile.tryLock(1); + // Check if we want to show the background-sync-card (delayed) + QTimer::singleShot(15000, this, [lastReminder, numLaunches]() { + // only when running a GUI + if (qobject_cast(QCoreApplication::instance()) == nullptr) + return; + auto *fp = FloweePay::instance(); + if (fp->backgroundUpdates()) // already on + return; + bool hasContent = false; + for (const auto *wallet : fp->wallets()) { + if (wallet->userOwnedWallet()) { + hasContent = true; + break; + } + } + if (!hasContent) + return; + // Don't bother the user too often with this. + if (lastReminder.isValid() && lastReminder.daysTo(QDateTime::currentDateTimeUtc()) < 6) + return; + if (numLaunches < 10) + return; + fp->setShowBGSyncCard(true); + QSettings appConfig; + appConfig.beginGroup(BACKGROUND_GROUP); + appConfig.setValue(BG_LAST_REMINDER, QDateTime::currentDateTimeUtc()); + appConfig.setValue(BG_NUM_LAUNCHES, 0); + }); + + // forward signals connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); connect (this, &FloweePay::startSaveData_priv, this, [=]() { @@ -476,6 +510,14 @@ void FloweePay::loadingCompleted() // make sure that we'll have a list of indexer services when we // need them later. indexerServices()->populate(); + + // update the launch counter for background reminder + if (!backgroundUpdates()) { + QSettings appConfig; + appConfig.beginGroup(BACKGROUND_GROUP); + const int nl = appConfig.value(BG_NUM_LAUNCHES, 100).toInt(); + appConfig.setValue(BG_NUM_LAUNCHES, nl + 1); + } } } @@ -965,6 +1007,25 @@ void FloweePay::setMnemonicProposals(const QStringList &proposals) emit mnemonicProposalsChanged(); } +bool FloweePay::showBGSyncCard() const +{ + return m_showBGSyncCard; +} + +/* + * Notice that we advice the user interface to only + * initialize the display status from this class, meaning + * that when the user dismisses it the card won't be shown + * again. It will shown exactly once in the runtime of the app. + */ +void FloweePay::setShowBGSyncCard(bool now) +{ + if (m_showBGSyncCard == now) + return; + m_showBGSyncCard = now; + emit showBGSyncCardChanged(); +} + bool FloweePay::externalNotifications() const { return m_notifications.externalNotifications(); diff --git a/src/FloweePay.h b/src/FloweePay.h index 8404e12..b7c4bdf 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -82,6 +82,9 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(bool deviceOffline READ deviceOffline WRITE setDeviceOffline NOTIFY deviceOfflineChanged FINAL) /// if true, supported platforms will try to update also when the app isn't running. Q_PROPERTY(bool backgroundUpdates READ backgroundUpdates WRITE setBackgroundUpdates NOTIFY backgroundUpdatesChanged FINAL) + /// if true, show a card reminding people they want to enable backgroundupdates + Q_PROPERTY(bool showBGSyncCard READ showBGSyncCard NOTIFY showBGSyncCardChanged FINAL) + /// In hours. Ignored if backgroundUpdates is false. Q_PROPERTY(int backgroundUpdateInterval READ backgroundUpdateInterval WRITE setBackgroundUpdateInterval NOTIFY backgroundUpdateIntervalChanged FINAL) @@ -458,6 +461,11 @@ public: bool externalNotifications() const; void setExternalNotifications(bool newExternalNotifications); + /// if true, show a card reminding people they want to enable backgroundupdates + bool showBGSyncCard() const; + void setShowBGSyncCard(bool newShowBGSyncCard); + + signals: void loadComplete(); /// \internal @@ -487,8 +495,8 @@ signals: void backgroundUpdatesChanged(); void backgroundUpdateIntervalChanged(); void mnemonicProposalsChanged(); - void externalNotificationsChanged(); + void showBGSyncCardChanged(); private slots: void loadingCompleted(); @@ -552,6 +560,7 @@ private: bool m_privateMode = false; // wallets marked private are hidden when true bool m_lockFailed = false; bool m_backgroundUpdates = false; + bool m_showBGSyncCard = false; int m_backgroundUpdateInterval = 6; // in hours friend class AccountConfig; -- 2.54.0 From 81360632ba36d428058b146acf2872baf6c8af86 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 21:59:47 +0200 Subject: [PATCH 1342/1428] Make card behavior more smooth on device. With some delays we allow the user interface to show up so the checkbox actually is pained to be 'on' when the user gets a question from the OS about allowing it. --- guis/mobile/BackgroundSyncConfig.qml | 28 +++++++++++- guis/mobile/MainViewBase.qml | 11 ++--- src/FloweePay.cpp | 66 +++++++++++++++------------- src/FloweePay.h | 1 + src/FloweePay_android.cpp | 6 ++- 5 files changed, 72 insertions(+), 40 deletions(-) diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index 00a30cc..41f2ca5 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -24,6 +24,8 @@ Page { headerText: qsTr("Background Synchronization") id: root + property bool autoEnableAll: false + Column { width: parent.width spacing: 10 @@ -38,7 +40,19 @@ Page { id: checkbox text: qsTr("Allow Background Synchronization") checked: Pay.backgroundUpdates - onCheckedChanged: Pay.backgroundUpdates = checked + onCheckedChanged: Pay.backgroundUpdates = checked + onToggled: { + if (checked && root.autoEnableAll) + Pay.externalNotifications = true; + } + + // Make the user-enables-all action be nice and animated to make clear what is going on. + Timer { + running: root.autoEnableAll + interval: 400 + repeat: false + onTriggered: if (!checkbox.checked) checkbox.toggle(); + } } Item { width: 1; height: 10 } // spacer @@ -124,10 +138,22 @@ Page { title: qsTr("Notifications") width: parent.width Flowee.CheckBox { + id: notificationsCheckbox width: parent.width text: qsTr("On Payments", "in relation to notifications") checked: Pay.externalNotifications onCheckedChanged: Pay.externalNotifications = checked + + // Make the user-enables-all only trigger this if the user accepted background sync + Timer { + running: root.autoEnableAll && checkbox.checked + interval: 400 + repeat: false + onTriggered: { + if (checkbox.checked && !notificationsCheckbox.checked) + notificationsCheckbox.toggle(); + } + } } } } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index c390cc6..23c5c00 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -303,7 +303,7 @@ QQC2.Control { Item { id: floatingCard width: mainText.width + 32 - height: 16 + mainText.height + 10 + hideButtonLabel.height + 16 + height: 28 + mainText.height + 10 + hideButtonLabel.height + 16 y: 330 x: 10 opacity: Pay.showBGSyncCard ? 1 : 0 @@ -341,10 +341,7 @@ QQC2.Control { // the hide button was hit. return; } - thePile.push("./BackgroundSyncConfig.qml"); - Pay.backgroundUpdates = true; - if (true === Pay.backgroundUpdates) - Pay.externalNotifications = true; + thePile.push("./BackgroundSyncConfig.qml", { "autoEnableAll": "true" } ); } } DragHandler { @@ -359,10 +356,10 @@ QQC2.Control { id: mainText text: qsTr("For a better experience, enable Flowee Pay background synchronization.") x: 16 - y: 16 + y: 28 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere - width: Math.min(160, contentWidth) + width: Math.min(220, contentWidth) } Flowee.Label { id: hideButtonLabel diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 5f166d6..e5b4a62 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -193,8 +193,6 @@ FloweePay::FloweePay() appConfig.beginGroup(BACKGROUND_GROUP); m_backgroundUpdates = appConfig.value("enabled", false).toBool(); m_backgroundUpdateInterval = appConfig.value(BG_INTERVAL, 6).toInt(); - const QDateTime lastReminder = appConfig.value(BG_LAST_REMINDER, QDateTime()).toDateTime(); - const int numLaunches = appConfig.value(BG_NUM_LAUNCHES, 100).toInt(); appConfig.endGroup(); // Update expected chain-height every 5 minutes @@ -264,34 +262,7 @@ FloweePay::FloweePay() m_lockFailed = !m_lockFile.tryLock(1); // Check if we want to show the background-sync-card (delayed) - QTimer::singleShot(15000, this, [lastReminder, numLaunches]() { - // only when running a GUI - if (qobject_cast(QCoreApplication::instance()) == nullptr) - return; - auto *fp = FloweePay::instance(); - if (fp->backgroundUpdates()) // already on - return; - bool hasContent = false; - for (const auto *wallet : fp->wallets()) { - if (wallet->userOwnedWallet()) { - hasContent = true; - break; - } - } - if (!hasContent) - return; - // Don't bother the user too often with this. - if (lastReminder.isValid() && lastReminder.daysTo(QDateTime::currentDateTimeUtc()) < 6) - return; - if (numLaunches < 10) - return; - fp->setShowBGSyncCard(true); - QSettings appConfig; - appConfig.beginGroup(BACKGROUND_GROUP); - appConfig.setValue(BG_LAST_REMINDER, QDateTime::currentDateTimeUtc()); - appConfig.setValue(BG_NUM_LAUNCHES, 0); - }); - + QTimer::singleShot(15000, this, &FloweePay::maybeShowBackgroundCard); // forward signals connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection); @@ -595,6 +566,37 @@ void FloweePay::saveData() } } +void FloweePay::maybeShowBackgroundCard() +{ + if (m_backgroundUpdates || m_showBGSyncCard) // already on + return; + // only when running a GUI + if (qobject_cast(QCoreApplication::instance()) == nullptr) + return; + bool hasContent = false; + for (const auto *wallet : std::as_const(m_wallets)) { + if (wallet->userOwnedWallet()) { + hasContent = true; + break; + } + } + if (!hasContent) + return; + + // Don't bother the user too often with this. + QSettings appConfig; + appConfig.beginGroup(BACKGROUND_GROUP); + const QDateTime lastReminder = appConfig.value(BG_LAST_REMINDER, QDateTime()).toDateTime(); + const int numLaunches = appConfig.value(BG_NUM_LAUNCHES, 100).toInt(); + if (lastReminder.isValid() && lastReminder.daysTo(QDateTime::currentDateTimeUtc()) < 6) + return; + if (numLaunches < 10) + return; + appConfig.setValue(BG_LAST_REMINDER, QDateTime::currentDateTimeUtc()); + appConfig.setValue(BG_NUM_LAUNCHES, 0); + setShowBGSyncCard(true); +} + void FloweePay::saveAll() { for (auto &wallet : m_wallets) { @@ -1068,6 +1070,7 @@ void FloweePay::broadcastTransaction(const std::shared_ptr &txOwne }); m_notifications.addConfirmationTx(txOwner->privSegment(), txOwner->txIndex()); + QTimer::singleShot(120 * 1000, this, &FloweePay::maybeShowBackgroundCard); } bool FloweePay::backgroundUpdates() const @@ -1120,6 +1123,9 @@ void FloweePay::setNotification(QObject *n) m_notification->deleteLater(); m_notification = n; emit notificationChanged(); + + if (n) // new transaction, suggest background running + QTimer::singleShot(6 * 1000, this, &FloweePay::maybeShowBackgroundCard); } FloweePay::UnlockingKeyboard FloweePay::unlockingKeyboard() const diff --git a/src/FloweePay.h b/src/FloweePay.h index b7c4bdf..8e846b0 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -501,6 +501,7 @@ signals: private slots: void loadingCompleted(); void saveData(); + void maybeShowBackgroundCard(); private: struct AccountConfigData { diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 580e7e5..9fcd6d0 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -89,8 +89,10 @@ FloweePayPrivate::FloweePayPrivate(NotificationManager *nm, FloweePay *qq) this, &FloweePayPrivate::checkOnline); connect (m_parent, &FloweePay::backgroundUpdateIntervalChanged, this, &FloweePayPrivate::followBGSettings); - connect (m_parent, &FloweePay::backgroundUpdatesChanged, - this, &FloweePayPrivate::backgroundUpdaterToggled); + connect (m_parent, &FloweePay::backgroundUpdatesChanged, this, [this]() { + // give it a bit of time to allow UI updates + QTimer::singleShot(350, this, &FloweePayPrivate::backgroundUpdaterToggled); + }); /* first start is a bit complex. * We set the 'deviceOffline' to true so the first time we call -- 2.54.0 From 58239488c74f49416bbc6189601e9a130bd93660 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 22:01:09 +0200 Subject: [PATCH 1343/1428] Minor language fix. --- src/NotificationManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index fa0a06b..cc4f19c 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -193,7 +193,7 @@ QString NotificationManager::describeConfirmations() if (firstMine) return tr("Your transaction got mined", nullptr, txCount); if (numConfirmations > 0) - return tr("Your transaction got %1 confirmations", nullptr, txCount).arg(numConfirmations); + return tr("Your transaction has %1 confirmations", nullptr, txCount).arg(numConfirmations); return tr("Your transaction got another confirmation", nullptr, txCount); } -- 2.54.0 From c7c4d7d35f1e4c420e452852cc6b9bded73aaba4 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 22:07:15 +0200 Subject: [PATCH 1344/1428] UX tweak, make animation faster. --- guis/Flowee/ProgressCheckIcon.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml index 7aee289..d863c11 100644 --- a/guis/Flowee/ProgressCheckIcon.qml +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -59,7 +59,7 @@ Item { return 320; return 10; } - Behavior on sweepAngle { NumberAnimation { duration: 2500 } } + Behavior on sweepAngle { NumberAnimation { duration: 1100 } } Behavior on startAngle { NumberAnimation { } } } } -- 2.54.0 From c8eac60e4942be4a3b12d9f9eaab961dbd50949a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 22:18:41 +0200 Subject: [PATCH 1345/1428] Fix linter issue; tr() needs to be in own class. Otherwise the translations extractor doesn't know what to do with it. --- src/NotificationManager.cpp | 5 +++++ src/NotificationManager.h | 3 +++ src/NotificationManager_android.cpp | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index cc4f19c..1069aac 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -68,6 +68,11 @@ void NotificationManager::setExternalNotifications(bool on) appConfig.setValue(KEY_EXTERNAL_NOTIFICATIONS, m_externalNotifications); } +QString NotificationManager::notificationTitle() const +{ + return tr("New Block Found"); +} + QString NotificationManager::describeCollated(int &txCount) const { const auto data = collatedData(); diff --git a/src/NotificationManager.h b/src/NotificationManager.h index 33fd678..20b5bf2 100644 --- a/src/NotificationManager.h +++ b/src/NotificationManager.h @@ -50,6 +50,9 @@ public: bool externalNotifications() const; void setExternalNotifications(bool on); + /// returns tr("New Block Found"); + QString notificationTitle() const; + public slots: /// visible is set to true if the app is active and not in the background void setApplicationForeground(bool visible); diff --git a/src/NotificationManager_android.cpp b/src/NotificationManager_android.cpp index 8b7e706..60bd088 100644 --- a/src/NotificationManager_android.cpp +++ b/src/NotificationManager_android.cpp @@ -84,7 +84,7 @@ void NotificationManagerPrivate::newBlockSeen() { auto message = q->describeConfirmations(); if (!message.isEmpty()) { - QString title = q->tr("New Block Found"); + QString title = q->notificationTitle(); auto jtitle = QJniObject::fromString(title); auto jmessage = QJniObject::fromString(message); QJniObject::callStaticMethod(ClassName, "setTxConfirmedNotification", -- 2.54.0 From 448de93ac38ad63befb4b277dba1c5c53f9e747d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 22:36:02 +0200 Subject: [PATCH 1346/1428] Add explanation for translator --- src/NotificationManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 1069aac..89f1338 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -196,9 +196,9 @@ QString NotificationManager::describeConfirmations() if (txCount < 1) return QString(); if (firstMine) - return tr("Your transaction got mined", nullptr, txCount); + return tr("Your transaction got mined", "form on number of transactions", txCount); if (numConfirmations > 0) - return tr("Your transaction has %1 confirmations", nullptr, txCount).arg(numConfirmations); - return tr("Your transaction got another confirmation", nullptr, txCount); + return tr("Your transaction has %1 confirmations", "form on number of transactions", txCount).arg(numConfirmations); + return tr("Your transaction got another confirmation", "form on number of transactions", txCount); } -- 2.54.0 From 842e31aa09a82b2ef1b1730a69101a555d879ebe Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 4 May 2025 22:36:44 +0200 Subject: [PATCH 1347/1428] Import Dutch translations from Crowdin --- translations/floweepay-common_nl.ts | 88 ++++++--- translations/floweepay-desktop_nl.ts | 204 ++++++++++---------- translations/floweepay-mobile_nl.ts | 170 +++++++++------- translations/module-bigtransfer_nl.ts | 16 +- translations/module-build-transaction_nl.ts | 18 +- 5 files changed, 273 insertions(+), 223 deletions(-) diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 11fcc8a..144bce1 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -113,7 +113,7 @@ Betaling is verzonden naar: - + Add a personal note Voeg een persoonlijke notitie toe @@ -142,30 +142,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -174,13 +174,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -228,45 +228,79 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) %1 nieuwe transacties in %2 portemonnees gevonden (%3) - + A payment of %1 has been sent Een betaling van %1 is verzonden + + + New Block Found + Nieuw blok gevonden + + + + Transaction between two wallets found. + the wallets are both yours + Transactie voor twee portemonnees gevonden. + - + + We received %1 anonimity transactions. + + We hebben een anonimiteit transactie ontvangen. + We hebben %1 anonimiteit transacties ontvangen. + + + + %1 new transactions found (%2) %1 nieuwe transactie gevonden (%2) %1 nieuwe transacties gevonden (%2) + + + Your transaction got mined + form on number of transactions + + Uw transactie is gedolven + Uw transacties zijn gedolven + + + + + Your transaction has %1 confirmations + form on number of transactions + + Uw transactie heeft %1 bevestigingen + Uw transacties hebben %1 bevestigingen + + + + + Your transaction got another confirmation + form on number of transactions + + Uw transactie kreeg nog een bevestiging + Uw transacties kregen nog een bevestiging + + NotificationManagerPrivate - - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash-blok gemijnd. Hoogte: %1 - - - - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) blok gemijnd: %1 - - - + Mute Negeren - + New Transactions dialog-title diff --git a/translations/floweepay-desktop_nl.ts b/translations/floweepay-desktop_nl.ts index 685f58f..39c0f89 100644 --- a/translations/floweepay-desktop_nl.ts +++ b/translations/floweepay-desktop_nl.ts @@ -54,108 +54,108 @@ Portemonnee Details - + Name Naam - + Sync Status Synchronisatie status - + Encryption Encryptie - - + + Password Wachtwoord - + Open Open - + Invalid PIN Ongeldige PIN - + Include balance in total Saldo opnemen in totaal - + Hide in private mode Verbergen in privémodus - + Address List Adreslijst - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Backup details Back-up details - + Seed-phrase Herstelzin - + Copy Kopieer - + Seed format Herstelzin formaat - + Derivation Derivatie - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Deze portemonnee is beveiligd met een wachtwoord (pin-to-pay). Om de back-upgegevens te zien moet u het wachtwoord invullen. @@ -365,79 +365,79 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Address to import Te importeren adres - - + + New Wallet Name Nieuwe naam Portemonnee - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wanneer ingeschakeld, zullen er geen extra adressen automatisch worden gegenereerd in deze portemonnee. Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + Start Start - + Discover Details online check for wallet details Vind de details - + Derivation Derivatie - + Nothing found for seed. Does it have a password? Niets gevonden voor herstelzin. Behoeft het een wachtwoord? - + Password Wachtwoord - + imported wallet password Wachtwoord geïmporteerde portemonnee @@ -631,7 +631,7 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Warning Waarschuwing @@ -687,65 +687,65 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Verstuur - + Destination Bestemming - + Max available The maximum balance available Max. beschikbaar - + %1 to %2 summary text to pay X-euro to address M %1 aan %2 - + Enter Bitcoin Cash Address Voer Bitcoin Cash adres in - - + + Copy Address Kopieer adres - + Amount Bedrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dit is een verzoek om te betalen aan een BTC-adres, wat een incompatibele munt is. Uw tegoeden konden verloren gaan en Flowee zal geen manier hebben om ze te herstellen. Weet u zeker dat u aan dit BTC-adres wilt betalen? - + Continue Doorgaan - + Cancel Afbreken - + Coin Selector Muntselectie - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -754,78 +754,78 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Total Number of coins Totaal - + Needed Benodigd - + Selected Geselecteerd - + Value Waarde - + Locked coins will never be used for payments. Right-click for menu. Vergrendelde munten worden nooit gebruikt voor betalingen. Rechtsklik voor menu. - + Age Leeftijd - + Unselect All Alles deselecteren - + Select All Alles selecteren - + Unlock coin Ontgrendel munt - + Lock coin Vergrendel munt - + Public-comment Publiek commentaar - + Add a comment you want to include in the transaction, visible for everyone. Voeg een opmerking toe die u wilt opnemen in de transactie, zichtbaar voor iedereen. - + Custom message, to be included in the transaction. Speciaal bericht dat wordt opgenomen in de transactie. - + Text Tekst - + Size Grootte @@ -848,57 +848,47 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. Toon Bitcoin Cash waarde op de activiteitenpagina - - Show Block Notifications - Toon blok notificaties - - - - When a new block is mined, Flowee Pay shows a desktop notification - Wanneer een nieuw blok is gedolven, toont Flowee Pay een desktop notificatie - - - + Night Mode Nachtmodus - + Private Mode Privé modus - + Hides private wallets while enabled Verbergt privé portemonnees - + Font sizing Lettertypegrootte - + Version Versie - + Library Version Bibliotheek versie - + Synchronization Synchronisatie - + Network Status Netwerk Status - + Address Stats Adres statistieken @@ -1239,95 +1229,95 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. main - + Activity Activiteit - + Archived wallets do not check for activities. Balance may be out of date. Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd. - + Unarchive Uit archief halen - + This wallet needs a password to open. Deze portemonnee heeft een wachtwoord nodig om te openen. - + Password: Wachtwoord: - + Invalid password Ongeldig wachtwoord - + Open Open - + Send Versturen - + Receive Ontvangen - + Balance Saldo - + Main balance (money), non specified Algemeen - + Unconfirmed balance (money) Onbevestigd - + Immature balance (money) Ongerijpt - + 1 BCH is: %1 1 BCH is: %1 - + Network status Netwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash portemonnee toevoegen - + Archived wallets [%1] Arg is wallet count @@ -1336,12 +1326,12 @@ Wisselgeld zal teruggestort worden op de geïmporteerde sleutel. - + Preparing... Voorbereiden... - + QR-Scan QR-Scannen diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index 65ab760..e8138be 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -76,131 +76,146 @@ Gearchiveerde portemonnees controleren niet op activiteiten. Saldo is mogelijk verouderd - + + To connect this wallet + Om deze portemonnee te koppelen + + + + Your XPub details + Uw XPub details + + + + Be careful who you share the xpub with! + Wees voorzichtig met wie u de xpub deelt! + + + Backup information Back-up Informatie - + Backup Details Back-up gegevens - + Wallet seed-phrase Herstelzin opslaan - + Password Wachtwoord - + Seed format Herstelzin formaat - - + + Starting Height height refers to block-height Beginhoogte - + Derivation Path Derivatie pad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Schrijf de herstelzin op papier, in de juiste volgorde, samen met het derivatie pad. Deze herstelzin stelt u in staat om uw portemonnee te herstellen in geval van een computerfout. - + <b>Important</b>: Never share your seed-phrase with others! <b>Belangrijk</b>: Deel nooit uw herstelzin met anderen! - + Wallet keys Sleutels van portemonnee - + Show Index toggle to show numbers Toon Index - + Change Addresses Wisselgeldadres - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Schakelt tussen adressen waar anderen je op kunnen betalen, en adressen welke de portemonnee voor wisselgeld gebruikt. - + Used Addresses Gebruikte adressen - + Switches between unused and used Bitcoin addresses Schakelt tussen ongebruikt en gebruikte Bitcoin adressen - + Addresses and keys Adressen en sleutels - + Sync Status Synchronisatie status - + Hide balance in overviews Balans in overzichten verbergen - + Hide in private mode Verbergen in privémodus - + Unarchive Wallet Portemonnee De-archiveren - + Archive Wallet Portemonnee Archiveren - + Really Delete? Echt verwijderen? - + Removing wallet "%1" can not be undone. argument is the wallet name Verwijderen van portemonnee "%1" kan niet ongedaan worden gemaakt. - + Remove Wallet Portemonnee verwijderen @@ -282,20 +297,31 @@ Achtergrond synchronisatie - + Without background synchronization your wallets will not see new transactions until you open Flowee Pay Zonder achtergrond synchronisatie zien uw portemonnees geen nieuwe transacties totdat u Flowee Pay opent - + Allow Background Synchronization Achtergrond synchronisatie toestaan - + Every %1 hours Iedere %1 uur + + + Notifications + Notificaties + + + + On Payments + in relation to notifications + Op betalingen + CurrencySelector @@ -361,84 +387,84 @@ Geheim als tekst - + Unknown word(s) found Onbekende woord(en) gevonden - + Next Volgende - + Address to import Te importeren adres - - + + New Wallet Name Nieuwe naam Portemonnee - + Force Single Address Forceer één adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Als ingeschakeld zullen er geen extra adressen automatisch toegevoegd worden in deze portemonnee. Wisselgeld gaat weer naar de geïmporteerde sleutel. - - + + Oldest Transaction Oudste transactie - + Check Age online check for wallet age Leeftijd opzoeken - + Nothing found for wallet Niets gevonden voor portemonnee - - + + Start Start - + Discover Details online check for wallet details Vind de details - + Derivation Path Derivatie pad - + Nothing found for seed. Does it have a password? Niets gevonden voor herstelzin. Behoeft het een wachtwoord? - + Password Wachtwoord - + imported wallet password Wachtwoord geïmporteerde portemonnee @@ -446,12 +472,12 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. InstaPayConfigButton - + Enable Instant Pay Direct Betalen inschakelen - + Configure Instant Pay Configureer Direct Betalen @@ -565,10 +591,20 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. MainViewBase - + No Internet Available Geen Internet gevonden + + + For a better experience, enable Flowee Pay background synchronization. + Schakel Flowee Pay achtergrond synchronisatie in voor een betere ervaring. + + + + hide + verberg + MenuOverlay @@ -752,7 +788,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -787,7 +823,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt PriceInputWidget - + All Currencies Alle valuta's @@ -805,12 +841,12 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Betaler moet deze QR lezen - + Encrypted Wallet Versleutelde portemonnee - + Import Running... Bezig met importeren... @@ -820,13 +856,13 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Omschrijving - + Address Bitcoin Cash address Adres - + Clear Wissen @@ -993,16 +1029,6 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Light Licht - - - Notifications - Notificaties - - - - On new block found - Bij nieuw gevonden blok - SlideToApprove @@ -1245,32 +1271,32 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt TransactionListItem - + Miner Reward Miner Beloning - + Fused Gefuseerd - + Received Ontvangen - + Moved Verschoven - + Sent Verzonden - + Rejected Afgewezen diff --git a/translations/module-bigtransfer_nl.ts b/translations/module-bigtransfer_nl.ts index 8fb4ba7..4557aa9 100644 --- a/translations/module-bigtransfer_nl.ts +++ b/translations/module-bigtransfer_nl.ts @@ -29,32 +29,32 @@ Portemonnee naar Portemonnee - + Select two wallets to transfer funds simply, using anonimity preserving transactions. Selecteer twee portemonnees om geld over te maken, met behulp van anonimiteit behoudende transacties. - - Spending Wallet - Uitgave portemonnee + + Select Spending Wallet + Selecteer start portemonnee - + Addresses Adressen - + Coins Munten - + Destination Wallet Bestemming portemonnee - + Prepare... Bereid voor... diff --git a/translations/module-build-transaction_nl.ts b/translations/module-build-transaction_nl.ts index 99efac6..a736afe 100644 --- a/translations/module-build-transaction_nl.ts +++ b/translations/module-build-transaction_nl.ts @@ -84,7 +84,7 @@ - + Add Destination Voeg bestemming toe @@ -136,44 +136,44 @@ %1 sat/byte - + Destination Bestemming - + unset indication of desination not being set niet ingesteld - + invalid address is not correct ongeldig - + Copy Address Kopieer adres - + Drag to Edit Sleep om te bewerken - + Drag to Delete Sleep om te verwijderen - + Unlock Wallet Portemonnee ontgrendelen - + Prepare Payment... Betaling voorbereiden... -- 2.54.0 From d49acd1d08331a4dd0ab0c1205b7ee49e9a73459 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 5 May 2025 14:08:50 +0200 Subject: [PATCH 1348/1428] Play with starting fiat feed downloads We move the starting of the download optimistically to be earlier in the boot process. This also moves the actual action out of the singleton, which gives more control over when to call it based on how the app is started. Specifically the headless way of running Pay now waits for the price feed to have been updated before starting the p2p net interactions, ensuring that any new transactions will be able to be shown to the user including the fiat price. --- src/FloweePay.cpp | 4 ++-- src/FloweePay_android.cpp | 3 ++- src/FloweePay_basic.cpp | 3 --- src/Periodic.cpp | 42 ++++++++++++++++++++++++++++++++++----- src/Periodic.h | 13 +++++++++++- src/headless.cpp | 2 +- src/main.cpp | 13 ++++++++---- 7 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index e5b4a62..f0bc8da 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -189,6 +189,8 @@ FloweePay::FloweePay() m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool(); m_privateMode = appConfig.value(PRIVATE_MODE, false).toBool(); m_prices.reset(new PriceDataProvider(appConfig.value(CURRENCY_COUNTRY).toString())); + if (m_chain == P2PNet::MainChain) + m_prices->loadPriceHistory(m_basedir); m_unlockingKeyboard = static_cast(appConfig.value(KEYBOARD4UNLOCK, m_unlockingKeyboard).toInt()); appConfig.beginGroup(BACKGROUND_GROUP); m_backgroundUpdates = appConfig.value("enabled", false).toBool(); @@ -470,8 +472,6 @@ void FloweePay::loadingCompleted() for (auto wallet : std::as_const(m_wallets)) { wallet->performUpgrades(); } - if (m_chain == P2PNet::MainChain) - m_prices->loadPriceHistory(m_basedir); setupPlatform(); m_loadingCompleted = true; emit loadComplete(); diff --git a/src/FloweePay_android.cpp b/src/FloweePay_android.cpp index 9fcd6d0..aad4141 100644 --- a/src/FloweePay_android.cpp +++ b/src/FloweePay_android.cpp @@ -157,7 +157,8 @@ void FloweePayPrivate::checkOnline() if (wasOffline && !isOffline) { // came online m_parent->startNet(); if (m_parent->chain() == P2PNet::MainChain - && !m_parent->isOffline() && !m_parent->deviceOffline()) + && !m_parent->isOffline() && !m_parent->deviceOffline() + && m_parent->prices()->oldData()) // avoid running it too often. m_parent->prices()->start(); } m_checkOfflineTimer->stop(); diff --git a/src/FloweePay_basic.cpp b/src/FloweePay_basic.cpp index f5ef031..e67580b 100644 --- a/src/FloweePay_basic.cpp +++ b/src/FloweePay_basic.cpp @@ -34,9 +34,6 @@ bool fp_platformSkinDark() void FloweePay::setupPlatform() { - if (m_chain == P2PNet::MainChain && !m_offline && !m_deviceOffline) - m_prices->start(); - static boost::asio::system_timer *g_lockTimer = nullptr; auto *gui = qobject_cast(QCoreApplication::instance()); if (gui) { diff --git a/src/Periodic.cpp b/src/Periodic.cpp index e9a2119..a69ec11 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -41,7 +41,7 @@ Periodic::~Periodic() ECC_Stop(); } -int Periodic::run() +int Periodic::run(RunType rt) { int argc = 0; char *argv[] = { nullptr }; @@ -52,9 +52,24 @@ int Periodic::run() auto app = FloweePay::instance(); if (app->lockFailed()) return 1; - if (!app->backgroundUpdates()) // nothing to do + if (rt == FollowUserOptions && !app->backgroundUpdates()) // nothing to do return 0; + /* + * we async fetch the price if needed, and in run2() we won't start the actual + * download until we have the current price + */ + auto priceDataProvider = app->prices(); + assert(priceDataProvider); + if (priceDataProvider->oldData()) { + priceDataProvider->start(); + connect (priceDataProvider, &PriceDataProvider::priceChanged, this, &Periodic::confirmFiat); + // price-fetching watchdog timer. + QTimer::singleShot(65 * 1000, this, &Periodic::confirmFiat); + } else { + m_haveFiatFeed = true; + } + auto blockheaders = handleStaticChain(nullptr); QObject::connect(app, &FloweePay::loadComplete, app, [=]() { auto app = FloweePay::instance(); @@ -70,12 +85,13 @@ int Periodic::run() } QObject::connect(app, &FloweePay::blockHeightCertaintyChanged, this, &Periodic::checkFinished); - app->startNet(); // lets go! - app->prices()->start(); + m_walletInitFinished = true; + + run2(); }); // watchdog timer. - QTimer::singleShot(120 * 1000, &qapp, []() { // no more than 2 minuts + QTimer::singleShot(120 * 1000, &qapp, []() { // no more than 2 minutes // Watchdog kicked in, shutting down QCoreApplication::quit(); }); @@ -89,6 +105,22 @@ int Periodic::run() return qapp.exec(); } +void Periodic::run2() +{ + if (!m_haveFiatFeed || !m_walletInitFinished) + return; + + FloweePay::instance()->startNet(); // lets go! +} + +void Periodic::confirmFiat() +{ + if (!m_haveFiatFeed) { + m_haveFiatFeed = true; + run2(); + } +} + void Periodic::checkFinished() { auto *app = FloweePay::instance(); diff --git a/src/Periodic.h b/src/Periodic.h index 6b4f0c9..e6e0186 100644 --- a/src/Periodic.h +++ b/src/Periodic.h @@ -25,14 +25,25 @@ class Periodic : public QObject { public: + enum RunType { + ForceRun, + FollowUserOptions + }; Periodic(); ~Periodic(); - int run(); + int run(RunType rt = FollowUserOptions); + +private slots: + void run2(); + void confirmFiat(); private: void checkFinished(); std::unique_ptr m_verifyHandle; + + bool m_haveFiatFeed = false; + bool m_walletInitFinished = false; }; #endif diff --git a/src/headless.cpp b/src/headless.cpp index fe19701..945436b 100644 --- a/src/headless.cpp +++ b/src/headless.cpp @@ -19,7 +19,7 @@ int main(int, char **) { Periodic periodic; - return periodic.run(); + return periodic.run(Periodic::ForceRun); } class ModuleManager; diff --git a/src/main.cpp b/src/main.cpp index bf9e115..c54022b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -196,19 +196,24 @@ int main(int argc, char *argv[]) #endif "/main.qml"); - PortfolioDataProvider *portfolio = nullptr; - + PortfolioDataProvider *portfolio = nullptr; // avoid it being created more than once. auto blockheaders = handleStaticChain(cld); QObject::connect(FloweePay::instance(), &FloweePay::loadComplete, &engine, [&engine, &cld, &portfolio]() { if (portfolio == nullptr) { // the portfolio can only be properly constructed when the init completed portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("portfolio", portfolio); + } - if (FloweePay::instance()->appProtection() != FloweePay::AppPassword) + auto app = FloweePay::instance(); + if (app->appProtection() != FloweePay::AppPassword) { + if (app->chain() == P2PNet::MainChain && !app->deviceOffline() && !app->isOffline()) + app->prices()->start(); loadCompleteHandler(engine, cld); + } }); - FloweePay::instance()->startP2PInit(); + + app->startP2PInit(); // We ship our own font to not have to depend on the host system's installed fonts -- 2.54.0 From e02ff80a944567397842559afc3f3ed7d1131b63 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 6 May 2025 15:16:14 +0200 Subject: [PATCH 1349/1428] Import German translations from crowdin --- translations/floweepay-common_de.ts | 96 ++++++--- translations/floweepay-desktop_de.ts | 224 ++++++++++---------- translations/floweepay-mobile_de.ts | 170 ++++++++------- translations/module-bigtransfer_de.ts | 16 +- translations/module-build-transaction_de.ts | 18 +- translations/module-send-sweep_de.ts | 6 +- 6 files changed, 290 insertions(+), 240 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 7612ef6..3d0a5f0 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -113,7 +113,7 @@ Die Zahlung wurde gesendet an: - + Add a personal note Eine persönliche Notiz hinzufügen @@ -142,30 +142,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -174,13 +174,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -228,45 +228,79 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) - %1 neue Transaktionen über %2 Wallets gefunden (%3) + %1 neue Transaktionen über %2 Geldbörsen gefunden (%3) - + A payment of %1 has been sent Eine Zahlung von %1 wurde gesendet + + + New Block Found + Neuen Block gefunden + + + + Transaction between two wallets found. + the wallets are both yours + Transaktion zwischen zwei Geldbörsen gefunden. + - + + We received %1 anonimity transactions. + + Wir haben %1 anonyme Transaktion erhalten. + Wir haben %1 anonyme Transaktionen erhalten. + + + + %1 new transactions found (%2) %1 neue Transaktion gefunden (%2) %1 neue Transaktionen gefunden (%2) + + + Your transaction got mined + form on number of transactions + + Ihre Transaktion wurde bestätigt + Ihre Transaktionen wurden bestätigt + + + + + Your transaction has %1 confirmations + form on number of transactions + + Ihre Transaktion hat %1 Bestätigung + Ihre Transaktion hat %1 Bestätigungen + + + + + Your transaction got another confirmation + form on number of transactions + + Ihre Transaktion hat eine weitere Bestätigung erhalten + Ihre Transaktionen haben eine weitere Bestätigung erhalten + + NotificationManagerPrivate - - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash Block abgebaut. Höhe: %1 - - - - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) Block erstellt: %1 - - - + Mute Stummschalten - + New Transactions dialog-title @@ -486,9 +520,9 @@ Coins a / b a) active coin-count. b) historical coin-count. - Coins a / b - a) aktive Coin-Anzahl. - b) historische Coin-Anzahl. + Münzen a / b + a) aktive Münzen-Anzahl. + b) historische Münzen-Anzahl. diff --git a/translations/floweepay-desktop_de.ts b/translations/floweepay-desktop_de.ts index f7b0c56..68fec97 100644 --- a/translations/floweepay-desktop_de.ts +++ b/translations/floweepay-desktop_de.ts @@ -54,108 +54,108 @@ Geldbörsen Details - + Name Name - + Sync Status Sync Status - + Encryption Verschlüsselung - - + + Password Passwort - + Open Öffnen - + Invalid PIN Ungültige PIN - + Include balance in total Inkludiere Guthaben in Gesamtsumme - + Hide in private mode Im privaten Modus ausblenden - + Address List Adressliste - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen, auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen, die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses Wechselt zwischen ungenutzten und genutzten Bitcoin-Adressen - + Backup details Backup-Details - + Seed-phrase Seed-Phrase - + Copy Kopieren - + Seed format Seed-Format - + Derivation Ableitung - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse im Falle eines Computerfehlers wiederherstellen. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Diese Geldbörse ist passwortgeschützt (pin-to-pay). Um die Backup-Details zu sehen, müssen Sie das Passwort angeben. @@ -365,79 +365,79 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussGeheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Address to import Zu importierende Adresse - - + + New Wallet Name Name der neuen Geldbörse - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + Start Start - + Discover Details online check for wallet details Finde Details - + Derivation Ableitung - + Nothing found for seed. Does it have a password? Nichts für Seed gefunden. Hat es ein Passwort? - + Password Passwort - + imported wallet password Passwort der zu importierenden Geldbörse @@ -516,12 +516,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Coin Selector - Coin Auswahl + Münzen Auswahl To override the default selection of coins that are used to pay a transaction, you can add the 'Coin Selector' where the wallets coins will be made visible. - Um die Standardauswahl von Coins zu überschreiben, die zur Zahlung einer Transaktion verwendet werden, können Sie den 'Coin Selektor' hinzufügen, in dem die Coins der Geldbörse sichtbar gemacht werden. + Um die Standardauswahl von Münzen zu überschreiben, die zur Zahlung einer Transaktion verwendet werden, können Sie die 'Münzen Auswahl' hinzufügen, indem die Münzen der Geldbörse sichtbar gemacht werden. @@ -579,7 +579,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Instant payment failed. Wait for confirmation. (double spent proof received) - Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Proof erhalten) + Sofortige Zahlung fehlgeschlagen. Warten Sie auf Bestätigung. (Double-Spend Beweis erhalten) @@ -631,7 +631,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Warning Warnung @@ -687,145 +687,145 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Senden - + Destination Ziel - + Max available The maximum balance available Max. verfügbar - + %1 to %2 summary text to pay X-euro to address M %1 zu %2 - + Enter Bitcoin Cash Address Geben Sie eine Bitcoin Cash Adresse ein - - + + Copy Address Adresse kopieren - + Amount Betrag - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Dies ist eine BTC-Adresse, welche eine inkompatible Währung ist. Ihr Guthaben könnte verloren gehen und Flowee wird keine Möglichkeit haben, es wiederherzustellen. Sind Sie sicher, dass dies die richtige Adresse ist? - + Continue Fortfahren - + Cancel Abbrechen - + Coin Selector - Coin Selektor + Münzen Auswahl - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins - %1 %2 in %3 Coins ausgewählt - %1 %2 in %3 Coins ausgewählt + %1 %2 von %3 Münzen ausgewählt + %1 %2 von %3 Münzen ausgewählt - + Total Number of coins Gesamt - + Needed Benötigt - + Selected Ausgewählt - + Value Wert - + Locked coins will never be used for payments. Right-click for menu. - Gesperrte Coins werden nie für Zahlungen verwendet. Rechtsklick für Menü. + Gesperrte Münzen werden nie für Zahlungen verwendet. Rechtsklick für Menü. - + Age Alter - + Unselect All Alles abwählen - + Select All Alles auswählen - + Unlock coin - Coin entsperren + Münze entsperren - + Lock coin - Coin sperren + Münze sperren - + Public-comment Öffentlicher Kommentar - + Add a comment you want to include in the transaction, visible for everyone. Fügen Sie einen Kommentar hinzu, den Sie in die Transaktion einfügen möchten, sichtbar für alle. - + Custom message, to be included in the transaction. Benutzerdefinierte Nachricht, die in die Transaktion aufgenommen werden soll. - + Text Text - + Size Größe @@ -848,57 +848,47 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Bitcoin Cash Wert auf der Aktivitätsseite anzeigen - - Show Block Notifications - Zeige Blockbenachrichtigungen - - - - When a new block is mined, Flowee Pay shows a desktop notification - Wenn ein neuer Block erzeugt wird, zeigt Flowee Pay eine Desktop-Benachrichtigung - - - + Night Mode Nachtmodus - + Private Mode Privater Modus - + Hides private wallets while enabled Versteckt private Geldbörsen, wenn aktiviert - + Font sizing Schriftgröße - + Version Version - + Library Version Version der Bibliothek - + Synchronization Synchronisation - + Network Status Netzwerkstatus - + Address Stats Adressstatistik @@ -1052,7 +1042,7 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Mined - Gemined + Bestätigt @@ -1239,95 +1229,95 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. main - + Activity Aktivität - + Archived wallets do not check for activities. Balance may be out of date. Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein. - + Unarchive Entarchivieren - + This wallet needs a password to open. Diese Geldbörse benötigt ein Passwort zum Öffnen. - + Password: Passwort: - + Invalid password Ungültiges Passwort - + Open Öffnen - + Send Senden - + Receive Empfangen - + Balance Guthaben - + Main balance (money), non specified Haupt - + Unconfirmed balance (money) Unbestätigt - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH ist: %1 - + Network status Netzwerkstatus - + Offline Offline - + Add Bitcoin Cash wallet Bitcoin Cash-Geldbörse hinzufügen - + Archived wallets [%1] Arg is wallet count @@ -1336,12 +1326,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. - + Preparing... Wird vorbereitet... - + QR-Scan QR-Scan diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index f0c9814..00ebd8a 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -76,131 +76,146 @@ Archivierte Geldbörsen überprüfen nicht auf Aktivitäten. Das Guthaben kann veraltet sein - + + To connect this wallet + Um diese Geldbörse zu verbinden + + + + Your XPub details + Ihre XPub-Details + + + + Be careful who you share the xpub with! + Seien Sie vorsichtig, mit wem Sie den XPub teilen! + + + Backup information Backup-Informationen - + Backup Details Backup-Details - + Wallet seed-phrase Geldbörsen-Seed-Phrase - + Password Passwort - + Seed format Seed-Format - - + + Starting Height height refers to block-height Starthöhe - + Derivation Path Ableitungspfad - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Bitte speichern Sie die Seed-Phrase in der richtigen Reihenfolge mit dem Ableitungspfad. Mit diesem Seed können Sie Ihre Geldbörse wiederherstellen, falls Sie Ihr Handy verlieren. - + <b>Important</b>: Never share your seed-phrase with others! <b>Wichtig</b>: Teilen Sie Ihre Seed-Phrase nie mit anderen! - + Wallet keys Geldbörsen-Schlüssel - + Show Index toggle to show numbers Index anzeigen - + Change Addresses Wechselgeldadressen - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Wechselt zwischen Adressen auf welchen Sie von anderen Leuten bezahlt werden können, und Adressen die die Geldbörse verwendet, um Wechselgeld an sich selbst zu senden. - + Used Addresses Benutzte Adressen - + Switches between unused and used Bitcoin addresses Wechselt zwischen ungenutzten und genutzten Bitcoin-Adressen - + Addresses and keys Adressen und Schlüssel - + Sync Status Synchronisierungsstatus - + Hide balance in overviews Guthaben in Übersichten ausblenden - + Hide in private mode Im privaten Modus ausblenden - + Unarchive Wallet Geldbörse entarchivieren - + Archive Wallet Geldbörse archivieren - + Really Delete? Wirklich löschen? - + Removing wallet "%1" can not be undone. argument is the wallet name Die Löschung der Geldbörse "%1" kann nicht rückgängig gemacht werden. - + Remove Wallet Geldbörse entfernen @@ -282,20 +297,31 @@ Hintergrund-Synchronisierung - + Without background synchronization your wallets will not see new transactions until you open Flowee Pay Ohne Hintergrund-Synchronisierung werden Ihre Brieftaschen erst dann neue Transaktionen sehen, wenn Sie Flowee Pay öffnen - + Allow Background Synchronization Hintergrund-Synchronisierung erlauben - + Every %1 hours Alle %1 Stunden + + + Notifications + Benachrichtigungen + + + + On Payments + in relation to notifications + Bei Zahlungen + CurrencySelector @@ -361,84 +387,84 @@ Geheimnis als Text - + Unknown word(s) found Unbekannte(s) Wort(e) gefunden - + Next Weiter - + Address to import Zu importierende Adresse - - + + New Wallet Name Neuer Geldbörsen Name - + Force Single Address Erzwinge einzelne Adresse - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Wenn aktiviert, werden keine zusätzlichen Adressen automatisch in dieser Geldbörse generiert. Wechselgeld wird zum importierten Schlüssel zurückgeführt. - - + + Oldest Transaction Älteste Transaktion - + Check Age online check for wallet age Überprüfe Alter - + Nothing found for wallet Nichts gefunden für Geldbörse - - + + Start Start - + Discover Details online check for wallet details Entdecke Details - + Derivation Path Ableitungspfad - + Nothing found for seed. Does it have a password? Nichts für Seed gefunden. Hat es ein Passwort? - + Password Passwort - + imported wallet password importiertes Geldbörsen-Passwort @@ -446,12 +472,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. InstaPayConfigButton - + Enable Instant Pay Sofortzahlung aktivieren - + Configure Instant Pay Sofortzahlung konfigurieren @@ -565,10 +591,20 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. MainViewBase - + No Internet Available Keine Internetverbindung + + + For a better experience, enable Flowee Pay background synchronization. + Für eine bessere Erfahrung, aktivieren Sie Flowee Pay Hintergrundsynchronisation. + + + + hide + ausblenden + MenuOverlay @@ -752,7 +788,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussZieladresse - + Unlock Wallet Geldbörse entsperren @@ -787,7 +823,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss PriceInputWidget - + All Currencies Alle Währungen @@ -805,12 +841,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussDiesen QR teilen um zu empfangen - + Encrypted Wallet Verschlüsselte Geldbörse - + Import Running... Import läuft... @@ -820,13 +856,13 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBeschreibung - + Address Bitcoin Cash address Adresse - + Clear Leeren @@ -993,16 +1029,6 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussLight Hell - - - Notifications - Benachrichtigungen - - - - On new block found - Bei neu gefundenem Block - SlideToApprove @@ -1245,32 +1271,32 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden muss TransactionListItem - + Miner Reward Miner Belohnung - + Fused Fusioniert - + Received Erhalten - + Moved Verschoben - + Sent Gesendet - + Rejected Abgelehnt diff --git a/translations/module-bigtransfer_de.ts b/translations/module-bigtransfer_de.ts index b897de9..47b626f 100644 --- a/translations/module-bigtransfer_de.ts +++ b/translations/module-bigtransfer_de.ts @@ -29,32 +29,32 @@ Geldbörse zu Geldbörse - + Select two wallets to transfer funds simply, using anonimity preserving transactions. Wähle zwei Geldbörsen, um Guthaben zu übertragen, mittels Verwendung von Transaktionen, die die Anonymität wahren. - - Spending Wallet - Zahlende Geldbörse + + Select Spending Wallet + Ausgaben-Geldbörse auswählen - + Addresses Adressen - + Coins Münzen - + Destination Wallet Empfangende Geldbörse - + Prepare... Vorbereiten... diff --git a/translations/module-build-transaction_de.ts b/translations/module-build-transaction_de.ts index 1ad5c5b..43aba19 100644 --- a/translations/module-build-transaction_de.ts +++ b/translations/module-build-transaction_de.ts @@ -84,7 +84,7 @@ - + Add Destination Empfänger hinzufügen @@ -136,44 +136,44 @@ %1 Sat/Byte - + Destination Empfänger - + unset indication of desination not being set nicht eingestellt - + invalid address is not correct ungültig - + Copy Address Adresse kopieren - + Drag to Edit Ziehen zum Bearbeiten - + Drag to Delete Ziehen zum Löschen - + Unlock Wallet Geldbörse entsperren - + Prepare Payment... Zahlung vorbereiten... diff --git a/translations/module-send-sweep_de.ts b/translations/module-send-sweep_de.ts index 1675583..b01d98b 100644 --- a/translations/module-send-sweep_de.ts +++ b/translations/module-send-sweep_de.ts @@ -6,7 +6,7 @@ Sweep coins - Coins einlesen + Münzen einlesen @@ -18,8 +18,8 @@ Found %1 coins on address. this is a simple number - %1 Coin auf Adresse gefunden. - %1 Coins auf Adresse gefunden. + %1 Münze auf Adresse gefunden. + %1 Münzen auf Adresse gefunden. -- 2.54.0 From 176ea40ee67a79912e94a54e99358e824d1b276a Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 12:32:03 +0200 Subject: [PATCH 1350/1428] Make sure we realize a series of transactions types We no longer throw away details when a longer list of transactions is combined into one. This means that we can easily detect anonimity transactions even if we get more then one coming in, which makes the notification text more appropriate as a result. --- src/FloweePay.cpp | 4 +++- src/NotificationManager.cpp | 43 +++++++++++++++++++++---------------- src/Wallet.cpp | 4 ++-- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index f0bc8da..5b86066 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -330,8 +330,10 @@ FloweePay *FloweePay::instance() return &s_app; } +// static void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ification) { + assert(notification.walletTxIds.size() == 1); auto *me = FloweePay::instance(); auto configs = me->m_accountConfigs; auto i = configs.find(notification.privacySegment); @@ -355,7 +357,7 @@ void FloweePay::sendTransactionNotification(const P2PNet::Notification ¬ifica int flagData = 0; pool->parse(in.size()).bind(1, &flagData); QFlags flags = QFlags::fromInt(flagData); - if (!wallet->transactionMatch(flags, notification.walletTxId)) + if (!wallet->transactionMatch(flags, notification.walletTxIds.back())) return; } break; diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 89f1338..b9397cc 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -81,25 +81,30 @@ QString NotificationManager::describeCollated(int &txCount) const return QString(); /* - * The basic feature is simple; a given number of wallets (data.size()) have each - * seen a certain amount of bch added or subtracted. - * - * The common case, however, is that only one transaction is seen at a time. - * (Even if it might touch two wallets). - * As such we do some checks for the common cases in order to have a nicer explanation - * of what happened to the user. - * - * 1. We detect one transaction touching two wallets. - * 2. We see (one or more) transactions arrive in a wallet that have special markers - * we know about. - */ + * The basic feature is simple; a given number of wallets (data.size()) have + * each seen a certain amount of bch added or subtracted. + * + * The common case, however, is that only one transaction is seen at a time. + * (Even if it might touch two wallets). + * As such we do some checks for the common cases in order to have a nicer + * explanation of what happened to the user. + * + * 1. We detect one transaction touching two wallets. + * 2. We see (one or more) transactions arrive in a wallet that have special + * markers we know about. + */ if (txCount == 2 && data.size() == 2) { // is this an intra-wallet transfer? - const auto &txid1 = txidFromWallet(data.at(0).privacySegment, data.at(0).walletTxId); - const auto &txid2 = txidFromWallet(data.at(1).privacySegment, data.at(1).walletTxId); + assert(data.at(0).walletTxIds.size() == 1); + const auto &txid1 = + txidFromWallet(data.at(0).privacySegment, data.at(0).walletTxIds.at(0)); + assert(data.at(1).walletTxIds.size() == 1); + const auto &txid2 = + txidFromWallet(data.at(1).privacySegment, data.at(1).walletTxIds.at(0)); if (!txid1.IsNull() && txid1 == txid2) { - return tr("Transaction between two wallets found.", "the wallets are both yours"); + return tr("Transaction between two wallets found.", + "the wallets are both yours"); } } @@ -110,15 +115,17 @@ QString NotificationManager::describeCollated(int &txCount) const for (const auto &item : data) { for (auto wallet : wallets) { if (wallet->segment()->segmentId() == item.privacySegment) { - if (wallet->isCFTransaction(item.walletTxId)) - ++cfFound; + for (const int txIndex : item.walletTxIds) { + if (wallet->isCFTransaction(txIndex)) + ++cfFound; + } break; } } deposited += item.deposited; spent += item.spent; - ++txCount; + txCount += item.walletTxIds.size(); } if (txCount == cfFound) { return tr("We received %1 anonimity transactions.", nullptr, txCount).arg(txCount); diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 913a26b..c646e47 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -360,7 +360,7 @@ void Wallet::addTransaction(const Tx &tx, NotificationNeeded notificationNeeded) int firstNewTransaction; P2PNet::Notification notification; notification.privacySegment = int(m_segment->segmentId()); - notification.walletTxId = m_nextWalletTransactionId; + notification.walletTxIds = { m_nextWalletTransactionId }; { QMutexLocker locker(&m_lock); if (m_walletIsImporting) { @@ -485,7 +485,7 @@ void Wallet::newTransactions(const uint256 &blockId, int blockHeight, const std: continue; walletTransactionId = oldTx->second; } - notification.walletTxId = walletTransactionId; + notification.walletTxIds = { walletTransactionId }; if (insertBeforeData.get() == nullptr) { // remove all transactions after this block, in order to replay them at the end // of this insert. -- 2.54.0 From cb521f4108d280833bd83af7b46b9048240f4bba Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 12:43:07 +0200 Subject: [PATCH 1351/1428] Import Polish translation update from crowdin --- translations/floweepay-common_pl.ts | 96 ++++++--- translations/floweepay-desktop_pl.ts | 204 ++++++++++---------- translations/floweepay-mobile_pl.ts | 170 +++++++++------- translations/module-bigtransfer_pl.ts | 16 +- translations/module-build-transaction_pl.ts | 18 +- 5 files changed, 281 insertions(+), 223 deletions(-) diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index c672a2b..7a6b1c0 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -119,7 +119,7 @@ Płatność została wysłana do: - + Add a personal note Dodaj osobistą notatkę @@ -148,30 +148,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -182,13 +182,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -238,17 +238,38 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) %1 nowe transakcje w %2 portfelach (%3) - + A payment of %1 has been sent Wysłano płatność w wysokości %1 + + + New Block Found + Znaleziono nowy blok + + + + Transaction between two wallets found. + the wallets are both yours + Znaleziono transakcję pomiędzy dwoma portfelami. + - + + We received %1 anonimity transactions. + + Otrzymano %1 zanonimizowaną transakcję. + Otrzymano %1 zanonimizowane transakcje. + Otrzymano %1 zanonimizowanych transakcji. + Otrzymano %1 zanonimizowanej transakcji. + + + + %1 new transactions found (%2) Znaleziono %1 nową transakcję (%2) @@ -257,28 +278,49 @@ Znaleziono %1 nowej transakcji (%2) + + + Your transaction got mined + form on number of transactions + + Twoja transakcja została wykopana + Twoje transakcje zostały wykopane + Twoje transakcje zostały wykopane + Twoje transakcje zostały wykopane + + + + + Your transaction has %1 confirmations + form on number of transactions + + Twoja transakcja uzyskała %1 potwierdzenie + Twoja transakcja uzyskała %1 potwierdzenia + Twoja transakcja uzyskała %1 potwierdzeń + Twoja transakcja uzyskała %1 potwierdzenia + + + + + Your transaction got another confirmation + form on number of transactions + + Twoja transakcja uzyskała kolejne potwierdzenie + Twoje transakcje uzyskały kolejne potwierdzenie + Twoje transakcje uzyskały kolejne potwierdzenie + Twoje transakcje uzyskały kolejne potwierdzenie + + NotificationManagerPrivate - - - Bitcoin Cash block mined. Height: %1 - Blok Bitcoin Cash został wykopany. Wysokość: %1 - - - - - tBCH (testnet4) block mined: %1 - Wykopano blok tBCH (testnet4): %1 - - - + Mute Wycisz - + New Transactions dialog-title diff --git a/translations/floweepay-desktop_pl.ts b/translations/floweepay-desktop_pl.ts index 80aeece..6c78916 100644 --- a/translations/floweepay-desktop_pl.ts +++ b/translations/floweepay-desktop_pl.ts @@ -54,108 +54,108 @@ Szczegóły portfela - + Name Nazwa - + Sync Status Status synchronizacji - + Encryption Szyfrowanie - - + + Password Hasło - + Open Otwórz - + Invalid PIN Nieprawidłowy PIN - + Include balance in total Uwzględnij saldo w podsumowaniu - + Hide in private mode Ukryj w trybie prywatnym - + Address List Lista adresów - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Backup details Szczegóły kopii zapasowej - + Seed-phrase Seed - + Copy Skopiuj - + Seed format Format seeda - + Derivation Derywacja - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii komputera. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Ten portfel jest chroniony hasłem (PIN by płacić). Aby zobaczyć szczegóły kopii zapasowej musisz podać hasło. @@ -367,78 +367,78 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoSekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Address to import Adres do zaimportowania - - + + New Wallet Name Nazwa nowego portfela - + Force Single Address Wymuś jeden adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Gdy włączone, dodatkowe adresy nie zostaną automatycznie stworzone dla tego portfela. Reszta wróci do zaimportowanego klucza. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - + + Start Rozpocznij - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Derywacja - + Nothing found for seed. Does it have a password? Nic nie znaleziono dla seeda. Czy posiada hasło? - + Password Hasło - + imported wallet password hasło do importowanego portfela @@ -632,7 +632,7 @@ Change will come back to the imported key. - + Warning Uwaga! @@ -688,65 +688,65 @@ Change will come back to the imported key. Wyślij - + Destination Odbiorca - + Max available The maximum balance available Maksymalnie dostępne - + %1 to %2 summary text to pay X-euro to address M %1 do %2 - + Enter Bitcoin Cash Address Wprowadź adres Bitcoin Cash - - + + Copy Address Skopiuj Adres - + Amount Kwota - + Max Maks. - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Jest to adres BTC, który jest niekompatybilny z adresem BCH. Twoje środki mogą zostać utracone i Flowee nie będzie miało możliwości ich odzyskania. Czy na pewno chcesz użyć tego adresu BTC? - + Continue Kontynuuj - + Cancel Anuluj - + Coin Selector Wybór monet - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -757,78 +757,78 @@ Change will come back to the imported key. - + Total Number of coins Dostępne - + Needed Potrzebne - + Selected Wybrane - + Value Wartość - + Locked coins will never be used for payments. Right-click for menu. Zablokowane monety nigdy nie zostaną użyte do płatności. Kliknij, aby rozwinąć menu. - + Age Wiek - + Unselect All Odznacz wszystkie - + Select All Zaznacz wszystkie - + Unlock coin Odblokuj monetę - + Lock coin Zablokuj monetę - + Public-comment Publiczny komentarz - + Add a comment you want to include in the transaction, visible for everyone. Dodaj komentarz, który chcesz zawrzeć w transakcji, widoczny dla wszystkich. - + Custom message, to be included in the transaction. Komunikat użytkownika, który ma być zawarty w transakcji. - + Text Tekst - + Size Rozmiar @@ -851,57 +851,47 @@ Change will come back to the imported key. Pokaż wartość Bitcoin Cash na stronie Aktywności - - Show Block Notifications - Pokaż powiadomienia o blokach - - - - When a new block is mined, Flowee Pay shows a desktop notification - Gdy nowy blok zostanie wykopany, Flowee Pay pokazuje powiadomienia na pulpicie - - - + Night Mode Tryb nocny - + Private Mode Tryb prywatny - + Hides private wallets while enabled Ukrywa prywatne portfele - + Font sizing Rozmiar czcionki - + Version Wersja - + Library Version Wersja biblioteki - + Synchronization Synchronizacja - + Network Status Status sieci - + Address Stats Statystyki adresu @@ -1246,95 +1236,95 @@ Change will come back to the imported key. main - + Activity Aktywność - + Archived wallets do not check for activities. Balance may be out of date. Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne. - + Unarchive Przywróć - + This wallet needs a password to open. Ten portfel wymaga hasła do otwarcia. - + Password: Hasło: - + Invalid password Nieprawidłowe hasło - + Open Otwórz - + Send Wyślij - + Receive Odbierz - + Balance Saldo - + Main balance (money), non specified Główny - + Unconfirmed balance (money) Niepotwierdzona - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH to: %1 - + Network status Status sieci - + Offline Offline - + Add Bitcoin Cash wallet Dodaj portfel Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1345,12 +1335,12 @@ Change will come back to the imported key. - + Preparing... Przygotowuję… - + QR-Scan Skanowanie QR diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 39f30c2..8429fdd 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -76,131 +76,146 @@ Zarchiwizowane portfele nie sprawdzają aktywności. Saldo może być nieaktualne - + + To connect this wallet + Aby połączyć portfel + + + + Your XPub details + Szczegóły XPub + + + + Be careful who you share the xpub with! + Uważaj, komu udostępniasz xpub! + + + Backup information Dane kopii zapasowej - + Backup Details Szczegóły kopii zapasowej - + Wallet seed-phrase Fraza seedowa portfela - + Password Hasło - + Seed format Format seeda - - + + Starting Height height refers to block-height Wysokość początkowa - + Derivation Path Ścieżka derywacji - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Prosimy o zapisanie seeda oraz ścieżki derywacji na papierze, z zachowaniem kolejności słów. Pozwoli to na odzyskanie portfela w przypadku awarii telefonu. - + <b>Important</b>: Never share your seed-phrase with others! <b>Ważne</b>: Nigdy nie udostępniaj seeda innym! - + Wallet keys Klucze portfela - + Show Index toggle to show numbers Pokaż indeks - + Change Addresses Adresy zawierające resztę - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Przełącza pomiędzy adresami, na które możesz otrzymać środki od innych a adresami, na które portfel wysyła własną resztę. - + Used Addresses Wykorzystane adresy - + Switches between unused and used Bitcoin addresses Przełącza pomiędzy wykorzystanymi i niewykorzystanymi adresami - + Addresses and keys Adresy i klucze - + Sync Status Status synchronizacji - + Hide balance in overviews Ukryj saldo w podsumowaniach - + Hide in private mode Ukryj w trybie prywatnym - + Unarchive Wallet Przywróć portfel - + Archive Wallet Zarchiwizuj portfel - + Really Delete? Czy na pewno usunąć? - + Removing wallet "%1" can not be undone. argument is the wallet name Usunięcie portfela "%1" nie może zostać cofnięte. - + Remove Wallet Usuń portfel @@ -282,20 +297,31 @@ Synchronizacja w tle - + Without background synchronization your wallets will not see new transactions until you open Flowee Pay Bez synchronizacji w tle portfele nie będą widzieć nowych transakcji dopóki nie otworzysz Flowee Pay - + Allow Background Synchronization Zezwól na synchronizację w tle - + Every %1 hours Co %1 godz. + + + Notifications + Powiadomienia + + + + On Payments + in relation to notifications + W przypadku płatności + CurrencySelector @@ -361,83 +387,83 @@ Sekret jako tekst - + Unknown word(s) found Znaleziono nieznane słowa - + Next Dalej - + Address to import Adres do zaimportowania - - + + New Wallet Name Nazwa nowego portfela - + Force Single Address Wymuś pojedynczy adres - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Włączenie sprawi, że dodatkowe adresy nie zostaną automatycznie wygenerowane dla tego portfela. Reszta wydana z transakcji trafi na zaimportowany klucz. - - + + Oldest Transaction Najstarsza transakcja - + Check Age online check for wallet age Sprawdź wiek portfela - + Nothing found for wallet Nie znaleziono nic dla portfela - - + + Start Rozpocznij - + Discover Details online check for wallet details Poznaj szczegóły - + Derivation Path Ścieżka derywacji - + Nothing found for seed. Does it have a password? Nic nie znaleziono dla seeda. Czy posiada hasło? - + Password Hasło - + imported wallet password hasło do importowanego portfela @@ -445,12 +471,12 @@ Change will come back to the imported key. InstaPayConfigButton - + Enable Instant Pay Włącz Szybką Płatność - + Configure Instant Pay Skonfiguruj Szybką Płatność @@ -564,10 +590,20 @@ Change will come back to the imported key. MainViewBase - + No Internet Available Brak dostępu do internetu + + + For a better experience, enable Flowee Pay background synchronization. + Włącz synchronizację w tle dla Flowee Pay. Ułatwi to korzystanie z aplikacji. + + + + hide + ukryj + MenuOverlay @@ -751,7 +787,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoAdres docelowy - + Unlock Wallet Odblokuj portfel @@ -786,7 +822,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego PriceInputWidget - + All Currencies Wszystkie waluty @@ -804,12 +840,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoUdostępnij ten QR kod, aby otrzymać - + Encrypted Wallet Zaszyfrowany portfel - + Import Running... Trwa Importowanie... @@ -819,13 +855,13 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoOpis - + Address Bitcoin Cash address Adres - + Clear Wyczyść @@ -992,16 +1028,6 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoLight Jasny - - - Notifications - Powiadomienia - - - - On new block found - o odkryciu nowego bloku - SlideToApprove @@ -1248,32 +1274,32 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnego TransactionListItem - + Miner Reward Nagroda dla górnika - + Fused Fused - + Received Otrzymano - + Moved Przeniesiono - + Sent Wysłano - + Rejected Odrzucono diff --git a/translations/module-bigtransfer_pl.ts b/translations/module-bigtransfer_pl.ts index 57c9d39..73a394f 100644 --- a/translations/module-bigtransfer_pl.ts +++ b/translations/module-bigtransfer_pl.ts @@ -29,32 +29,32 @@ Portfel do portfela - + Select two wallets to transfer funds simply, using anonimity preserving transactions. Wybierz dwa portfele, aby przenieść środki używając transakcji zapewniających anonimowość. - - Spending Wallet - Portfel źródłowy + + Select Spending Wallet + Wybierz portfel źródłowy - + Addresses Adresy - + Coins Monety - + Destination Wallet Portfel docelowy - + Prepare... Przygotuj... diff --git a/translations/module-build-transaction_pl.ts b/translations/module-build-transaction_pl.ts index 8a9d7d2..79b2131 100644 --- a/translations/module-build-transaction_pl.ts +++ b/translations/module-build-transaction_pl.ts @@ -84,7 +84,7 @@ - + Add Destination Dodaj Odbiorcę @@ -136,44 +136,44 @@ %1 sat/bajt - + Destination Odbiorca - + unset indication of desination not being set nie ustawiono - + invalid address is not correct nieprawidłowy - + Copy Address Skopiuj adres - + Drag to Edit Przeciągnij, aby edytować - + Drag to Delete Przeciągnij, aby usunąć - + Unlock Wallet Odblokuj portfel - + Prepare Payment... Przygotuj płatność... -- 2.54.0 From e21f0eb17dbdcea82f6f135814458d8c54619cd8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 13:04:08 +0200 Subject: [PATCH 1352/1428] Fix dates not being translated. People will now have proper localized months and mostly localized formats. --- src/FloweePay.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 5b86066..afd7f57 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -699,7 +699,7 @@ QString FloweePay::formatDate(QDateTime date, FloweePay::DateFormatOption type) if (days == 1) return tr("Yesterday"); if (days < 9) // return day of the week - return date.toString("dddd"); + return QLocale::system().toString(date, "dddd"); } if (type == NoYear || date.date().year() == QDate::currentDate().year()) { @@ -712,14 +712,14 @@ QString FloweePay::formatDate(QDateTime date, FloweePay::DateFormatOption type) int d = format.indexOf('d'); if (d == -1) d = format.indexOf('D'); - if (m < d) + if (m > d) shortFormat = "d MMM"; else shortFormat = "MMM d"; } - return date.toString(shortFormat); + return QLocale::system().toString(date, shortFormat); } - return date.toString(format); + return QLocale::system().toString(date, format); } QString FloweePay::formatDateTime(QDateTime date) const @@ -759,7 +759,7 @@ QString FloweePay::formatDateTime(QDateTime date) const if (days < 9) // return day of the week return date.toString("dddd " + timeFormat); } - return date.toString(format); + return QLocale::system().toString(date, format); } QDateTime FloweePay::timeOfBlockHeight(int blockHeight) const -- 2.54.0 From d87ae8a813da1da79962efe14bbc2d02507cad3e Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 18:48:18 +0200 Subject: [PATCH 1353/1428] Ensure same time source Make sure all places use the same source of time for storage and comparison, avoiding timezone and other such issues. --- src/PriceDataProvider.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 633cbb2..2e0d924 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -55,14 +55,14 @@ void PriceDataProvider::start() if (m_priceHistory.get()) m_priceHistory->initialPopulate(); m_timer.start(ReloadTimeout); - const auto now = time(nullptr); + const auto now = QDateTime::currentSecsSinceEpoch(); if (now - m_currentPrice.timestamp > 300) fetch(); } bool PriceDataProvider::oldData() const { - const auto now = time(nullptr); + const auto now = QDateTime::currentSecsSinceEpoch(); return m_currentPrice.timestamp == 0 || now - m_currentPrice.timestamp > 3600; } @@ -340,7 +340,7 @@ void PriceDataProvider::finishedDownload() } else { m_currentPrice.price = price.toDouble() * 100; } - m_currentPrice.timestamp = time(nullptr); + m_currentPrice.timestamp = QDateTime::currentSecsSinceEpoch(); emit priceChanged(m_currentPrice.price); } catch (const std::runtime_error &error) { logWarning() << "PriceDataProvider failed." << error.what(); -- 2.54.0 From 8bf8550e60f851dd387448d314d56f7b9bd28780 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 18:49:35 +0200 Subject: [PATCH 1354/1428] Handle fallback more proper When no fiat price is shown, also for the bch price remove the minus in front of an amount that is stated to have been sent. --- src/NotificationManager.cpp | 23 +++++++++++++++-------- src/NotificationManager_p_dbus.h | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index b9397cc..5b52f7e 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -133,22 +133,29 @@ QString NotificationManager::describeCollated(int &txCount) const const auto gained = deposited - spent; auto pricesOracle = FloweePay::instance()->prices(); + QString gainedStr; + if (gained < 0 && txCount == 1) { + if (pricesOracle->oldData()) { + // no price data available (yet). Display crypto units + gainedStr = QString("%1 %2") + .arg(FloweePay::instance()->amountToStringPretty(std::abs((double) gained)), + FloweePay::instance()->unitName()); + } else { + gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign); + } + return tr("A payment of %1 has been sent").arg(gainedStr); + } + if (pricesOracle->oldData()) { // no price data available (yet). Display crypto units gainedStr = QString("%1 %2") .arg(FloweePay::instance()->amountToStringPretty((double) gained), FloweePay::instance()->unitName()); } - - if (gained < 0 && txCount == 1) { - if (gainedStr.isEmpty()) - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign); - return tr("A payment of %1 has been sent").arg(gainedStr); - } - - if (gainedStr.isEmpty()) + else { gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::AlwaysAddSign); + } if (data.size() > 1) return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); return tr("%1 new transactions found (%2)", "", txCount).arg(txCount).arg(gainedStr); diff --git a/src/NotificationManager_p_dbus.h b/src/NotificationManager_p_dbus.h index 21e1cd0..e2d8f7b 100644 --- a/src/NotificationManager_p_dbus.h +++ b/src/NotificationManager_p_dbus.h @@ -59,7 +59,7 @@ private: QDBusInterface *m_remote = nullptr; bool m_openingNewFundsNotification = false; uint32_t m_blockNotificationId = 0; - uint32_t m_newFundsNotificationId = 0; + uint32_t m_newFundsNotificationId = 129873; QVariantMap m_newBlockHints; QVariantMap m_walletUpdateHints; -- 2.54.0 From 83695dc051ddf54ab36878888d6dfff302c534ec Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 7 May 2025 22:44:53 +0200 Subject: [PATCH 1355/1428] Import English "translations". This is quite minimal, obviously. But plural forms etc go through the translation system. Where the source says "Your transactions got mined" it then is split into plural and singular (one transaction), as long as the translations are actually provided of both forms. --- translations/floweepay-common_en.ts | 92 ++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index 2452c5f..b4176fd 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -113,7 +113,7 @@ The payment has been sent to: - + Add a personal note Add a personal note @@ -142,30 +142,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -174,13 +174,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -228,45 +228,79 @@ NotificationManager - + %1 new transactions across %2 wallets found (%3) %1 new transactions across %2 wallets found (%3) - + A payment of %1 has been sent A payment of %1 has been sent + + + New Block Found + New Block Found + + + + Transaction between two wallets found. + the wallets are both yours + Transaction between two wallets found. + - - %1 new transactions found (%2) + + We received %1 anonimity transactions. + We received %1 anonimity transactions. + We received %1 anonimity transactions. + + + + + %1 new transactions found (%2) + + %1 new transaction found (%2) %1 new transactions found (%2) - %1 new transactions found (%2) + + + + + Your transaction got mined + form on number of transactions + + Your transaction got mined + Your transactions got mined + + + + + Your transaction has %1 confirmations + form on number of transactions + + Your transaction has %1 confirmations + Your transactions have %1 confirmations + + + + + Your transaction got another confirmation + form on number of transactions + + Your transaction got another confirmation + Your transactions got another confirmation NotificationManagerPrivate - - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash block mined. Height: %1 - - - - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) block mined: %1 - - - + Mute Mute - + New Transactions dialog-title -- 2.54.0 From b42c5c3aaf57f13ff10325f3d7f0f9efae590f38 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 May 2025 20:44:58 +0200 Subject: [PATCH 1356/1428] Fix regression; on instapay InstaPay should not be enabled when the user is supposed to type their own amount. --- guis/mobile/PayWithQR.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 636739f..e838014 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -75,10 +75,13 @@ Page { // should the price be included in the QR code, don't show editing widgets. root.allowEditAmount = payment.paymentAmount <= 0; - if (root.allowEditAmount) + if (root.allowEditAmount) { priceInput.takeFocus(); - else + payment.instaPay = false; + } + else { root.takeFocus(); + } } Item { // data -- 2.54.0 From fbf1b61f53d4a7b537dca4a0a91fa81d757a4fca Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 May 2025 20:53:53 +0200 Subject: [PATCH 1357/1428] Remove dead code. --- guis/mobile/WorkflowStarter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/WorkflowStarter.js b/guis/mobile/WorkflowStarter.js index b6ed4f3..13d5d14 100644 --- a/guis/mobile/WorkflowStarter.js +++ b/guis/mobile/WorkflowStarter.js @@ -32,6 +32,6 @@ function startSweep(wif) { function startImportWithSecret(secret) { thePile.push("ImportWalletPage.qml", {"secret": secret, - "backHandler": function handler() { thePile.pop(); thePile.pop(); thePile.pop; }}, + "backHandler": function handler() { thePile.pop(); thePile.pop(); }}, QQC2.StackView.Immediate); } -- 2.54.0 From 2e69c1469ac1636971cfba286f620b5238a4f693 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 20 May 2025 21:54:05 +0200 Subject: [PATCH 1358/1428] UX improve bg sync popup This adds a proper looking button and makes the hide not look so much like the thing to press anymore. --- guis/mobile/MainViewBase.qml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 23c5c00..8aa0a7e 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -303,7 +303,7 @@ QQC2.Control { Item { id: floatingCard width: mainText.width + 32 - height: 28 + mainText.height + 10 + hideButtonLabel.height + 16 + height: 28 + mainText.height + 10 + bgSyncButton.height + 10 + hideButtonLabel.height + 16 y: 330 x: 10 opacity: Pay.showBGSyncCard ? 1 : 0 @@ -361,14 +361,30 @@ QQC2.Control { wrapMode: Text.WrapAtWordBoundaryOrAnywhere width: Math.min(220, contentWidth) } + Rectangle { + id: bgSyncButton + width: Math.max(160, bgSyncButtonLabel.width + 30) + height: bgSyncButtonLabel.height + 20 + radius: 10 + color: mainWindow.floweeGreen + anchors.top: mainText.bottom + anchors.topMargin: 10 + anchors.horizontalCenter: parent.horizontalCenter + Flowee.Label { + id: bgSyncButtonLabel + text: qsTr("Start") + color: "black" + anchors.centerIn: parent + } + } + Flowee.Label { id: hideButtonLabel text: qsTr("hide") - color: Pay.useDarkSkin ? "#2079ff" : mainWindow.floweeBlue - anchors.top: mainText.bottom + opacity: 0.6 + anchors.top: bgSyncButton.bottom anchors.topMargin: 10 - anchors.right: parent.right - anchors.rightMargin: 16 + anchors.horizontalCenter: parent.horizontalCenter } Behavior on opacity { NumberAnimation { } } -- 2.54.0 From 03fad01771098f8ab6f06b0d156fa38c06765efd Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 May 2025 19:36:40 +0200 Subject: [PATCH 1359/1428] Wait longer after receive The popup is a too intrusive when it arrives too fast after receiving a new transaction, this moves it to 150 seconds to make it clearly not part of the receive flow and simply a new dialog for the user to consider. --- src/FloweePay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index afd7f57..3451ac8 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -1127,7 +1127,7 @@ void FloweePay::setNotification(QObject *n) emit notificationChanged(); if (n) // new transaction, suggest background running - QTimer::singleShot(6 * 1000, this, &FloweePay::maybeShowBackgroundCard); + QTimer::singleShot(150 * 1000, this, &FloweePay::maybeShowBackgroundCard); } FloweePay::UnlockingKeyboard FloweePay::unlockingKeyboard() const -- 2.54.0 From 57ca81bf8fe03434e1c434340771b6c0047a3fac Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 May 2025 19:42:05 +0200 Subject: [PATCH 1360/1428] Make mobile also show a page when locked. --- guis/mobile.qrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guis/mobile.qrc b/guis/mobile.qrc index ffe3588..3cabeff 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -99,5 +99,7 @@ mobile/UnlockWidget.qml mobile/VisualSeparator.qml mobile/NewIndicator.qml + + desktop/locked.qml -- 2.54.0 From ea7f17f331792816233ce2d12892325b6b8a7054 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 21 May 2025 22:57:07 +0200 Subject: [PATCH 1361/1428] Improve broadcast feedback This fixes the general flow, but specifically adds two things: 1. we detect the lack of peers and notify the user of this problem with an alarming looking screen. 2. We move to only requiring 1 peer to accept the transaction, since on slow network connections the others tend to get it from each other instead of me. --- guis/Flowee/BroadcastFeedback.qml | 35 +++++++++++- guis/Flowee/ProgressCheckIcon.qml | 13 +++-- src/FloweePay.h | 3 +- src/TxInfoObject.cpp | 94 +++++++++++++++++++++---------- src/TxInfoObject.h | 1 + src/TxInfoObject_p.h | 2 + 6 files changed, 107 insertions(+), 41 deletions(-) diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index a410911..432edfe 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -20,6 +20,7 @@ import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import Flowee.org.pay +import "../ControlColors.js" as ControlColors // screen to show the result of a (Payment) broadcast. // This screen won't do anything until you call start(). @@ -34,7 +35,9 @@ QQC2.Control { function start() { background.y = 0; + waitingForPeers.hasTriggered = false; background.opacity = 1; + ControlColors.applyDarkSkin(root); } function reset() { background.y = height + 2; @@ -49,17 +52,37 @@ QQC2.Control { property var processNote: undefined property bool hideHeader: false + // is true when the backend has initiated actual transfer of transaction data. + property bool hasSent: { + var s = root.status; + return s !== FloweePay.NotStarted && s !== FloweePay.TxOffered && s !== FloweePay.TxRejected; + } + Rectangle { id: background width: parent.width height: parent.height opacity: 0 visible: opacity > 0 - color: root.status === FloweePay.TxRejected ? "#7f0000" : "#60b671"; + color: { + if (root.status === FloweePay.NotStarted && waitingForPeers.hasTriggered) + return "#aa821d"; // the state when we can't find peers. + if (root.status === FloweePay.TxRejected) + return "#7f0000"; + return "#60b671"; // all Ok + } y: height + 2 + MouseArea { anchors.fill: parent // eat all mouse events. } + Timer { + id: waitingForPeers + interval: 400 + running: background.opacity > 0.8; + property bool hasTriggered: false + onTriggered: hasTriggered = true; + } Rectangle { id: header @@ -92,7 +115,11 @@ QQC2.Control { color: "#e8e8e8" text: { var s = root.status; - if (s === FloweePay.TxSent1 || s === FloweePay.TxWaiting) + if (s === FloweePay.NotStarted) + return qsTr("Finding Network"); + if (s === FloweePay.TxOffered) + return "bad peers"; // this shouldn't really show more than mere milliseconds.. + if (s === FloweePay.TxWaiting) return qsTr("Sending Payment"); if (s === FloweePay.TxBroadcastSuccess) return qsTr("Payment Sent"); @@ -171,7 +198,7 @@ QQC2.Control { Label { id: addressLabel color: statusLabel.color - visible: root.targetAddress !== "" + visible: root.targetAddress !== "" && root.hasSent; width: parent.width - 20 x: 10 horizontalAlignment: Qt.AlignHCenter @@ -201,6 +228,7 @@ QQC2.Control { Label { id: transactionCommentComment color: statusLabel.color + visible: root.hasSent opacity: transactionComment.totalText === "" ? 1 : 0 text: qsTr("Add a personal note") Behavior on opacity { NumberAnimation { } } @@ -212,6 +240,7 @@ QQC2.Control { palette.base: statusLabel.color onTotalTextChanged: root.processNote(totalText) width: parent.width + visible: root.hasSent anchors.bottom: parent.bottom Rectangle { diff --git a/guis/Flowee/ProgressCheckIcon.qml b/guis/Flowee/ProgressCheckIcon.qml index d863c11..a6dc8cc 100644 --- a/guis/Flowee/ProgressCheckIcon.qml +++ b/guis/Flowee/ProgressCheckIcon.qml @@ -51,13 +51,14 @@ Item { } sweepAngle: { var s = root.broadcastStatus; + if (s === FloweePay.NotStarted) + return 10; if (s === FloweePay.TxOffered) - return 90; - if (s === FloweePay.TxSent1) - return 150; - if (s === FloweePay.TxWaiting || s === FloweePay.TxBroadcastSuccess) - return 320; - return 10; + return 50; + if (s === FloweePay.TxWaiting || s === FloweePay.TxRejected) + return 100; + // Last option: FloweePay.TxBroadcastSuccess + return 320; } Behavior on sweepAngle { NumberAnimation { duration: 1100 } } Behavior on startAngle { NumberAnimation { } } diff --git a/src/FloweePay.h b/src/FloweePay.h index 8e846b0..b4de473 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -137,8 +137,7 @@ public: */ enum BroadcastStatus { NotStarted, //< We have not yet seen a call to broadcast() - TxOffered, //< Tx has not been offered to any peers. - TxSent1, //< Tx has been sent to at least one peer. + TxOffered, //< Tx has been offered to relevant peers. TxWaiting, //< Tx has been downloaded by more than one peer. TxBroadcastSuccess, //< Tx broadcast and accepted by multiple peers. TxRejected //< Tx has been offered, downloaded and rejected by at least one peer. diff --git a/src/TxInfoObject.cpp b/src/TxInfoObject.cpp index c3162e0..47061b2 100644 --- a/src/TxInfoObject.cpp +++ b/src/TxInfoObject.cpp @@ -27,7 +27,7 @@ #include constexpr int SECTION = 10004; -constexpr int WaitingTimeoutMs = 950; +constexpr int WaitingTimeoutMs = 350; // between us starting to send and us saying "ok, no reject message" TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex) : BroadcastTxData(tx), @@ -48,6 +48,11 @@ TxInfoObject::TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex) connect(this, SIGNAL(finished(int,bool)), d->wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection); } +void TxInfoObject::offeredVia(const std::shared_ptr &peer) +{ + d->offeredVia(peer); +} + void TxInfoObject::sentVia(const std::shared_ptr &peer) { d->sentVia(peer); @@ -75,7 +80,7 @@ void TxInfoObject::setBroadcastStatus(const FloweePay::BroadcastStatus &status) assert(thread() == QThread::currentThread()); if (m_status == status) return; - if (m_status < FloweePay::TxSent1 && status >= FloweePay::TxSent1) + if (m_status < FloweePay::TxOffered && status >= FloweePay::TxOffered) emit sentOne(); if (m_status < FloweePay::TxRejected && status >= FloweePay::TxRejected) emit rejectionSeen(); @@ -106,7 +111,7 @@ TxInfoPrivate::Status TxInfoPrivate::calcStatus() const { QMutexLocker l(&lock); Status rc; - rc.sent = broadcasts.size(); + rc.announced = broadcasts.size(); rc.inProgress = 0; const auto time = std::chrono::system_clock::now(); @@ -118,33 +123,54 @@ TxInfoPrivate::Status TxInfoPrivate::calcStatus() const rc.failed++; else if (broadcast.sentTime > cutoff) rc.inProgress++; + if (broadcast.sentTime > 0) + rc.sent++; } + assert(rc.inProgress <= rc.sent); // sanity check return rc; } +// we sent an INV +void TxInfoPrivate::offeredVia(const std::shared_ptr &peer) +{ + assert(peer.get()); + /* + * This is called directly from a peer's network processing code. Each peer may + * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, + * so we lock. + */ + QMutexLocker l(&lock); + const auto id = peer->connectionId(); + for (auto iter = broadcasts.begin(); iter != broadcasts.end(); ++iter) { + if (iter->connectionId == id) // avoid duplicates + return; + } + + Broadcast broadcast; + broadcast.connectionId = id; + broadcast.ep = peer->peerAddress().peerAddress(); + broadcasts.append(broadcast); + emit broadcastsChanged(); +} + void TxInfoPrivate::sentVia(const std::shared_ptr &peer) { assert(peer.get()); - /* - * This is called directly from a peer's network processing code. Each peer may - * be on a different 'strand' (effectively a thread) as a result of using NetworkManager, - * so we lock. - */ - QMutexLocker l(&lock); - const auto id = peer->connectionId(); - for (auto iter = broadcasts.begin(); iter != broadcasts.end(); ++iter) { - if (iter->connectionId == id) // avoid duplicates - return; - } + QMutexLocker l(&lock); + const auto id = peer->connectionId(); + for (auto iter = broadcasts.begin(); iter != broadcasts.end(); ++iter) { + if (iter->connectionId == id) { - Broadcast broadcast; - broadcast.connectionId = id; - broadcast.ep = peer->peerAddress().peerAddress(); - const auto time = std::chrono::system_clock::now(); - const auto millis = std::chrono::duration_cast(time.time_since_epoch()); - broadcast.sentTime = millis.count(); - broadcasts.append(broadcast); - emit broadcastsChanged(); + const auto time = std::chrono::system_clock::now(); + const auto millis = std::chrono::duration_cast(time.time_since_epoch()); + iter->sentTime = millis.count(); + emit broadcastsChanged(); + return; + } + } + // not in the list? Hmm.. getting sentVia before we + // got offeredVia is a violation of our API + assert(false); } void TxInfoPrivate::txRejected(int connectionId, BroadcastTxData::RejectReason reason, const std::string &message) @@ -166,13 +192,16 @@ void TxInfoPrivate::txRejected(int connectionId, BroadcastTxData::RejectReason r iter->rejectMsg = message; emit broadcastsChanged(); } - break; + return; } } + // not in the list? Hmm.. getting sentVia before we + // got offeredVia is a violation of our API + assert(false); } /* - * The broadcast happens in INV style, and we wait for the peer to then get the data, + * The broadcast happens in INV style, and we wait for the peer to then ask (GETDATA) the data, * which we notice with the 'sentOne' call. Called once per peer. * But likely only one or maybe two actually call this as the more likely scenario * (especially on slower networks) is that they get the transaction from each other. @@ -188,21 +217,26 @@ void TxInfoPrivate::checkState() const Status status = calcStatus(); // this uses the mutex. Just so you know.. logDebug(SECTION) << "sent:" << status.sent << "in progress:" << status.inProgress << "failed:" << status.failed; - FloweePay::BroadcastStatus bs = FloweePay::TxWaiting; - if (status.sent == 0) + FloweePay::BroadcastStatus bs = FloweePay::NotStarted; + if (status.announced > 0) bs = FloweePay::TxOffered; else if (status.sent > 0) - bs = FloweePay::TxSent1; + bs = FloweePay::TxWaiting; if (status.failed) bs = FloweePay::TxRejected; - else if (status.sent - status.inProgress >= 2) + else if (status.sent - status.inProgress >= 1) bs = FloweePay::TxBroadcastSuccess; q->setBroadcastStatus(bs); if (!finished && status.sent >= 1) { // a typical wallet has 3 peers. - // we react when at least 2 downloaded our transaction and didn't report a failure - if (status.sent - status.inProgress >= 2) { + // we react when at least 1 downloaded our transaction and they didn't report a failure. + // Why 1? Because, especially on slower networks, it is very often so that propagation + // of the transaction goes via the greater network instead of via us. So sending it to + // one is often enough. + // Notice that when the transaction fails, all peers will request it and thus be + // inProgress until they reject (or not). + if (status.sent - status.inProgress >= 1) { emit q->finished(txIndex, /* bool success = */ status.sent - status.inProgress - status.failed >= 1); diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h index e87b13c..a78cab8 100644 --- a/src/TxInfoObject.h +++ b/src/TxInfoObject.h @@ -61,6 +61,7 @@ public: TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex = -1); // broadcastTxData interface. + void offeredVia(const std::shared_ptr &peer); void sentVia(const std::shared_ptr &peer) override; void txRejected(int connectionId, RejectReason reason, const std::string &message) override; uint16_t privSegment() const override; diff --git a/src/TxInfoObject_p.h b/src/TxInfoObject_p.h index 339ddc0..0100adc 100644 --- a/src/TxInfoObject_p.h +++ b/src/TxInfoObject_p.h @@ -34,6 +34,7 @@ public: TxInfoPrivate(TxInfoObject *parent); struct Status { + int announced = 0; // num of peers we announced this tx to. int sent = 0; // number of peers we sent this transaction to. int inProgress = 0; // number of peers in progress. int failed = 0; // number of peers reported failure. @@ -41,6 +42,7 @@ public: /// Fill a status struct where we take the time things in flight into account. Status calcStatus() const; + void offeredVia(const std::shared_ptr &peer); void sentVia(const std::shared_ptr &peer); void txRejected(int connectionId, BroadcastTxData::RejectReason reason, const std::string &message); -- 2.54.0 From 49459caf997d1eb640a376e6800faa43281819f8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 24 May 2025 09:33:45 +0200 Subject: [PATCH 1362/1428] Make notification text nicer --- src/NotificationManager.cpp | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 5b52f7e..30e4505 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -134,31 +134,36 @@ QString NotificationManager::describeCollated(int &txCount) const const auto gained = deposited - spent; auto pricesOracle = FloweePay::instance()->prices(); - QString gainedStr; if (gained < 0 && txCount == 1) { - if (pricesOracle->oldData()) { - // no price data available (yet). Display crypto units - gainedStr = QString("%1 %2") - .arg(FloweePay::instance()->amountToStringPretty(std::abs((double) gained)), - FloweePay::instance()->unitName()); - } else { - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign); + QString sentStr = QString("%1 %2") + .arg(FloweePay::instance()->amountToStringPretty(std::abs((double) gained)), + FloweePay::instance()->unitName()); + + if (true || !pricesOracle->oldData()) { + // price data available. Add fiat. + sentStr += " (" + + pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign) + + ")"; } - return tr("A payment of %1 has been sent").arg(gainedStr); + return tr("%1 has been sent", + "The %1 is replaced with amount of money").arg(sentStr); } - if (pricesOracle->oldData()) { - // no price data available (yet). Display crypto units - gainedStr = QString("%1 %2") - .arg(FloweePay::instance()->amountToStringPretty((double) gained), - FloweePay::instance()->unitName()); - } - else { - gainedStr = pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::AlwaysAddSign); + QString gainedStr = QString("%1 %2") + .arg(FloweePay::instance()->amountToStringPretty((double) gained), + FloweePay::instance()->unitName()); + + if (!pricesOracle->oldData()) { + gainedStr += " (" + + pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::AlwaysAddSign) + + ")"; } if (data.size() > 1) return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); - return tr("%1 new transactions found (%2)", "", txCount).arg(txCount).arg(gainedStr); + if (txCount == 1) + return tr("Received %1 ", "The %1 is replaced with amount of money").arg(gainedStr); + return tr("Received %1 in %2 transactions", "%1 is amount of money", txCount) + .arg(gainedStr).arg(txCount); } QString NotificationManager::describeConfirmations() -- 2.54.0 From e20c77bbcfdb7b03b68a16e4f46510b76b68981c Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 24 May 2025 19:59:07 +0200 Subject: [PATCH 1363/1428] Fix fiat service in background When running in the background on Android the user selected locale information turns out to not be available. Which, to be honest, I won't qualify as a bug, just unfortunate. We now always store the data in our app config which makes the notifications show a fiat price as expected. --- .../java/org/flowee/pay/PeriodicService.java | 8 ++++---- src/FloweePay.cpp | 17 +++++++++++++++++ src/PriceDataProvider.cpp | 11 ++++++++--- src/PriceDataProvider.h | 3 +++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/android/java/org/flowee/pay/PeriodicService.java b/android/java/org/flowee/pay/PeriodicService.java index dd2b787..80e6481 100644 --- a/android/java/org/flowee/pay/PeriodicService.java +++ b/android/java/org/flowee/pay/PeriodicService.java @@ -52,17 +52,17 @@ public class PeriodicService extends QtService { public static void startSingleTimer(Context context) { - startTimerMs(context, 60 * 60 * 1000, 0); + // hour from now. No repeat (interval is 0). + startTimerMs(context, SystemClock.elapsedRealtime() + 60 * 60 * 1000, 0); } + // times in milli seconds private static void startTimerMs(Context context, long start, long interval) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (alarmManager == null) return; - // They can be scheduled, but they won't do anything unless the - // user approves the previous ask. Intent serviceIntent = new Intent(context, PeriodicService.class); serviceIntent.setPackage("org.flowee.pay"); PendingIntent pendingIntent = PendingIntent.getService(context, 9875, @@ -76,7 +76,7 @@ public class PeriodicService extends QtService { if (interval > 0) { alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, start, interval, pendingIntent); - } else { + } else { // plan a single event alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, start, pendingIntent); } diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 3451ac8..1e89cea 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -196,6 +196,15 @@ FloweePay::FloweePay() m_backgroundUpdates = appConfig.value("enabled", false).toBool(); m_backgroundUpdateInterval = appConfig.value(BG_INTERVAL, 6).toInt(); appConfig.endGroup(); + if (m_backgroundUpdates) { + // in release 2025.04.0 - 2025.05.0 we didn't copy the country code yet on + // enabling of the background updates, to resolve that for users + // that enabled background updates in that time, we temporarily copy + // it here. To be removed in, say, 6 months. + if (appConfig.value(CURRENCY_COUNTRY).isNull() + && qobject_cast(QCoreApplication::instance())) + appConfig.setValue(CURRENCY_COUNTRY, m_prices->locale()); + } // Update expected chain-height every 5 minutes QTimer *timer = new QTimer(this); @@ -1092,6 +1101,14 @@ void FloweePay::setBackgroundUpdates(bool on) appConfig.setValue("enabled", on); appConfig.endGroup(); + if (on) { + assert(m_prices.get() != nullptr); + // Any background process will likely not have access to the + // user selected locale, as such we store that in our config + // in order to use the user selected fiat price to display money + if (appConfig.value(CURRENCY_COUNTRY).isNull()) + appConfig.setValue(CURRENCY_COUNTRY, m_prices->locale()); + } } bool FloweePay::deviceOffline() const diff --git a/src/PriceDataProvider.cpp b/src/PriceDataProvider.cpp index 2e0d924..8a8cb8d 100644 --- a/src/PriceDataProvider.cpp +++ b/src/PriceDataProvider.cpp @@ -68,11 +68,11 @@ bool PriceDataProvider::oldData() const void PriceDataProvider::setCurrency(const QLocale &countryLocale) { - auto newCurrency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); - if (m_currency == newCurrency) + if (m_locale == countryLocale.name()) return; + m_locale = countryLocale.name(); m_currentPrice = Price(); - m_currency = newCurrency; + m_currency = countryLocale.currencySymbol(QLocale::CurrencyIsoCode); m_currencySymbolPrefix = countryLocale.currencySymbol(QLocale::CurrencySymbol); m_currencySymbolPost.clear(); @@ -395,6 +395,11 @@ void PriceDataProvider::finishedYadioDownload() } } +QString PriceDataProvider::locale() const +{ + return m_locale; +} + QString PriceDataProvider::currencySymbolPost() const { return m_currencySymbolPost; diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index a9b5a79..7f1ec48 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -124,6 +124,8 @@ public: void loadPriceHistory(const QString &basedir); + QString locale() const; + signals: void priceChanged(int32_t price); void currencySymbolChanged(); @@ -146,6 +148,7 @@ private: Price m_currentPrice; QNetworkReply *m_reply = nullptr; + QString m_locale; QString m_currency; QString m_currencySymbolPrefix, m_currencySymbolPost; bool m_displayCents = true; // if true, display 2 digits behind the unit-separator -- 2.54.0 From 32cbe7a29e1b3813c2c55a3f0fd7a7c300379f52 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 24 May 2025 22:52:31 +0200 Subject: [PATCH 1364/1428] Be better at handling stray spaces. --- src/ImportHandler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImportHandler.cpp b/src/ImportHandler.cpp index a2bb860..447b02c 100644 --- a/src/ImportHandler.cpp +++ b/src/ImportHandler.cpp @@ -45,7 +45,8 @@ void ImportHandler::startCheck(WalletType type, const QString &text, const QStri m_nextToCheck = MainDerivation; m_type = type; assert(m_type >= FromSeed && m_type <= FromXPub); - m_input = text; + // remove any type of whitespace. + m_input = text.split(' ', Qt::SkipEmptyParts).join(' '); assert(!m_input.isEmpty()); m_password = password; m_found.clear(); -- 2.54.0 From 9a69ee3914c2cbd1d4044a071a82aa3f7122cf98 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 27 May 2025 08:18:04 +0200 Subject: [PATCH 1365/1428] New version --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index ac72040..8af5d47 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="42" android:versionName="2025.05.1"> diff --git a/src/main.cpp b/src/main.cpp index c54022b..271efcf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.05.0"); + qapp.setApplicationVersion("2025.05.1"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 487cf3e87509db4ca2eb619f022567e66cabf139 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 6 Jun 2025 20:52:04 +0200 Subject: [PATCH 1366/1428] Start RepeatPaymentDetails --- src/CMakeLists.txt | 3 +- src/Payment.cpp | 20 ++++++++++ src/Payment.h | 9 +++++ src/RepeatPaymentDetails.cpp | 39 +++++++++++++++++++ src/RepeatPaymentDetails.h | 73 ++++++++++++++++++++++++++++++++++++ src/WalletEnums.h | 6 +++ 6 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/RepeatPaymentDetails.cpp create mode 100644 src/RepeatPaymentDetails.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9fdb08..1b24d46 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,7 +91,8 @@ if (${Qt6Multimedia_FOUND}) list(APPEND PayLib_PRIVATE_LIBS Qt6::Multimedia) endif () -add_library(pay_lib STATIC ${PAY_SOURCES}) +add_library(pay_lib STATIC ${PAY_SOURCES} + RepeatPaymentDetails.h RepeatPaymentDetails.cpp) target_link_libraries(pay_lib flowee_apputils diff --git a/src/Payment.cpp b/src/Payment.cpp index acc0181..9757b40 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -25,6 +25,7 @@ #include "PaymentProtocol.h" #include "TxInfoObject.h" #include "PaymentDetailComment_p.h" +#include "RepeatPaymentDetails.h" #include #include @@ -483,11 +484,30 @@ void Payment::addDetail(PaymentDetail *detail) doAutoPrepare(); } +RepeatPaymentDetails *Payment::repeatDetails() const +{ + return m_repeatDetails; +} + +void Payment::setRepeatDetails(RepeatPaymentDetails *newRepeatDetails) +{ + if (m_repeatDetails == newRepeatDetails) + return; + m_repeatDetails = newRepeatDetails; + emit repeatDetailsChanged(); +} + bool Payment::simpleAddressTarget() const { return m_simpleAddressTarget; } +void Payment::makeRepeating() +{ + if (m_repeatDetails == nullptr) + setRepeatDetails(new RepeatPaymentDetails(this)); +} + Tx Payment::tx() const { return m_tx; diff --git a/src/Payment.h b/src/Payment.h index 87d69ff..d98a5c4 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -32,6 +32,7 @@ class PaymentDetailInputs; class PaymentDetailComment; class AccountInfo; class TxInfoObject; +class RepeatPaymentDetails; /** * This represents a single payment, possibly a complex one. @@ -80,6 +81,7 @@ class Payment : public QObject Q_PROPERTY(bool autoPrepare READ autoPrepare WRITE setAutoPrepare NOTIFY autoPrepareChanged) Q_PROPERTY(bool instaPay READ allowInstaPay WRITE setAllowInstaPay NOTIFY allowInstaPayChanged) Q_PROPERTY(bool simpleAddressTarget READ simpleAddressTarget NOTIFY simpleAddressTargetChanged FINAL) + Q_PROPERTY(RepeatPaymentDetails *repeat READ repeatDetails NOTIFY repeatDetailsChanged FINAL) // --- Stuff that becomes available / useful after prepare has been called: /// Tx has been prepared @@ -253,6 +255,10 @@ public: bool simpleAddressTarget() const; void setSimpleAddressTarget(bool newSimpleAddressTarget); + Q_INVOKABLE void makeRepeating(); + RepeatPaymentDetails *repeatDetails() const; + void setRepeatDetails(RepeatPaymentDetails *newRepeatDetails); + private slots: void recalcAmounts(); void broadcast(); @@ -278,6 +284,7 @@ signals: void warningsChanged(); void simpleAddressTargetChanged(); void approvedByUser(); + void repeatDetailsChanged(); private: void doAutoPrepare(); @@ -308,6 +315,8 @@ private: Wallet *m_wallet = nullptr; QString m_error; QString m_userComment; + + RepeatPaymentDetails *m_repeatDetails; }; diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp new file mode 100644 index 0000000..3aeb399 --- /dev/null +++ b/src/RepeatPaymentDetails.cpp @@ -0,0 +1,39 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "RepeatPaymentDetails.h" +#include "Payment.h" + + +RepeatPaymentDetails::RepeatPaymentDetails(Payment *parent) + : QObject(parent), + m_parent(parent) +{ +} + +WalletEnums::PaymentRepeatType RepeatPaymentDetails::repeatType() const +{ + return m_repeatType; +} + +void RepeatPaymentDetails::setRepeatType(WalletEnums::PaymentRepeatType newRepeatType) +{ + if (m_repeatType == newRepeatType) + return; + m_repeatType = newRepeatType; + emit repeatTypeChanged(); +} diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h new file mode 100644 index 0000000..1870b39 --- /dev/null +++ b/src/RepeatPaymentDetails.h @@ -0,0 +1,73 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef REPEATPAYMENTDETAILS_H +#define REPEATPAYMENTDETAILS_H + +#include "WalletEnums.h" + +#include +#include + +class Payment; + +class RepeatPaymentDetails : public QObject +{ + Q_OBJECT + Q_PROPERTY(WalletEnums::PaymentRepeatType repeatType READ repeatType WRITE setRepeatType NOTIFY repeatTypeChanged FINAL) +public: + explicit RepeatPaymentDetails(Payment *parent = nullptr); + + WalletEnums::PaymentRepeatType repeatType() const; + void setRepeatType(WalletEnums::PaymentRepeatType newRepeatType); + + + +signals: + void repeatTypeChanged(); + +private: + const Payment *m_parent; + QTime m_time; // time of day for scheduled item(s) + /* + * Repeat style should have options like: + * which day of week. + * which week of month + * which week of year + * + * so every day can be "every day of the week, ever week of the month". + * + * Alternatively; + * every 15th of the month. + * which month(s) of the year. + */ + WalletEnums::PaymentRepeatType m_repeatType = WalletEnums::NoRepeat; + + // type = RepeatByDayOfWeek repeat every (nth) tuesday + uint8_t m_repeatDayOfWeek = 0; // bitfield on which day of the week we repeat + uint8_t m_repeatWeekOfMonth = 0; // bitfield on which week of the month we repeat + // type = RepeatByDayOfMonth Repeat every 15th of (every other) month + uint16_t m_monthOfYear = 0; // bitfield + QVector m_dayOfMonth; // list of days in the month we repeat + + // after this date / time we will not send any more payments. + QDateTime m_endRepeats; + + QVector m_historicalPayments; // txIndex's in the wallet +}; + +#endif diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 55c22e9..8f686b8 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -87,6 +87,12 @@ public: // there is no rejected as those just get kicked. }; Q_ENUM(PeerValidity) + + enum PaymentRepeatType { + NoRepeat, + RepeatByDayOfWeek, // repeat every (nth) tuesday + RepeatByDayOfMonth // Repeat every 15th of (every other) month + }; }; #endif -- 2.54.0 From e1925ad7c3235698c6f69b011c9e589d6239f23d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 7 Jun 2025 22:17:36 +0200 Subject: [PATCH 1367/1428] Remove dead code --- testing/fiat/TestFiatUtils.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/testing/fiat/TestFiatUtils.cpp b/testing/fiat/TestFiatUtils.cpp index 1b01af7..4b49f24 100644 --- a/testing/fiat/TestFiatUtils.cpp +++ b/testing/fiat/TestFiatUtils.cpp @@ -18,9 +18,6 @@ #include "TestFiatUtils.h" #include -//#include -//#include -//#include #include #include -- 2.54.0 From 2b13984e82fd216b01f2df98c0daf4fa812efabd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 7 Jun 2025 22:23:34 +0200 Subject: [PATCH 1368/1428] Start the save method --- src/Payment.cpp | 102 +++++++++++++++++++++++++++++++- src/Payment.h | 6 +- src/RepeatPaymentDetails.cpp | 77 ++++++++++++++++++++++++ src/RepeatPaymentDetails.h | 32 ++++++++-- testing/CMakeLists.txt | 2 + testing/payment/CMakeLists.txt | 26 ++++++++ testing/payment/TestPayment.cpp | 27 +++++++++ testing/payment/TestPayment.h | 30 ++++++++++ 8 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 testing/payment/CMakeLists.txt create mode 100644 testing/payment/TestPayment.cpp create mode 100644 testing/payment/TestPayment.h diff --git a/src/Payment.cpp b/src/Payment.cpp index 9757b40..350300f 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -31,13 +31,41 @@ #include #include #include +#include +#include -// #define DEBUG_TX_CREATION +#define DEBUG_TX_CREATION #ifdef DEBUG_TX_CREATION # include # include #endif +enum SaveTags { + UserComment, // utf8 string. + WantedFee, // int. Sats per byte + + // Payment detail inputs: + // TODO + + // Payment detail output: + DestinationOutputAddress, // utf8 string. + DestinationFiatAmount, // either this one, or the next. + DestinationSatsAmount, + + // Payment detail comment (op-return): + DetailComment, // either this one, or the next + DetailCommentBytes, + + // Repeat + RepeatType, + RepeatDaysOfWeek, + RepeatWeeksOfMonth, + RepeatMonthsOfYear, + RepeatDayOfMonth, // can occur multiple times + RepeatSunsetDate, + RepeatPaymentTxIndex +}; + Payment::Payment(QObject *parent) : QObject(parent) @@ -45,6 +73,16 @@ Payment::Payment(QObject *parent) reset(); } +Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) + : QObject(parent), + m_account(new AccountInfo(parent)) +{ + assert(parent); + + // TODO +} + + void Payment::setFeePerByte(int sats) { if (m_fee == sats) @@ -497,6 +535,68 @@ void Payment::setRepeatDetails(RepeatPaymentDetails *newRepeatDetails) emit repeatDetailsChanged(); } +Streaming::ConstBuffer Payment::save() const +{ + Streaming::MessageBuilder builder(Streaming::pool(400)); + if (!m_userComment.isEmpty()) { + auto buf = m_userComment.toUtf8(); + builder.addByteArray(UserComment, buf.constBegin(), buf.size()); + } + builder.add(WantedFee, m_fee); + for (const auto detail : m_paymentDetails) { + if (detail->isInputs()) { + auto *inputs = detail->toInputs(); + // TODO + } + else if (detail->isOutput()) { + auto *out = detail->toOutput(); + auto buf = out->address().toUtf8(); + builder.addByteArray(DestinationOutputAddress, buf.constData(), buf.size()); + if (out->fiatFollows()) + builder.add(DestinationSatsAmount, out->paymentAmount()); + else + builder.add(DestinationFiatAmount, (std::uint64_t)out->paymentAmountFiat()); + } + else if (detail->isComment()) { + auto *comment = detail->toComment(); + auto commentStr = comment->commentStr(); + if (!commentStr.isEmpty()) { + auto buf = commentStr.toUtf8(); + builder.addByteArray(DetailComment, buf.constData(), buf.size()); + } + auto bytes = comment->commentBytes(); + if (!bytes.isEmpty()) + builder.add(DetailCommentBytes, bytes); + } + } + + if (m_repeatDetails && m_repeatDetails->repeatType() != WalletEnums::NoRepeat) { + builder.add(RepeatType, static_cast(m_repeatDetails->repeatType())); + builder.add(RepeatMonthsOfYear, m_repeatDetails->monthOfYear()); + switch (m_repeatDetails->repeatType()) { + case WalletEnums::NoRepeat: + assert(false); + break; + case WalletEnums::RepeatByDayOfWeek: + builder.add(RepeatDaysOfWeek, m_repeatDetails->repeatDayOfWeek()); + builder.add(RepeatWeeksOfMonth, m_repeatDetails->repeatWeekOfMonth()); + break; + case WalletEnums::RepeatByDayOfMonth: + for (const auto day : m_repeatDetails->dayOfMonth()) { + builder.add(RepeatDayOfMonth, day); + } + break; + } + builder.add(RepeatSunsetDate, + (uint64_t) m_repeatDetails->sunset().toSecsSinceEpoch()); + for (const auto txIndex : m_repeatDetails->historicalPayments()) { + builder.add(RepeatPaymentTxIndex, txIndex); + } + } + + return builder.buffer(); +} + bool Payment::simpleAddressTarget() const { return m_simpleAddressTarget; diff --git a/src/Payment.h b/src/Payment.h index d98a5c4..7e2f71e 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -19,6 +19,7 @@ #define PAYMENT_H #include "FloweePay.h" +#include "RepeatPaymentDetails.h" #include @@ -32,7 +33,6 @@ class PaymentDetailInputs; class PaymentDetailComment; class AccountInfo; class TxInfoObject; -class RepeatPaymentDetails; /** * This represents a single payment, possibly a complex one. @@ -109,6 +109,7 @@ public: Q_ENUM(Warning) Payment(QObject *parent = nullptr); + Payment(const Streaming::ConstBuffer &stored, Wallet *parent); void setFeePerByte(int sats); int feePerByte(); @@ -259,6 +260,9 @@ public: RepeatPaymentDetails *repeatDetails() const; void setRepeatDetails(RepeatPaymentDetails *newRepeatDetails); + // Store the Payment and its details in a blob. + Streaming::ConstBuffer save() const; + private slots: void recalcAmounts(); void broadcast(); diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index 3aeb399..eb15c4d 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -37,3 +37,80 @@ void RepeatPaymentDetails::setRepeatType(WalletEnums::PaymentRepeatType newRepea m_repeatType = newRepeatType; emit repeatTypeChanged(); } + +uint8_t RepeatPaymentDetails::repeatDayOfWeek() const +{ + return m_repeatDayOfWeek; +} + +void RepeatPaymentDetails::setRepeatDayOfWeek(uint8_t newRepeatDayOfWeek) +{ + m_repeatDayOfWeek = newRepeatDayOfWeek; +} + +uint8_t RepeatPaymentDetails::repeatWeekOfMonth() const +{ + return m_repeatWeekOfMonth; +} + +void RepeatPaymentDetails::setRepeatWeekOfMonth(uint8_t newRepeatWeekOfMonth) +{ + m_repeatWeekOfMonth = newRepeatWeekOfMonth; +} + +uint16_t RepeatPaymentDetails::monthOfYear() const +{ + return m_monthOfYear; +} + +void RepeatPaymentDetails::setMonthOfYear(uint16_t newMonthOfYear) +{ + m_monthOfYear = newMonthOfYear; +} + +QVector RepeatPaymentDetails::dayOfMonth() const +{ + return m_dayOfMonth; +} + +void RepeatPaymentDetails::setDayOfMonth(const QVector &newDayOfMonth) +{ + m_dayOfMonth = newDayOfMonth; +} + +void RepeatPaymentDetails::addDayOfMonth(int day) +{ + if (m_dayOfMonth.contains(day)) + return; + // TODO sort in. + m_dayOfMonth.append(day); +} + +QDateTime RepeatPaymentDetails::sunset() const +{ + return m_sunset; +} + +void RepeatPaymentDetails::setSunset(const QDateTime &newSunset) +{ + if (m_sunset == newSunset) + return; + m_sunset = newSunset; + emit sunsetChanged(); +} + +QVector RepeatPaymentDetails::historicalPayments() const +{ + return m_historicalPayments; +} + +void RepeatPaymentDetails::setHistoricalPayments(const QVector &newHistoricalPayments) +{ + m_historicalPayments = newHistoricalPayments; +} + +void RepeatPaymentDetails::addPayment(int txIndex) +{ + assert(!m_historicalPayments.contains(txIndex)); + m_historicalPayments.append(txIndex); +} diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index 1870b39..5a7e1f3 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -29,6 +29,7 @@ class RepeatPaymentDetails : public QObject { Q_OBJECT Q_PROPERTY(WalletEnums::PaymentRepeatType repeatType READ repeatType WRITE setRepeatType NOTIFY repeatTypeChanged FINAL) + Q_PROPERTY(QDateTime sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); @@ -37,8 +38,29 @@ public: + uint8_t repeatDayOfWeek() const; + void setRepeatDayOfWeek(uint8_t newRepeatDayOfWeek); + + uint8_t repeatWeekOfMonth() const; + void setRepeatWeekOfMonth(uint8_t newRepeatWeekOfMonth); + + uint16_t monthOfYear() const; + void setMonthOfYear(uint16_t newMonthOfYear); + + QVector dayOfMonth() const; + void setDayOfMonth(const QVector &newDayOfMonth); + void addDayOfMonth(int day); + + QDateTime sunset() const; + void setSunset(const QDateTime &newSunset); + + QVector historicalPayments() const; + void setHistoricalPayments(const QVector &newHistoricalPayments); + void addPayment(int txIndex); + signals: void repeatTypeChanged(); + void sunsetChanged(); private: const Payment *m_parent; @@ -58,14 +80,14 @@ private: WalletEnums::PaymentRepeatType m_repeatType = WalletEnums::NoRepeat; // type = RepeatByDayOfWeek repeat every (nth) tuesday - uint8_t m_repeatDayOfWeek = 0; // bitfield on which day of the week we repeat - uint8_t m_repeatWeekOfMonth = 0; // bitfield on which week of the month we repeat + uint8_t m_repeatDayOfWeek = 0x7f; // bitfield on which day of the week we repeat + uint8_t m_repeatWeekOfMonth = 15; // bitfield on which week of the month we repeat // type = RepeatByDayOfMonth Repeat every 15th of (every other) month - uint16_t m_monthOfYear = 0; // bitfield + uint16_t m_monthOfYear = 0x0fff; // bitfield QVector m_dayOfMonth; // list of days in the month we repeat - // after this date / time we will not send any more payments. - QDateTime m_endRepeats; + // after this date+time we will not send any more payments. + QDateTime m_sunset; QVector m_historicalPayments; // txIndex's in the wallet }; diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index bd428c4..e9fadc7 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -24,11 +24,13 @@ if (${Qt6Test_FOUND}) add_subdirectory(priceHistory) add_subdirectory(fiat) add_subdirectory(utils) + add_subdirectory(payment) add_custom_target(check COMMAND LANG=C ${CMAKE_CTEST_COMMAND} DEPENDS test_wallet test_wallethistorymodel test_value + test_payment test_price_history test_fiat test_utils diff --git a/testing/payment/CMakeLists.txt b/testing/payment/CMakeLists.txt new file mode 100644 index 0000000..6c92142 --- /dev/null +++ b/testing/payment/CMakeLists.txt @@ -0,0 +1,26 @@ +# This file is part of the Flowee project +# Copyright (C) 2025 Tom Zander +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(CMAKE_AUTOMOC ON) + +include_directories(${Qt6Test_INCLUDE_DIRS}) + +add_executable(test_payment + TestPayment.cpp + ${CMAKE_SOURCE_DIR}/src/ModuleManager_empty.cpp +) +target_link_libraries(test_payment pay_lib Qt6::Test) +add_test(NAME Pay_test_Payment COMMAND test_payment) diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp new file mode 100644 index 0000000..a9976d9 --- /dev/null +++ b/testing/payment/TestPayment.cpp @@ -0,0 +1,27 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "TestPayment.h" + +#include +#include + +void TestPayment::basic() +{ +} + +QTEST_MAIN(TestPayment) diff --git a/testing/payment/TestPayment.h b/testing/payment/TestPayment.h new file mode 100644 index 0000000..1cde3ef --- /dev/null +++ b/testing/payment/TestPayment.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef TEST_PAYMENT_H +#define TEST_PAYMENT_H + +#include + +class TestPayment : public QObject +{ + Q_OBJECT +private slots: + void basic(); +}; + +#endif -- 2.54.0 From 5b40359ee6aa200e90d75f4c0818c7775f7db7c8 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 8 Jun 2025 12:46:00 +0200 Subject: [PATCH 1369/1428] Add tdd Payment saving code --- src/Payment.cpp | 78 +++++++++++++++++++++- src/Payment.h | 2 +- testing/payment/TestPayment.cpp | 115 ++++++++++++++++++++++++++++++++ testing/payment/TestPayment.h | 9 +++ 4 files changed, 201 insertions(+), 3 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 350300f..eee7c6c 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -43,6 +43,7 @@ enum SaveTags { UserComment, // utf8 string. WantedFee, // int. Sats per byte + ExchangePrice, // cents per whole bch // Payment detail inputs: // TODO @@ -51,6 +52,7 @@ enum SaveTags { DestinationOutputAddress, // utf8 string. DestinationFiatAmount, // either this one, or the next. DestinationSatsAmount, + DestinationOutScript, // Payment detail comment (op-return): DetailComment, // either this one, or the next @@ -79,7 +81,74 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) { assert(parent); - // TODO + PaymentDetailOutput *curOut = nullptr; + Streaming::MessageParser parser(stored); + while (parser.next() == Streaming::FoundTag) { + switch (parser.tag()) { + case UserComment: { + auto bytes = parser.bytesDataBuffer(); + m_userComment = QString::fromUtf8(bytes.begin(), bytes.size()); + break; + } + case WantedFee: // int. Sats per byte + m_fee = parser.intData(); + break; + case ExchangePrice: + m_fiatPrice = parser.intData(); + break; + + // Payment detail output: + case DestinationOutScript: // fall through + case DestinationOutputAddress: { + curOut = new PaymentDetailOutput(this); + auto bytes = parser.bytesDataBuffer(); + if (parser.tag() == DestinationOutputAddress) + curOut->setAddress(QString::fromUtf8(bytes.begin(), bytes.size())); + else + curOut->setOutputScript(bytes); + m_paymentDetails.append(curOut); + break; + } + case DestinationFiatAmount: // either this one, or the next. + assert(curOut); + curOut->setFiatFollows(false); + curOut->setPaymentAmountFiat(parser.longData()); + break; + case DestinationSatsAmount: + assert(curOut); + curOut->setFiatFollows(true); + curOut->setPaymentAmount(parser.doubleData()); + break; + + // Payment detail comment (op-return): + case DetailComment: // fall through + case DetailCommentBytes: { + curOut = nullptr; + auto *curComment = new PaymentDetailComment(this); + auto bytes = parser.bytesDataBuffer(); + if (parser.tag() == DetailComment) + curComment->setCommentStr(QString::fromUtf8(bytes.begin(), bytes.size())); + else + curComment->setCommentBytes(bytes); + m_paymentDetails.append(curComment); + break; + } + + // Repeat + case RepeatType: + curOut = nullptr; + // TODO + break; + case RepeatDaysOfWeek: + case RepeatWeeksOfMonth: + case RepeatMonthsOfYear: + case RepeatDayOfMonth: // can occur multiple times + case RepeatSunsetDate: + case RepeatPaymentTxIndex: + assert(false); + break; + } + } } @@ -543,6 +612,7 @@ Streaming::ConstBuffer Payment::save() const builder.addByteArray(UserComment, buf.constBegin(), buf.size()); } builder.add(WantedFee, m_fee); + builder.add(ExchangePrice, m_fiatPrice); for (const auto detail : m_paymentDetails) { if (detail->isInputs()) { auto *inputs = detail->toInputs(); @@ -551,7 +621,11 @@ Streaming::ConstBuffer Payment::save() const else if (detail->isOutput()) { auto *out = detail->toOutput(); auto buf = out->address().toUtf8(); - builder.addByteArray(DestinationOutputAddress, buf.constData(), buf.size()); + // either the address or the script are filled. Or neither. + if (!out->outputScript().isEmpty()) + builder.add(DestinationOutScript, out->outputScript()); + else + builder.addByteArray(DestinationOutputAddress, buf.constData(), buf.size()); if (out->fiatFollows()) builder.add(DestinationSatsAmount, out->paymentAmount()); else diff --git a/src/Payment.h b/src/Payment.h index 7e2f71e..c9ecf3a 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -320,7 +320,7 @@ private: QString m_error; QString m_userComment; - RepeatPaymentDetails *m_repeatDetails; + RepeatPaymentDetails *m_repeatDetails = nullptr; }; diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index a9976d9..76119d7 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -17,11 +17,126 @@ */ #include "TestPayment.h" +#include +#include +#include +#include #include #include +#include + +std::unique_ptr TestPayment::createWallet() +{ + if (m_dir.isEmpty()) { + QString basedir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + m_dir = basedir + QString("/floweepay-%1/").arg(QCoreApplication::instance()->applicationPid()); + } + + std::unique_ptr wallet(Wallet::createWallet(m_dir.toStdString(), 1111, "test")); + return wallet; +} + void TestPayment::basic() { + Streaming::ConstBuffer saveFile; + { + Payment payment; + payment.setPaymentAmount(1001000); + payment.setUserComment("my comment"); + QCOMPARE(payment.paymentAmount(), 1001000); + payment.setFeePerByte(80); + payment.setFiatPrice(40000); // price per coin. + QCOMPARE(payment.feePerByte(), 80); + QCOMPARE(payment.paymentAmount(), 1001000); + QCOMPARE(payment.paymentAmountFiat(), 400); + QCOMPARE(payment.userComment(), "my comment"); + + saveFile = payment.save(); + } + auto dummy = createWallet(); + { + Payment payment(saveFile, dummy.get()); + QCOMPARE(payment.feePerByte(), 80); + QCOMPARE(payment.paymentAmount(), 1001000); + QCOMPARE(payment.paymentAmountFiat(), 400); + QCOMPARE(payment.userComment(), "my comment"); + } +} + +void TestPayment::saveOutput() +{ + Streaming::ConstBuffer saveFile; + { + Payment payment; + payment.setFiatPrice(40000); // price per coin. + auto det = payment.paymentDetails(); + QCOMPARE(det.size() , 1); + auto *out = qobject_cast(det.at(0)); + QVERIFY(out != nullptr); + out->setPaymentAmount(999011124); + out->setFiatFollows(true); + out->setAddress("hello"); + + saveFile = payment.save(); + } + auto dummy = createWallet(); + { + Payment payment(saveFile, dummy.get()); + auto det = payment.paymentDetails(); + QCOMPARE(det.size() , 1); + auto *out = qobject_cast(det.at(0)); + QVERIFY(out != nullptr); + QCOMPARE(out->paymentAmount(), 999011124); + QCOMPARE(out->fiatFollows(), true); + QVERIFY(out->outputScript().isEmpty()); + QCOMPARE(out->address(), "hello"); + QCOMPARE(payment.repeatDetails(), nullptr); + + const auto copy = out->paymentAmountFiat(); + out->setFiatFollows(false); + out->setPaymentAmountFiat(copy); + saveFile = payment.save(); + } + + Streaming::BufferPool pool; + pool.write("someScript"); + Streaming::ConstBuffer outScript = pool.commit(); + QCOMPARE(outScript.size(), 10); + { + Payment payment(saveFile, dummy.get()); + auto det = payment.paymentDetails(); + QCOMPARE(det.size() , 1); + auto *out = qobject_cast(det.at(0)); + QVERIFY(out != nullptr); + QCOMPARE(out->fiatFollows(), false); + QCOMPARE(out->paymentAmount(), 999010000); // some rounding. + QVERIFY(out->outputScript().isEmpty()); + QCOMPARE(out->address(), "hello"); + QCOMPARE(payment.repeatDetails(), nullptr); + + out->setOutputScript(outScript); + QVERIFY(out->address().isEmpty()); + saveFile = payment.save(); + } + + { + Payment payment(saveFile, dummy.get()); + auto det = payment.paymentDetails(); + QCOMPARE(det.size() , 1); + auto *out = qobject_cast(det.at(0)); + QVERIFY(out != nullptr); + QCOMPARE(out->fiatFollows(), false); + QCOMPARE(out->paymentAmount(), 999010000); + QCOMPARE(out->outputScript(), outScript); + QVERIFY(out->address().isEmpty()); + QCOMPARE(payment.repeatDetails(), nullptr); + } +} + +void TestPayment::saveRepeat() +{ + } QTEST_MAIN(TestPayment) diff --git a/testing/payment/TestPayment.h b/testing/payment/TestPayment.h index 1cde3ef..305ab3e 100644 --- a/testing/payment/TestPayment.h +++ b/testing/payment/TestPayment.h @@ -19,12 +19,21 @@ #define TEST_PAYMENT_H #include +#include + +class Wallet; class TestPayment : public QObject { Q_OBJECT private slots: void basic(); + void saveOutput(); + void saveRepeat(); + +private: + std::unique_ptr createWallet(); + QString m_dir; }; #endif -- 2.54.0 From 32d886c53c0d78f3fd6ebfeaf776058e3f2e904c Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 16 Jun 2025 22:30:49 +0200 Subject: [PATCH 1370/1428] hmm? --- modules/big-transfer/ShowPrepared.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/big-transfer/ShowPrepared.qml b/modules/big-transfer/ShowPrepared.qml index 529d5ac..16da4e1 100644 --- a/modules/big-transfer/ShowPrepared.qml +++ b/modules/big-transfer/ShowPrepared.qml @@ -227,7 +227,6 @@ Mobile.Page { visible: root.transferManager.unsentTxCount === 0 anchors.bottom: parent.bottom anchors.bottomMargin: 10 - enabled: false onClicked: { var mainView = thePile.get(0); mainView.currentIndex = 0; // go to the 'main' tab. -- 2.54.0 From 6792f1f1a63070c5f7105563170502b416b642b5 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 20:52:05 +0200 Subject: [PATCH 1371/1428] Fixes; sometimes import partially uses name --- guis/mobile/ImportWalletPage.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 19a702b..3fcbe2e 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -427,7 +427,7 @@ Page { Flowee.BigButton { id: privImportStartButton - text: qsTr("Start") + text: qsTr("Import") Layout.alignment: Qt.AlignRight onClicked: { @@ -444,10 +444,10 @@ Page { function startImport() { if (privKeyImportHelper.resultCount > 0) { - var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) + var options = Pay.createImportedWallet(secretText.text, accountName.totalText, privKeyImportHelper.startHeight(0)) } else { var height = root.heightOfBlockAtTime(oldestTransactionChooser.item.selectedDate); - options = Pay.createImportedWallet(secretText.text, accountName.text, height) + options = Pay.createImportedWallet(secretText.text, accountName.totalText, height) } options.forceSingleAddress = singleAddress.checked; @@ -602,12 +602,12 @@ Page { function startImport() { if (seedImportHelper.resultCount > 0) { var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), + derivationPath.text, accountName2.totalText, seedImportHelper.startHeight(0), seedImportHelper.isElectrumSeed(0)); } else { var height = root.heightOfBlockAtTime(oldestTransactionChooser2.item.selectedDate); options = Pay.createImportedHDWallet(secretText.text, passwordField.text, - derivationPath.text, accountName2.text, height); + derivationPath.text, accountName2.totalText, height); } for (let a of portfolio.accounts) { -- 2.54.0 From 697dad6c3bfbb3fd679c37748fa0b56c8ac1c408 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 21:03:26 +0200 Subject: [PATCH 1372/1428] Avoid 'undefined' text in UI. When the default wallet is unavailable, avoid showing a JavaScript 'undefined' as text. --- guis/mobile/SelectDefaultConfigButton.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/guis/mobile/SelectDefaultConfigButton.qml b/guis/mobile/SelectDefaultConfigButton.qml index 41c680b..45a2d84 100644 --- a/guis/mobile/SelectDefaultConfigButton.qml +++ b/guis/mobile/SelectDefaultConfigButton.qml @@ -1,6 +1,6 @@ /* * This file is part of the Flowee project - * Copyright (C) 2022-2024 Tom Zander + * Copyright (C) 2022-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,12 +22,15 @@ TextButton { text: qsTr("Default Wallet") visible: !portfolio.singleAccountSetup subtext: { + var defaultAccount = ""; for (let a of portfolio.rawAccounts) { if (a.isPrimaryAccount) { - var defaultAccount = a.name; + defaultAccount = a.name; break; } } + if (defaultAccount === "") + return ""; qsTr("%1 is used on startup").arg(defaultAccount); } -- 2.54.0 From 185e74beae9b6254a03cd63d3858e9d03983406e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 21:32:02 +0200 Subject: [PATCH 1373/1428] New month --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 8af5d47..46d64fd 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="42" android:versionName="2025.06.0"> diff --git a/src/main.cpp b/src/main.cpp index 271efcf..51a0229 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.05.1"); + qapp.setApplicationVersion("2025.06.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 046da7515dcced184faedc42cc002a79e2c65f62 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 22:42:54 +0200 Subject: [PATCH 1374/1428] Fix spelling. I just noticed I've been near consistently writing "anonimity" in Flowee Pay, spellcheck tells me it should be "anonymity". Word blindness is fun. --- modules/big-transfer/BigTransferModuleInfo.cpp | 2 +- modules/big-transfer/Main.qml | 2 +- modules/big-transfer/README.md | 6 +++--- src/NotificationManager.cpp | 2 +- src/Wallet_spending.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/big-transfer/BigTransferModuleInfo.cpp b/modules/big-transfer/BigTransferModuleInfo.cpp index f71e7b9..469e3b7 100644 --- a/modules/big-transfer/BigTransferModuleInfo.cpp +++ b/modules/big-transfer/BigTransferModuleInfo.cpp @@ -31,7 +31,7 @@ BigTransferModuleInfo::BigTransferModuleInfo() { setId("bigTransfer"); setTitle(tr("Wallet to Wallet")); - setDescription(tr("Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses.")); + setDescription(tr("Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses.")); setPriority(5); ModuleSection *sendTab = new ModuleSection(ModuleSection::SendMethod, this); diff --git a/modules/big-transfer/Main.qml b/modules/big-transfer/Main.qml index 4554e63..04f7560 100644 --- a/modules/big-transfer/Main.qml +++ b/modules/big-transfer/Main.qml @@ -34,7 +34,7 @@ Mobile.Page { Flowee.Label { Layout.fillWidth: true wrapMode: Text.WordWrap - text: qsTr("Select two wallets to transfer funds simply, using anonimity preserving transactions.") + text: qsTr("Select two wallets to transfer funds simply, using anonymity preserving transactions.") } Mobile.TextButton { diff --git a/modules/big-transfer/README.md b/modules/big-transfer/README.md index f9442e6..a6f5a80 100644 --- a/modules/big-transfer/README.md +++ b/modules/big-transfer/README.md @@ -1,14 +1,14 @@ This module provides a screen that allows a wallet to have most or all -of its content transferred to another wallet, without giving up anonimity. +of its content transferred to another wallet, without giving up anonymity. If you have a large number of coins in a wallet then the normal "send all" method will simply merge all coins into one transaction. The downside of creating one transaction is that it indicates to anyone checking the blockchain that all those addresses are owned by the same person. Thereby -giving up any anonimity those addresses would have provided. +giving up any anonymity those addresses would have provided. This module instead creates a transaction per address that is in use in the old wallet and picks a fresh address from the target wallet in order -to avoid the obvious joining that would give away our anonimity. +to avoid the obvious joining that would give away our anonymity. diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 30e4505..eb1d46f 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -128,7 +128,7 @@ QString NotificationManager::describeCollated(int &txCount) const txCount += item.walletTxIds.size(); } if (txCount == cfFound) { - return tr("We received %1 anonimity transactions.", nullptr, txCount).arg(txCount); + return tr("We received %1 anonymity transactions.", nullptr, txCount).arg(txCount); } const auto gained = deposited - spent; diff --git a/src/Wallet_spending.cpp b/src/Wallet_spending.cpp index e1d656c..a16507a 100644 --- a/src/Wallet_spending.cpp +++ b/src/Wallet_spending.cpp @@ -70,7 +70,7 @@ int Wallet::scoreForSolution(const OutputSet &set, int64_t change, size_t unspen // wallet utxo-count is managed by the fuser and output counts are // not relevant to us. // The priority lies in not joining fused outputs which is a big - // anonimity-diminishing thing and thus better avoided. + // anonymity-diminishing thing and thus better avoided. int foundFusionOutputs = 0; for (const auto &output : set.outputs) { -- 2.54.0 From 1a32bd4eedc5bfd520d29848416bbd4bb15d26a7 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 23:02:19 +0200 Subject: [PATCH 1375/1428] minor fixes --- src/NotificationManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index eb1d46f..46fb589 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -139,7 +139,7 @@ QString NotificationManager::describeCollated(int &txCount) const .arg(FloweePay::instance()->amountToStringPretty(std::abs((double) gained)), FloweePay::instance()->unitName()); - if (true || !pricesOracle->oldData()) { + if (!pricesOracle->oldData()) { // price data available. Add fiat. sentStr += " (" + pricesOracle->formattedPrice(gained, pricesOracle->price(), PriceDataProvider::NoSign) @@ -161,7 +161,7 @@ QString NotificationManager::describeCollated(int &txCount) const if (data.size() > 1) return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); if (txCount == 1) - return tr("Received %1 ", "The %1 is replaced with amount of money").arg(gainedStr); + return tr("Received %1", "The %1 is replaced with amount of money").arg(gainedStr); return tr("Received %1 in %2 transactions", "%1 is amount of money", txCount) .arg(gainedStr).arg(txCount); } -- 2.54.0 From be4ed07afe0e51204f7954b580900ab73403d8b2 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 23:09:13 +0200 Subject: [PATCH 1376/1428] Unconfuse translators --- src/NotificationManager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/NotificationManager.cpp b/src/NotificationManager.cpp index 46fb589..a35dfd3 100644 --- a/src/NotificationManager.cpp +++ b/src/NotificationManager.cpp @@ -128,7 +128,7 @@ QString NotificationManager::describeCollated(int &txCount) const txCount += item.walletTxIds.size(); } if (txCount == cfFound) { - return tr("We received %1 anonymity transactions.", nullptr, txCount).arg(txCount); + return tr("We received %1 anonymity transactions", nullptr, txCount).arg(txCount); } const auto gained = deposited - spent; @@ -160,9 +160,7 @@ QString NotificationManager::describeCollated(int &txCount) const } if (data.size() > 1) return tr("%1 new transactions across %2 wallets found (%3)").arg(txCount).arg(data.size()).arg(gainedStr); - if (txCount == 1) - return tr("Received %1", "The %1 is replaced with amount of money").arg(gainedStr); - return tr("Received %1 in %2 transactions", "%1 is amount of money", txCount) + return tr("Received: %1 in %2 transactions", "%1 is amount of money", txCount) .arg(gainedStr).arg(txCount); } -- 2.54.0 From 0e617db1fd715bd182af5c81ecce6cb9b7bf493e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 17 Jun 2025 23:34:38 +0200 Subject: [PATCH 1377/1428] Import translations from Crowdin --- translations/floweepay-common_de.ts | 113 ++++---- translations/floweepay-common_en.ts | 113 ++++---- translations/floweepay-common_ha.ts | 151 +++++++---- translations/floweepay-common_nl.ts | 113 ++++---- translations/floweepay-common_pl.ts | 121 +++++---- translations/floweepay-desktop_en.ts | 204 +++++++------- translations/floweepay-desktop_ha.ts | 282 ++++++++++---------- translations/floweepay-mobile_de.ts | 35 ++- translations/floweepay-mobile_en.ts | 201 ++++++++------ translations/floweepay-mobile_ha.ts | 257 ++++++++++-------- translations/floweepay-mobile_nl.ts | 35 ++- translations/floweepay-mobile_pl.ts | 35 ++- translations/module-bigtransfer_de.ts | 6 +- translations/module-bigtransfer_en.ts | 26 +- translations/module-bigtransfer_ha.ts | 58 ++-- translations/module-bigtransfer_nl.ts | 6 +- translations/module-bigtransfer_pl.ts | 6 +- translations/module-build-transaction_en.ts | 18 +- translations/module-build-transaction_ha.ts | 18 +- translations/module-send-sweep_ha.ts | 26 +- translations/module-social-feed_ha.ts | 8 +- 21 files changed, 1009 insertions(+), 823 deletions(-) diff --git a/translations/floweepay-common_de.ts b/translations/floweepay-common_de.ts index 3d0a5f0..a1ee210 100644 --- a/translations/floweepay-common_de.ts +++ b/translations/floweepay-common_de.ts @@ -87,33 +87,38 @@ BroadcastFeedback - + + Finding Network + Suche Netzwerk + + + Sending Payment Sende Zahlung - + Payment Sent Zahlung gesendet - + Failed Fehlgeschlagen - + Transaction rejected by network Transaktion wurde vom Netzwerk abgelehnt - + The payment has been sent to: Followed by the address Die Zahlung wurde gesendet an: - + Add a personal note Eine persönliche Notiz hinzufügen @@ -142,30 +147,30 @@ FloweePay - + Initial Wallet Initiale Geldbörse - - + + Today Heute - - + + Yesterday Gestern - + Now timestamp Jetzt - + %1 minutes ago relative time stamp @@ -174,13 +179,13 @@ - + ½ hour ago timestamp vor ½ Stunde - + %1 hours ago timestamp @@ -199,11 +204,6 @@ MenuModel - - - Explore - Erkunden - Settings @@ -224,48 +224,55 @@ Wallets Geldbörsen + + + Explore + Erkunden + NotificationManager - - - %1 new transactions across %2 wallets found (%3) - %1 neue Transaktionen über %2 Geldbörsen gefunden (%3) - - - - A payment of %1 has been sent - Eine Zahlung von %1 wurde gesendet - New Block Found Neuen Block gefunden - + Transaction between two wallets found. the wallets are both yours Transaktion zwischen zwei Geldbörsen gefunden. - - We received %1 anonimity transactions. + + We received %1 anonymity transactions - Wir haben %1 anonyme Transaktion erhalten. - Wir haben %1 anonyme Transaktionen erhalten. + Wir haben %1 anonyme Transaktion erhalten + Wir haben %1 anonyme Transaktionen erhalten + + + + + %1 has been sent + The %1 is replaced with amount of money + %1 wurde gesendet + + + + %1 new transactions across %2 wallets found (%3) + %1 neue Transaktionen über %2 Geldbörsen gefunden (%3) + + + + Received: %1 in %2 transactions + %1 is amount of money + + Empfangen: %1 + Empfangen: %1 in %2 Transaktionen - - %1 new transactions found (%2) - - %1 neue Transaktion gefunden (%2) - %1 neue Transaktionen gefunden (%2) - - - - + Your transaction got mined form on number of transactions @@ -274,7 +281,7 @@ - + Your transaction has %1 confirmations form on number of transactions @@ -283,7 +290,7 @@ - + Your transaction got another confirmation form on number of transactions @@ -312,37 +319,37 @@ Payment - + Invalid PIN Ungültige PIN - + Wallet is locked Geldbörse ist gesperrt - + Not enough funds selected for fees Nicht genug Guthaben für Gebühren ausgewählt - + Not enough funds in wallet to make payment! Nicht genügend Guthaben in der Geldbörse, um eine Zahlung zu machen! - + Transaction too large. Amount selected needs too many coins. Transaktion zu groß. Der ausgewählte Betrag benötigt zu viele Coins. - + Request received over insecure channel. Anyone could have altered it! Anfrage über unsicheren Kanal empfangen. Jeder hätte sie verändern können! - + Download of payment request failed. Download der Zahlungsanfrage fehlgeschlagen. diff --git a/translations/floweepay-common_en.ts b/translations/floweepay-common_en.ts index b4176fd..4e5d6f8 100644 --- a/translations/floweepay-common_en.ts +++ b/translations/floweepay-common_en.ts @@ -87,33 +87,38 @@ BroadcastFeedback - + + Finding Network + Finding Network + + + Sending Payment Sending Payment - + Payment Sent Payment Sent - + Failed Failed - + Transaction rejected by network Transaction rejected by network - + The payment has been sent to: Followed by the address The payment has been sent to: - + Add a personal note Add a personal note @@ -142,30 +147,30 @@ FloweePay - + Initial Wallet Initial Wallet - - + + Today Today - - + + Yesterday Yesterday - + Now timestamp Now - + %1 minutes ago relative time stamp @@ -174,13 +179,13 @@ - + ½ hour ago timestamp ½ hour ago - + %1 hours ago timestamp @@ -199,11 +204,6 @@ MenuModel - - - Explore - Explore - Settings @@ -224,48 +224,55 @@ Wallets Wallets + + + Explore + Explore + NotificationManager - - - %1 new transactions across %2 wallets found (%3) - %1 new transactions across %2 wallets found (%3) - - - - A payment of %1 has been sent - A payment of %1 has been sent - New Block Found New Block Found - + Transaction between two wallets found. the wallets are both yours Transaction between two wallets found. - - We received %1 anonimity transactions. + + We received %1 anonymity transactions - We received %1 anonimity transactions. - We received %1 anonimity transactions. + We received an anonymity transaction + We received %1 anonymity transactions + + + + + %1 has been sent + The %1 is replaced with amount of money + %1 has been sent + + + + %1 new transactions across %2 wallets found (%3) + %1 new transactions across %2 wallets found (%3) + + + + Received: %1 in %2 transactions + %1 is amount of money + + Received: %1 + Received: %1 in %2 transactions - - %1 new transactions found (%2) - - %1 new transaction found (%2) - %1 new transactions found (%2) - - - - + Your transaction got mined form on number of transactions @@ -274,7 +281,7 @@ - + Your transaction has %1 confirmations form on number of transactions @@ -283,7 +290,7 @@ - + Your transaction got another confirmation form on number of transactions @@ -312,37 +319,37 @@ Payment - + Invalid PIN Invalid PIN - + Wallet is locked Wallet is locked - + Not enough funds selected for fees Not enough funds selected for fees - + Not enough funds in wallet to make payment! Not enough funds in wallet to make payment! - + Transaction too large. Amount selected needs too many coins. Transaction too large. Amount selected needs too many coins. - + Request received over insecure channel. Anyone could have altered it! Request received over insecure channel. Anyone could have altered it! - + Download of payment request failed. Download of payment request failed. diff --git a/translations/floweepay-common_ha.ts b/translations/floweepay-common_ha.ts index 337e180..7779deb 100644 --- a/translations/floweepay-common_ha.ts +++ b/translations/floweepay-common_ha.ts @@ -87,33 +87,38 @@ BroadcastFeedback - + + Finding Network + Finding Network + + + Sending Payment Aika Biyan Kuɗi - + Payment Sent An aika Biya - + Failed Ba a yi nasara ba - + Transaction rejected by network hanyar sadarwa ta ƙi ciniki - + The payment has been sent to: Followed by the address An aika biyan kuɗi zuwa: - + Add a personal note Ƙara bayanin kula na sirri @@ -142,30 +147,30 @@ FloweePay - + Initial Wallet Asusu na farko - - + + Today Yau - - + + Yesterday Jiya - + Now timestamp Yanzu - + %1 minutes ago relative time stamp @@ -174,13 +179,13 @@ - + ½ hour ago timestamp ½ awa da ya wuce - + %1 hours ago timestamp @@ -199,11 +204,6 @@ MenuModel - - - Explore - Bincika - Settings @@ -224,91 +224,132 @@ Wallets Asusu + + + Explore + Bincika + NotificationManager - + + New Block Found + An Samu Sabon Toshe + + + + Transaction between two wallets found. + the wallets are both yours + An sami ma'amala tsakanin wallet biyu. + + + + We received %1 anonymity transactions + + Mun sami %1 mu'amalar rashin mutunci + Mun sami %1 mu'amalar rashin mutunci + + + + + %1 has been sent + The %1 is replaced with amount of money + %1 has been sent + + + %1 new transactions across %2 wallets found (%3) %1 sabbin ma'amaloli a cikin %2 asusun da aka samu (%3) - - - A payment of %1 has been sent - An aika biyan kuɗi na %1 + + + Received: %1 in %2 transactions + %1 is amount of money + + Received: %1 in %2 transactions + Received: %1 in %2 transactions + - - %1 new transactions found (%2) + + Your transaction got mined + form on number of transactions - %1 sababbin ma'amaloli akasamu (%2) - %1 sababbin ma'amaloli aka samu (%2) + Kasuwancin ku ya sami ma'adinai + Kasuwancin ku ya sami ma'adinai + + + + + Your transaction has %1 confirmations + form on number of transactions + + Ma'amalar ku tana da tabbaci %1 + Ma'amalar ku tana da tabbaci %1 + + + + + Your transaction got another confirmation + form on number of transactions + + Kasuwancin ku ya sami wani tabbaci + Kasuwancin ku ya sami wani tabbaci NotificationManagerPrivate - - - Bitcoin Cash block mined. Height: %1 - Bitcoin Cash tubali hako ma'adanai ne. Tsawo: %1 - - - - - tBCH (testnet4) block mined: %1 - tBCH (testnet4) toshe ma'adinai: %1 - - - + Mute Shiru - + New Transactions dialog-title - + + Sabbin Ma'amaloli Sabbin Ma'amaloli - New Transactions Payment - + Invalid PIN Makulli Mara inganci - + Wallet is locked - Wallet is locked + Wallet yana kulle - + Not enough funds selected for fees Babu isassun kuɗin da aka zaɓa don kuɗi - + Not enough funds in wallet to make payment! Babu isassun kuɗi a cikin asusu don biyan kuɗi! - + Transaction too large. Amount selected needs too many coins. Ma'amala yayi girma sosai. Adadin da aka zaɓa yana buƙatar tsabar kuɗi da yawa. - + Request received over insecure channel. Anyone could have altered it! An karɓi buƙatar akan tashar da ba ta da tsaro. Kowa zai iya canza shi! - + Download of payment request failed. Sauke buƙatar biyan kuɗi ya gagara. @@ -498,7 +539,7 @@ b) tsabar kudi na tarihi. QR of Address - QR of Address + QR na Adireshi @@ -508,7 +549,7 @@ b) tsabar kudi na tarihi. QR of Private Key - QR of Private Key + QR na Keɓaɓɓen Maɓalli diff --git a/translations/floweepay-common_nl.ts b/translations/floweepay-common_nl.ts index 144bce1..432c920 100644 --- a/translations/floweepay-common_nl.ts +++ b/translations/floweepay-common_nl.ts @@ -87,33 +87,38 @@ BroadcastFeedback - + + Finding Network + Zoeken naar netwerk + + + Sending Payment Betaling wordt verzonden - + Payment Sent Betaling Verzonden - + Failed Mislukt - + Transaction rejected by network Transactie afgewezen door het netwerk - + The payment has been sent to: Followed by the address Betaling is verzonden naar: - + Add a personal note Voeg een persoonlijke notitie toe @@ -142,30 +147,30 @@ FloweePay - + Initial Wallet Eerste portemonnee - - + + Today Vandaag - - + + Yesterday Gisteren - + Now timestamp Nu - + %1 minutes ago relative time stamp @@ -174,13 +179,13 @@ - + ½ hour ago timestamp Half uur geleden - + %1 hours ago timestamp @@ -199,11 +204,6 @@ MenuModel - - - Explore - Ontdek - Settings @@ -224,48 +224,55 @@ Wallets Portemonnees + + + Explore + Ontdek + NotificationManager - - - %1 new transactions across %2 wallets found (%3) - %1 nieuwe transacties in %2 portemonnees gevonden (%3) - - - - A payment of %1 has been sent - Een betaling van %1 is verzonden - New Block Found Nieuw blok gevonden - + Transaction between two wallets found. the wallets are both yours Transactie voor twee portemonnees gevonden. - - We received %1 anonimity transactions. + + We received %1 anonymity transactions - We hebben een anonimiteit transactie ontvangen. - We hebben %1 anonimiteit transacties ontvangen. + We hebben een anonimiteit transactie ontvangen + We hebben %1 anonimiteit transacties ontvangen + + + + + %1 has been sent + The %1 is replaced with amount of money + %1 is verzonden + + + + %1 new transactions across %2 wallets found (%3) + %1 nieuwe transacties in %2 portemonnees gevonden (%3) + + + + Received: %1 in %2 transactions + %1 is amount of money + + Ontvangen: %1 + Ontvangen: %1 in %2 transacties - - %1 new transactions found (%2) - - %1 nieuwe transactie gevonden (%2) - %1 nieuwe transacties gevonden (%2) - - - - + Your transaction got mined form on number of transactions @@ -274,7 +281,7 @@ - + Your transaction has %1 confirmations form on number of transactions @@ -283,7 +290,7 @@ - + Your transaction got another confirmation form on number of transactions @@ -312,37 +319,37 @@ Payment - + Invalid PIN Ongeldige PIN - + Wallet is locked Portemonnee is vergrendeld - + Not enough funds selected for fees Onvoldoende saldo voor transactiekosten - + Not enough funds in wallet to make payment! Niet genoeg saldo in portemonnee om te betalen! - + Transaction too large. Amount selected needs too many coins. Transactie te groot. Geselecteerd bedrag vereist te veel munten. - + Request received over insecure channel. Anyone could have altered it! Verzoek ontvangen via onveilig kanaal. Hij kan door eenieder gewijzigd zijn! - + Download of payment request failed. Downloaden van betalingsverzoek mislukt. diff --git a/translations/floweepay-common_pl.ts b/translations/floweepay-common_pl.ts index 7a6b1c0..e3d392c 100644 --- a/translations/floweepay-common_pl.ts +++ b/translations/floweepay-common_pl.ts @@ -93,33 +93,38 @@ BroadcastFeedback - + + Finding Network + Szukanie sieci + + + Sending Payment Wysyłanie Płatności - + Payment Sent Płatność Wysłana - + Failed Niepowodzenie - + Transaction rejected by network Transakcja odrzucona przez sieć - + The payment has been sent to: Followed by the address Płatność została wysłana do: - + Add a personal note Dodaj osobistą notatkę @@ -148,30 +153,30 @@ FloweePay - + Initial Wallet Portfel Początkowy - - + + Today Dzisiaj - - + + Yesterday Wczoraj - + Now timestamp Teraz - + %1 minutes ago relative time stamp @@ -182,13 +187,13 @@ - + ½ hour ago timestamp ½ godziny temu - + %1 hours ago timestamp @@ -209,11 +214,6 @@ MenuModel - - - Explore - Eksploruj - Settings @@ -234,52 +234,59 @@ Wallets Portfele + + + Explore + Eksploruj + NotificationManager - - - %1 new transactions across %2 wallets found (%3) - %1 nowe transakcje w %2 portfelach (%3) - - - - A payment of %1 has been sent - Wysłano płatność w wysokości %1 - New Block Found Znaleziono nowy blok - + Transaction between two wallets found. the wallets are both yours Znaleziono transakcję pomiędzy dwoma portfelami. - - We received %1 anonimity transactions. + + We received %1 anonymity transactions - Otrzymano %1 zanonimizowaną transakcję. - Otrzymano %1 zanonimizowane transakcje. - Otrzymano %1 zanonimizowanych transakcji. - Otrzymano %1 zanonimizowanej transakcji. + Otrzymano %1 zanonimizowaną transakcję + Otrzymano %1 zanonimizowane transakcje + Otrzymano %1 zanonimizowanych transakcji + Otrzymano %1 zanonimizowanej transakcji + + + + + %1 has been sent + The %1 is replaced with amount of money + Wysłano %1 + + + + %1 new transactions across %2 wallets found (%3) + %1 nowe transakcje w %2 portfelach (%3) + + + + Received: %1 in %2 transactions + %1 is amount of money + + Otrzymano %1 + Otrzymano: %1 w %2 transakcjach + Otrzymano: %1 w %2 transakcjach + Otrzymano: %1 w %2 transakcji - - %1 new transactions found (%2) - - Znaleziono %1 nową transakcję (%2) - Znaleziono %1 nowe transakcje (%2) - Znaleziono %1 nowych transakcji (%2) - Znaleziono %1 nowej transakcji (%2) - - - - + Your transaction got mined form on number of transactions @@ -290,7 +297,7 @@ - + Your transaction has %1 confirmations form on number of transactions @@ -301,7 +308,7 @@ - + Your transaction got another confirmation form on number of transactions @@ -334,37 +341,37 @@ Payment - + Invalid PIN Nieprawidłowy PIN - + Wallet is locked Portfel zablokowany - + Not enough funds selected for fees Nie wybrano wystarczającej ilości środków, by pokryć koszt transakcji - + Not enough funds in wallet to make payment! Niewystarczająca ilość środków w portfelu, aby dokonać płatności! - + Transaction too large. Amount selected needs too many coins. Transakcja jest zbyt duża. Wybrana kwota wymaga zbyt wielu monet. - + Request received over insecure channel. Anyone could have altered it! Żądanie otrzymane przez niezabezpieczony kanał. Ktoś mógł je zmodyfikować! - + Download of payment request failed. Pobieranie żądania płatności nie powiodło się. diff --git a/translations/floweepay-desktop_en.ts b/translations/floweepay-desktop_en.ts index cde2b72..75b0007 100644 --- a/translations/floweepay-desktop_en.ts +++ b/translations/floweepay-desktop_en.ts @@ -54,108 +54,108 @@ Wallet Details - + Name Name - + Sync Status Sync Status - + Encryption Encryption - - + + Password Password - + Open Open - + Invalid PIN Invalid PIN - + Include balance in total Include balance in total - + Hide in private mode Hide in private mode - + Address List Address List - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Backup details Backup details - + Seed-phrase Seed-phrase - + Copy Copy - + Seed format Seed format - + Derivation Derivation - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. @@ -365,79 +365,79 @@ This ensures only one private key will need to be backed up Secret as text - + Unknown word(s) found Unknown word(s) found - + Address to import Address to import - - + + New Wallet Name New Wallet Name - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + Start Start - + Discover Details online check for wallet details Discover Details - + Derivation Derivation - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Password - + imported wallet password imported wallet password @@ -631,7 +631,7 @@ Change will come back to the imported key. - + Warning Warning @@ -687,65 +687,65 @@ Change will come back to the imported key. Send - + Destination Destination - + Max available The maximum balance available Max available - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Enter Bitcoin Cash Address - - + + Copy Address Copy Address - + Amount Amount - + Max Max - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? - + Continue Continue - + Cancel Cancel - + Coin Selector Coin Selector - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -754,78 +754,78 @@ Change will come back to the imported key. - + Total Number of coins Total - + Needed Needed - + Selected Selected - + Value Value - + Locked coins will never be used for payments. Right-click for menu. Locked coins will never be used for payments. Right-click for menu. - + Age Age - + Unselect All Unselect All - + Select All Select All - + Unlock coin Unlock coin - + Lock coin Lock coin - + Public-comment Public-comment - + Add a comment you want to include in the transaction, visible for everyone. Add a comment you want to include in the transaction, visible for everyone. - + Custom message, to be included in the transaction. Custom message, to be included in the transaction. - + Text Text - + Size Size @@ -848,57 +848,47 @@ Change will come back to the imported key. Show Bitcoin Cash value on Activity page - - Show Block Notifications - Show Block Notifications - - - - When a new block is mined, Flowee Pay shows a desktop notification - When a new block is mined, Flowee Pay shows a desktop notification - - - + Night Mode Night Mode - + Private Mode Private Mode - + Hides private wallets while enabled Hides private wallets while enabled - + Font sizing Font sizing - + Version Version - + Library Version Library Version - + Synchronization Synchronization - + Network Status Network Status - + Address Stats Address Stats @@ -1239,95 +1229,95 @@ Change will come back to the imported key. main - + Activity Activity - + Archived wallets do not check for activities. Balance may be out of date. Archived wallets do not check for activities. Balance may be out of date. - + Unarchive Unarchive - + This wallet needs a password to open. This wallet needs a password to open. - + Password: Password: - + Invalid password Invalid password - + Open Open - + Send Send - + Receive Receive - + Balance Balance - + Main balance (money), non specified Main - + Unconfirmed balance (money) Unconfirmed - + Immature balance (money) Immature - + 1 BCH is: %1 1 BCH is: %1 - + Network status Network status - + Offline Offline - + Add Bitcoin Cash wallet Add Bitcoin Cash wallet - + Archived wallets [%1] Arg is wallet count @@ -1336,12 +1326,12 @@ Change will come back to the imported key. - + Preparing... Preparing... - + QR-Scan QR-Scan diff --git a/translations/floweepay-desktop_ha.ts b/translations/floweepay-desktop_ha.ts index 6e1b264..63a1fdc 100644 --- a/translations/floweepay-desktop_ha.ts +++ b/translations/floweepay-desktop_ha.ts @@ -54,108 +54,108 @@ Bayanan asusun ajiya - + Name Suna - + Sync Status Daidaita matsayi - + Encryption Rufewa - - + + Password Kwadon shiga - + Open Bude - + Invalid PIN Makulli Mara inganci - + Include balance in total Haɗa ma'aunin chanji baki ɗaya - + Hide in private mode Ɓoye a yanayin sirri - + Address List Jerin adireshi - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Backup details Ajiyayyen bayanai - + Seed-phrase Jimlar iri - + Copy Kwafi - + Seed format Tsarin iri - + Derivation Samo asali - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password. Wannan asusun na tsare da almar sirri (pin-to-pay). Don ganin bayanan ciki akwai buƙatar samar da kalmar wucewa. @@ -173,7 +173,7 @@ Filter - Filter + Tacewa @@ -193,42 +193,42 @@ IP Addresses - IP Addresses + Adireshin da aka yi amfani da shi Total found - Total found + An samu jimlar Tried - Tried + An gwada Punished count - Punished count + Ƙididdigar azabtarwa Banned count - Banned count + An haramta kirga IP-v4 count - IP-v4 count + IP-v4 ƙidaya IP-v6 count - IP-v6 count + IP-v6 ƙidaya Pardon the Banned - Pardon the Banned + Yafewa Wanda Aka Haramta @@ -270,12 +270,12 @@ Disconnect Peer - Disconnect Peer + Cire haɗin Peer Ban Peer - Ban Peer + Haramta tsara @@ -345,12 +345,12 @@ This ensures only one private key will need to be backed up Select import method - Select import method + Zaɓi hanyar shigo da kaya Camera - Camera + Kamera @@ -361,83 +361,83 @@ This ensures only one private key will need to be backed up Secret as text The seed-phrase or private key - Secret as text + Sirri kamar rubutu - + Unknown word(s) found - Unknown word(s) found + An samo kalmar(s) da ba'a sani ba - + Address to import - Address to import + Adireshin shigo da kaya - - + + New Wallet Name - New Wallet Name + Sabon Sunan Wallet - + Force Single Address Tilasta Adireshi Guda - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age - Check Age + Duba Shekaru - + Nothing found for wallet - Nothing found for wallet + Babu wani abu da aka samo don walat - - + + Start - Start + Fara - + Discover Details online check for wallet details - Discover Details + Gano Cikakken Bayani - + Derivation Samo asali - + Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Babu wani abu da aka samo don iri. Shin yana da kalmar sirri? - + Password Kwadon shiga - + imported wallet password - imported wallet password + kalmar sirrin walat da aka shigo da ita @@ -445,7 +445,7 @@ Change will come back to the imported key. New HD wallet - New HD wallet + Sabon Asusun HD @@ -483,7 +483,7 @@ Change will come back to the imported key. New Basic Wallet - New Basic Wallet + Sabon Asusun Basic @@ -524,12 +524,12 @@ Change will come back to the imported key. Comment - Comment + Sharhi This allows adding a public comment, that will be included in the transaction and seen by everyone. - This allows adding a public comment, that will be included in the transaction and seen by everyone. + Wannan yana ba da damar ƙara bayanin jama'a, wanda za'a haɗa cikin ma'amala kuma kowa ya gani. @@ -557,7 +557,7 @@ Change will come back to the imported key. High risk transaction - High risk transaction + Babban ma'amala haɗari @@ -629,7 +629,7 @@ Change will come back to the imported key. - + Warning Gargaɗi @@ -685,65 +685,65 @@ Change will come back to the imported key. Aika - + Destination Madakata - + Max available The maximum balance available Mafi girman samuwa - + %1 to %2 summary text to pay X-euro to address M %1 to %2 - + Enter Bitcoin Cash Address Shigar da Adireshin Kuɗi na Bitcoin - - + + Copy Address Kwafi Adireshi - + Amount Adadi - + Max Matsakaici - + This is a BTC address, which is an incompatible coin. Your funds could get lost and Flowee will have no way to recover them. Are you sure this is the right address? Wannan adireshin BTC ne, wanda tsabar kudin da bai dace ba. Kuɗin ku na iya yin asara kuma Flowee ba za ta sami hanyar dawo da su ba. Shin kun tabbata wannan shine daidai adireshin? - + Continue Ci gaba - + Cancel Soke - + Coin Selector Zaɓin Tsabar kudi - + Selected %1 %2 in %3 coins selected 2 BCH in 5 coins @@ -752,78 +752,78 @@ Change will come back to the imported key. - + Total Number of coins Jimilla - + Needed Ake Bukata - + Selected An zaɓa - + Value Daraja - + Locked coins will never be used for payments. Right-click for menu. Kullallun tsabar kudi ba za a taɓa amfani da su don biyan kuɗi ba. Danna-dama don menu. - + Age Shekaru - + Unselect All Cire Zaɓi Duk - + Select All Zaɓi Duk - + Unlock coin Buɗe tsabar kudi - + Lock coin Kulle tsabar kudi - + Public-comment - Public-comment + Bayanin jama'a - + Add a comment you want to include in the transaction, visible for everyone. - Add a comment you want to include in the transaction, visible for everyone. + Ƙara sharhi da kuke son haɗawa a cikin ma'amala, bayyane ga kowa. - + Custom message, to be included in the transaction. - Custom message, to be included in the transaction. + Saƙon al'ada, don haɗawa cikin ciniki. - + Text - Text + Rubutu - + Size Girman @@ -846,59 +846,49 @@ Change will come back to the imported key. Nuna ƙimar Bitcoin Cash akan shafin Aiki - - Show Block Notifications - Nuna Sanarwa da ta toshe - - - - When a new block is mined, Flowee Pay shows a desktop notification - Lokacin da aka haƙa sabon toshe, Flowee Pay yana nuna sanarwar tebur - - - + Night Mode Yanayin dare - + Private Mode Yanayin sirri - + Hides private wallets while enabled Ɓoye sirrin asusu yayin kunnawa - + Font sizing Girman haruffa - + Version Siga - + Library Version Sigar Laburare - + Synchronization Haɗa Aiki tare - + Network Status Matsayin hanyar sadarwa - + Address Stats - Address Stats + Adadin adireshi @@ -944,7 +934,7 @@ Change will come back to the imported key. First Seen - First Seen + Farkon Gani @@ -967,12 +957,12 @@ Change will come back to the imported key. Waiting for block - Waiting for block + Jiran toshewa Comment - Comment + Sharhi @@ -997,7 +987,7 @@ Change will come back to the imported key. Is Coinbase - Is Coinbase + Coinbase @@ -1231,101 +1221,101 @@ Change will come back to the imported key. Already running? - Already running? + Ya riga ya gudana? main - + Activity Ayyuka - + Archived wallets do not check for activities. Balance may be out of date. Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya tsufa. - + Unarchive Cire Takardu - + This wallet needs a password to open. Wannan asusun tana buƙatar kalmar sirri don buɗewa. - + Password: Kalmar wucewa: - + Invalid password Kalmar shiga mara inganci - + Open Bude - + Send Aika - + Receive Karɓa - + Balance Sauran kudi - + Main balance (money), non specified Babban - + Unconfirmed balance (money) Ba'a tabbatar ba - + Immature balance (money) Rashin cika - + 1 BCH is: %1 1 BCH shi ne: %1 - + Network status Matsayin hanyar sadarwa - + Offline Baya kan na'ura - + Add Bitcoin Cash wallet Sanya asusun Bitcoin Cash - + Archived wallets [%1] Arg is wallet count @@ -1334,14 +1324,14 @@ Change will come back to the imported key. - + Preparing... Shiryawa... - + QR-Scan - QR-Scan + QR-Duba diff --git a/translations/floweepay-mobile_de.ts b/translations/floweepay-mobile_de.ts index 00ebd8a..07b5a13 100644 --- a/translations/floweepay-mobile_de.ts +++ b/translations/floweepay-mobile_de.ts @@ -438,6 +438,10 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. + Import + Import + + Start Start @@ -601,7 +605,12 @@ Wechselgeld wird zum importierten Schlüssel zurückgeführt. Für eine bessere Erfahrung, aktivieren Sie Flowee Pay Hintergrundsynchronisation. - + + Start + Start + + + hide ausblenden @@ -758,37 +767,37 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussBetrag bearbeiten - + Invalid QR code Ungültiger QR Code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ich verstehe den gescannten Code nicht. Ich entschuldige mich, ich kann keine Zahlung starten. - + details details - + Scanned text: <pre>%1</pre> Gescannter Text: <pre>%1</pre> - + Payment description Zahlungsbeschreibung - + Destination Address Zieladresse - + Unlock Wallet Geldbörse entsperren @@ -934,7 +943,7 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussStandard-Geldbörse - + %1 is used on startup %1 wird beim Starten verwendet @@ -1323,4 +1332,12 @@ Dies stellt sicher, dass nur ein privater Schlüssel gesichert werden mussÖffnen + + locked + + + Already running? + Wird bereits ausgeführt? + + diff --git a/translations/floweepay-mobile_en.ts b/translations/floweepay-mobile_en.ts index 5a11b80..81caed8 100644 --- a/translations/floweepay-mobile_en.ts +++ b/translations/floweepay-mobile_en.ts @@ -76,131 +76,146 @@ Archived wallets do not check for activities. Balance may be out of date - + + To connect this wallet + To connect this wallet + + + + Your XPub details + Your XPub details + + + + Be careful who you share the xpub with! + Be careful who you share the xpub with! + + + Backup information Backup information - + Backup Details Backup Details - + Wallet seed-phrase Wallet seed-phrase - + Password Password - + Seed format Seed format - - + + Starting Height height refers to block-height Starting Height - + Derivation Path Derivation Path - + xpub xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. - + <b>Important</b>: Never share your seed-phrase with others! <b>Important</b>: Never share your seed-phrase with others! - + Wallet keys Wallet keys - + Show Index toggle to show numbers Show Index - + Change Addresses Change Addresses - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. - + Used Addresses Used Addresses - + Switches between unused and used Bitcoin addresses Switches between unused and used Bitcoin addresses - + Addresses and keys Addresses and keys - + Sync Status Sync Status - + Hide balance in overviews Hide balance in overviews - + Hide in private mode Hide in private mode - + Unarchive Wallet Unarchive Wallet - + Archive Wallet Archive Wallet - + Really Delete? Really Delete? - + Removing wallet "%1" can not be undone. argument is the wallet name Removing wallet "%1" can not be undone. - + Remove Wallet Remove Wallet @@ -282,20 +297,31 @@ Background Synchronization - + Without background synchronization your wallets will not see new transactions until you open Flowee Pay Without background synchronization your wallets will not see new transactions until you open Flowee Pay - + Allow Background Synchronization Allow Background Synchronization - + Every %1 hours Every %1 hours + + + Notifications + Notifications + + + + On Payments + in relation to notifications + On Payments + CurrencySelector @@ -361,84 +387,88 @@ Secret as text - + Unknown word(s) found Unknown word(s) found - + Next Next - + Address to import Address to import - - + + New Wallet Name New Wallet Name - + Force Single Address Force Single Address - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. - - + + Oldest Transaction Oldest Transaction - + Check Age online check for wallet age Check Age - + Nothing found for wallet Nothing found for wallet - - + + Import + Import + + + Start Start - + Discover Details online check for wallet details Discover Details - + Derivation Path Derivation Path - + Nothing found for seed. Does it have a password? Nothing found for seed. Does it have a password? - + Password Password - + imported wallet password imported wallet password @@ -446,12 +476,12 @@ Change will come back to the imported key. InstaPayConfigButton - + Enable Instant Pay Enable Instant Pay - + Configure Instant Pay Configure Instant Pay @@ -565,10 +595,25 @@ Change will come back to the imported key. MainViewBase - + No Internet Available No Internet Available + + + For a better experience, enable Flowee Pay background synchronization. + For a better experience, enable Flowee Pay background synchronization. + + + + Start + Start + + + + hide + hide + MenuOverlay @@ -722,37 +767,37 @@ This ensures only one private key will need to be backed up Edit Amount - + Invalid QR code Invalid QR code - + I don't understand the scanned code. I'm sorry, I can't start a payment. I don't understand the scanned code. I'm sorry, I can't start a payment. - + details details - + Scanned text: <pre>%1</pre> Scanned text: <pre>%1</pre> - + Payment description Payment description - + Destination Address Destination Address - + Unlock Wallet Unlock Wallet @@ -787,7 +832,7 @@ This ensures only one private key will need to be backed up PriceInputWidget - + All Currencies All Currencies @@ -805,12 +850,12 @@ This ensures only one private key will need to be backed up Share this QR to receive - + Encrypted Wallet Encrypted Wallet - + Import Running... Import Running... @@ -820,13 +865,13 @@ This ensures only one private key will need to be backed up Description - + Address Bitcoin Cash address Address - + Clear Clear @@ -898,7 +943,7 @@ This ensures only one private key will need to be backed up Default Wallet - + %1 is used on startup %1 is used on startup @@ -993,16 +1038,6 @@ This ensures only one private key will need to be backed up Light Light - - - Notifications - Notifications - - - - On new block found - On new block found - SlideToApprove @@ -1245,32 +1280,32 @@ This ensures only one private key will need to be backed up TransactionListItem - + Miner Reward Miner Reward - + Fused Fused - + Received Received - + Moved Moved - + Sent Sent - + Rejected Rejected @@ -1297,4 +1332,12 @@ This ensures only one private key will need to be backed up Open + + locked + + + Already running? + Already running? + + diff --git a/translations/floweepay-mobile_ha.ts b/translations/floweepay-mobile_ha.ts index bdd9385..da16c2f 100644 --- a/translations/floweepay-mobile_ha.ts +++ b/translations/floweepay-mobile_ha.ts @@ -27,7 +27,7 @@ © 2020-2025 Tom Zander and contributors - © 2020-2025 Tom Zander and contributors + © 2020-2025 Tom Zander da masu ba da gudummawa @@ -76,133 +76,148 @@ Asusun ɗin da aka adana ba sa bincika ayyuka. Ma'auni na iya zama ya ƙare - + + To connect this wallet + Don haɗa wannan walat + + + + Your XPub details + Bayanin XPub naku + + + + Be careful who you share the xpub with! + Yi hankali da wanda kuke rabawa tare da xpub! + + + Backup information Ajiyayyen Bayanai - + Backup Details Ajiyayyen bayanai - + Wallet seed-phrase Asusun Jumla iri-iri - + Password Kwadon shiga - + Seed format Tsarin iri - - + + Starting Height height refers to block-height Fara tsawo - + Derivation Path Hanyar Fitowa - + xpub Xpub - + Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case you lose your mobile. Da fatan za a ajiye jimlar iri akan takarda, cikin tsari mai kyau, tare da hanyar da aka samo asali. Wannan nau'in zai ba ku damar dawo da walat ɗin ku idan akwai gazawar kwamfuta. - + <b>Important</b>: Never share your seed-phrase with others! <b>Muhimmanci</b>: Kada ku taɓa raba jumlar irin ku ga wasu! - + Wallet keys Maɓallan asusu - + Show Index toggle to show numbers Nuna index - + Change Addresses Chanjin adireshi - + Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself. Canjawa tsakanin jerin adireshin da wasu za su iya biyan ku dashi, da adiresoshin da asusun ɗin ke amfani da su don aika canji ga kanku. - + Used Addresses Adireshin da aka yi amfani da shi - + Switches between unused and used Bitcoin addresses Canzawa tsakanin adiresoshin Bitcoin da akayi amfani da wanda ba'a yi amfani da su ba - + Addresses and keys Adireshi da maɓallai - + Sync Status Daidaita matsayi - + Hide balance in overviews Boye ma'auni a cikin bayyani - + Hide in private mode Boye a yanayin sirri - + Unarchive Wallet Ma'ajiyin taskoki - + Archive Wallet Ma'ajiyin taskoki - + Really Delete? - Really Delete? + Da gaske Zaka Share? - + Removing wallet "%1" can not be undone. argument is the wallet name - Removing wallet "%1" can not be undone. + Cire walat "%1" ba za'a iya sakewa ba. - + Remove Wallet - Remove Wallet + Cire Wallet @@ -279,22 +294,33 @@ Background Synchronization - Background Synchronization + Aiki tare a bayan fage - + Without background synchronization your wallets will not see new transactions until you open Flowee Pay - Without background synchronization your wallets will not see new transactions until you open Flowee Pay + Idan ba aiki tare a bayan fage ba walat ɗin ku ba za su ga sabbin ma'amaloli ba har sai kun buɗe Pay na Flowee - + Allow Background Synchronization - Allow Background Synchronization + Barin Aiki tare a bayan fage - + Every %1 hours - Every %1 hours + Kowane %1 awa + + + + Notifications + Sanarwa + + + + On Payments + in relation to notifications + Akan Biyan Kuɗi @@ -323,13 +349,13 @@ Transactions Filter - Transactions Filter + Tace ma'amaloli Only with a comment This is a statement about a transaction - Only with a comment + Tare da sharhi kawai @@ -342,12 +368,12 @@ Select import method - Select import method + Zaɓi hanyar shigo da kaya Scan QR - Scan QR + Duba QR @@ -358,99 +384,103 @@ Secret as text The seed-phrase or private key - Secret as text + Sirri kamar rubutu - + Unknown word(s) found - Unknown word(s) found + An samo kalmar(s) da ba'a sani ba - + Next - Next + Na gaba - + Address to import - Address to import + Adireshin shigo da kaya - - + + New Wallet Name - New Wallet Name + Sabon Sunan Wallet - + Force Single Address Tilasta Adireshi Guda Daya - + When enabled, no extra addresses will be auto-generated in this wallet. Change will come back to the imported key. Lokacin da aka kunna, ba za'a samar da ƙarin adireshi ta atomatik a cikin wannan wallet ɗin ba. Canji zai dawo kan maɓallin da aka shigo da shi. - - + + Oldest Transaction Tsohon ciniki - + Check Age online check for wallet age - Check Age + Duba Shekaru - + Nothing found for wallet - Nothing found for wallet + Babu wani abu da aka samo don walat - - + + Import + Import + + + Start - Start + Fara - + Discover Details online check for wallet details - Discover Details + Gano Cikakken Bayani - + Derivation Path Hanyar Fitowa - + Nothing found for seed. Does it have a password? - Nothing found for seed. Does it have a password? + Babu wani abu da aka samo don iri. Shin yana da kalmar sirri? - + Password Kwadon shiga - + imported wallet password - imported wallet password + kalmar sirrin walat da aka shigo da ita InstaPayConfigButton - + Enable Instant Pay Kunna Biyan Nan take - + Configure Instant Pay Sanya Biyan Nan take @@ -458,7 +488,7 @@ Change will come back to the imported key. Limits for %1 argument is a name - Limits for %1 + Iyaka na %1 @@ -564,10 +594,25 @@ Change will come back to the imported key. MainViewBase - + No Internet Available No Internet Available + + + For a better experience, enable Flowee Pay background synchronization. + For a better experience, enable Flowee Pay background synchronization. + + + + Start + Fara + + + + hide + hide + MenuOverlay @@ -625,7 +670,7 @@ Change will come back to the imported key. New Basic Wallet - New Basic Wallet + Sabon Asusun Basic @@ -720,37 +765,37 @@ This ensures only one private key will need to be backed up Gyara Adadin - + Invalid QR code Lambar QR mara inganci - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ban fahimci lambar da aka bincika ba. Yi hakuri, ba zan iya fara biya ba. - + details bayanai - + Scanned text: <pre>%1</pre> Rubutun da aka duba: <pre>%1</pre> - + Payment description Bayanin biyan kuɗi - + Destination Address Adireshin Zuwa - + Unlock Wallet Buɗe Asusu @@ -785,7 +830,7 @@ This ensures only one private key will need to be backed up PriceInputWidget - + All Currencies Ireiren kuɗaɗe @@ -803,12 +848,12 @@ This ensures only one private key will need to be backed up Raba wannan QR don karɓa - + Encrypted Wallet Rufaffen asusu - + Import Running... Tsarin Shiga na gudu... @@ -818,13 +863,13 @@ This ensures only one private key will need to be backed up Bayani - + Address Bitcoin Cash address Adireshi - + Clear Share @@ -837,7 +882,7 @@ This ensures only one private key will need to be backed up High risk transaction - High risk transaction + Babban ma'amala haɗari @@ -896,7 +941,7 @@ This ensures only one private key will need to be backed up Tsohuwar asusu - + %1 is used on startup %1 is used on startup @@ -954,7 +999,7 @@ This ensures only one private key will need to be backed up Background Synchronization - Background Synchronization + Aiki tare a bayan fage @@ -991,16 +1036,6 @@ This ensures only one private key will need to be backed up Light Light - - - Notifications - Notifications - - - - On new block found - On new block found - SlideToApprove @@ -1087,7 +1122,7 @@ This ensures only one private key will need to be backed up First Seen - First Seen + Farkon Gani @@ -1243,32 +1278,32 @@ This ensures only one private key will need to be backed up TransactionListItem - + Miner Reward Ladan Ma'adinai - + Fused Fuskanci - + Received An samu - + Moved Motsa - + Sent An aika - + Rejected An ƙii @@ -1295,4 +1330,12 @@ This ensures only one private key will need to be backed up Bude + + locked + + + Already running? + Ya riga ya gudana? + + diff --git a/translations/floweepay-mobile_nl.ts b/translations/floweepay-mobile_nl.ts index e8138be..d1ab014 100644 --- a/translations/floweepay-mobile_nl.ts +++ b/translations/floweepay-mobile_nl.ts @@ -438,6 +438,10 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. + Import + Importeer + + Start Start @@ -601,7 +605,12 @@ Wisselgeld gaat weer naar de geïmporteerde sleutel. Schakel Flowee Pay achtergrond synchronisatie in voor een betere ervaring. - + + Start + Start + + + hide verberg @@ -758,37 +767,37 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Bedrag aanpassen - + Invalid QR code Ongeldige QR-code - + I don't understand the scanned code. I'm sorry, I can't start a payment. Ik begrijp de gelezen code niet. Sorry, ik kan de betaling niet starten. - + details details - + Scanned text: <pre>%1</pre> Gelezen tekst: <pre>%1</pre> - + Payment description Omschrijving betaling - + Destination Address Bestemmingsadres - + Unlock Wallet Portemonnee ontgrendelen @@ -934,7 +943,7 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Standaard portemonnee - + %1 is used on startup %1 wordt gebruikt bij start @@ -1323,4 +1332,12 @@ Dit zorgt ervoor dat er slechts één privésleutel zal moeten worden geback-upt Open + + locked + + + Already running? + Al actief? + + diff --git a/translations/floweepay-mobile_pl.ts b/translations/floweepay-mobile_pl.ts index 8429fdd..9f4d28e 100644 --- a/translations/floweepay-mobile_pl.ts +++ b/translations/floweepay-mobile_pl.ts @@ -437,6 +437,10 @@ Change will come back to the imported key. + Import + Importuj + + Start Rozpocznij @@ -600,7 +604,12 @@ Change will come back to the imported key. Włącz synchronizację w tle dla Flowee Pay. Ułatwi to korzystanie z aplikacji. - + + Start + Rozpocznij + + + hide ukryj @@ -757,37 +766,37 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoEdytuj kwotę - + Invalid QR code Nieprawidłowy kod QR - + I don't understand the scanned code. I'm sorry, I can't start a payment. Nie rozumiem zeskanowanego kodu. Przepraszam, mogę rozpocząć płatności. - + details szczegóły - + Scanned text: <pre>%1</pre> Zeskanowany tekst: <pre>%1</pre> - + Payment description Opis płatności - + Destination Address Adres docelowy - + Unlock Wallet Odblokuj portfel @@ -933,7 +942,7 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoDomyślny Portfel - + %1 is used on startup %1 jest używany przy starcie @@ -1326,4 +1335,12 @@ Pozwala to na tworzenie kopii zapasowej tylko jednego klucza prywatnegoOtwórz + + locked + + + Already running? + Już uruchomione? + + diff --git a/translations/module-bigtransfer_de.ts b/translations/module-bigtransfer_de.ts index 47b626f..b562a19 100644 --- a/translations/module-bigtransfer_de.ts +++ b/translations/module-bigtransfer_de.ts @@ -12,7 +12,7 @@ - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. Verschieben Sie viele Münzen zwischen den Geldbörsen. Optimieren Sie die Anonymität, indem Sie nur eine Transaktion pro Ursprungsadresse machen, während Sie sie auf verschiedene Zieladressen aufteilen können. @@ -30,7 +30,7 @@ - Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonymity preserving transactions. Wähle zwei Geldbörsen, um Guthaben zu übertragen, mittels Verwendung von Transaktionen, die die Anonymität wahren. @@ -88,7 +88,7 @@ - + Target Coins indicates a number Ziel Münzen diff --git a/translations/module-bigtransfer_en.ts b/translations/module-bigtransfer_en.ts index 1aecb22..8577ea0 100644 --- a/translations/module-bigtransfer_en.ts +++ b/translations/module-bigtransfer_en.ts @@ -12,8 +12,8 @@ - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. @@ -29,32 +29,32 @@ Wallet to Wallet - - Select two wallets to transfer funds simply, using anonimity preserving transactions. - Select two wallets to transfer funds simply, using anonimity preserving transactions. + + Select two wallets to transfer funds simply, using anonymity preserving transactions. + Select two wallets to transfer funds simply, using anonymity preserving transactions. - - Spending Wallet - Spending Wallet + + Select Spending Wallet + Select Spending Wallet - + Addresses Addresses - + Coins Coins - + Destination Wallet Destination Wallet - + Prepare... Prepare... @@ -88,7 +88,7 @@ - + Target Coins indicates a number Target Coins diff --git a/translations/module-bigtransfer_ha.ts b/translations/module-bigtransfer_ha.ts index 7cdd695..dc9e746 100644 --- a/translations/module-bigtransfer_ha.ts +++ b/translations/module-bigtransfer_ha.ts @@ -8,17 +8,17 @@ Wallet to Wallet - Wallet to Wallet + Wallet zuwa Wallet - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. + Matsar da tsabar kudi da yawa tsakanin wallets, inganta don rashin amincewa ta hanyar ba da ma'amala guda ɗaya a kowane adireshi yayin ba da damar rarraba akan adiresoshin daban-daban. Move funds to another wallet - Move funds to another wallet + Matsar da kuɗi zuwa wani walat @@ -26,37 +26,37 @@ Wallet to Wallet - Wallet to Wallet + Wallet zuwa Wallet - - Select two wallets to transfer funds simply, using anonimity preserving transactions. - Select two wallets to transfer funds simply, using anonimity preserving transactions. + + Select two wallets to transfer funds simply, using anonymity preserving transactions. + Zaɓi walat guda biyu don canja wurin kuɗi a sauƙaƙe, ta amfani da ma'amaloli masu ɓoyewa. - - Spending Wallet - Spending Wallet + + Select Spending Wallet + Zaɓi Wallet ɗin da ake kashewa - + Addresses - Addresses + Adiresuna - + Coins - Coins + Tsabar kudi - + Destination Wallet - Destination Wallet + Makomar Wallet - + Prepare... - Prepare... + Shiryawa... @@ -64,34 +64,34 @@ Verify %1 Transactions - - Verify %1 Transactions - Verify %1 Transactions + + Tabbatar da %1 Ma'amaloli + Tabbatar da %1 Ma'amaloli Coins - Coins + Tsabar kudi Send Now - Send Now + Aika Yanzu Create and send all %1 transactions - - Create and send all %1 transactions - Create and send all %1 transactions + + Ƙirƙiri kuma aika duk %1 ma'amaloli + Ƙirƙiri kuma aika duk %1 ma'amaloli - + Target Coins indicates a number - Target Coins + Manufar Tsabar diff --git a/translations/module-bigtransfer_nl.ts b/translations/module-bigtransfer_nl.ts index 4557aa9..3164e70 100644 --- a/translations/module-bigtransfer_nl.ts +++ b/translations/module-bigtransfer_nl.ts @@ -12,7 +12,7 @@ - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. Verplaats veel munten tussen portemonnees, optimaliseer voor anonimiteit met één transactie per adres. Het is mogelijk om ze over verschillende adressen op te splitsen. @@ -30,7 +30,7 @@ - Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonymity preserving transactions. Selecteer twee portemonnees om geld over te maken, met behulp van anonimiteit behoudende transacties. @@ -88,7 +88,7 @@ - + Target Coins indicates a number Bestemmingsmunten diff --git a/translations/module-bigtransfer_pl.ts b/translations/module-bigtransfer_pl.ts index 73a394f..c1aa4e8 100644 --- a/translations/module-bigtransfer_pl.ts +++ b/translations/module-bigtransfer_pl.ts @@ -12,7 +12,7 @@ - Move many coins between wallets, optimize for anonimity by offering one transaction per address while allowing it to split over various addresses. + Move many coins between wallets, optimize for anonymity by offering one transaction per address while allowing it to split over various addresses. Przenieś wiele monet między portfelami, optymalizując pod kątem anonimowości poprzez oferowanie jednej transakcji na adres, pozwalając na podział na różne adresy. @@ -30,7 +30,7 @@ - Select two wallets to transfer funds simply, using anonimity preserving transactions. + Select two wallets to transfer funds simply, using anonymity preserving transactions. Wybierz dwa portfele, aby przenieść środki używając transakcji zapewniających anonimowość. @@ -92,7 +92,7 @@ - + Target Coins indicates a number Docelowe Monety diff --git a/translations/module-build-transaction_en.ts b/translations/module-build-transaction_en.ts index 251dbb2..d3196ac 100644 --- a/translations/module-build-transaction_en.ts +++ b/translations/module-build-transaction_en.ts @@ -84,7 +84,7 @@ - + Add Destination Add Destination @@ -136,44 +136,44 @@ %1 sat/byte - + Destination Destination - + unset indication of desination not being set unset - + invalid address is not correct invalid - + Copy Address Copy Address - + Drag to Edit Drag to Edit - + Drag to Delete Drag to Delete - + Unlock Wallet Unlock Wallet - + Prepare Payment... Prepare Payment... diff --git a/translations/module-build-transaction_ha.ts b/translations/module-build-transaction_ha.ts index 4dc8241..84b0653 100644 --- a/translations/module-build-transaction_ha.ts +++ b/translations/module-build-transaction_ha.ts @@ -84,7 +84,7 @@ - + Add Destination Ƙara madakata @@ -136,44 +136,44 @@ %1 sat/bytes - + Destination Madakata - + unset indication of desination not being set unset - + invalid address is not correct Ba daidai ba - + Copy Address Kwafi Adireshi - + Drag to Edit Jawo don Gyarawa - + Drag to Delete Jawo don gogewa - + Unlock Wallet Buɗe Asusu - + Prepare Payment... Shirya Biya... diff --git a/translations/module-send-sweep_ha.ts b/translations/module-send-sweep_ha.ts index 7442b4e..ea4ae38 100644 --- a/translations/module-send-sweep_ha.ts +++ b/translations/module-send-sweep_ha.ts @@ -6,20 +6,20 @@ Sweep coins - Sweep coins + Cire tsabar kudi Sweeping from address: - Sweeping from address: + Zazzagewa daga adireshin: Found %1 coins on address. this is a simple number - - Found %1 coins on address. - Found %1 coins on address. + + An samo %1 tsabar kudi akan adireshi. + An samo %1 tsabar kudi akan adireshi. @@ -27,24 +27,24 @@ Ignoring %1 tokens. Number of CashTokens - Ignoring %1 tokens. + Yin watsi da %1 alamu. Ignoring %1 tokens. Failed to understand QR - Failed to understand QR + An kasa fahimtar QR Indexer results invalid. Please try again. - Indexer results invalid. Please try again. + Sakamakon indexer ba daidai ba ne. Da fatan za'a sake gwadawa. Transfer to: - Transfer to: + Canja wurin zuwa: @@ -52,17 +52,17 @@ Claim Cash Stamp - Claim Cash Stamp + Da'awar Cash Stamp A QR code with a CashStamp can be taken to transfer the money to your wallet. - A QR code with a CashStamp can be taken to transfer the money to your wallet. + Ana iya ɗaukar lambar QR tare da CashStamp don canja wurin kuɗin zuwa walat ɗin ku. Claim a Cash Stamp - Claim a Cash Stamp + Da'awar Cash Stamp @@ -71,7 +71,7 @@ Scan QR (WIF) to find funds Please note that WIF and QR are names - Scan QR (WIF) to find funds + Duba QR (WIF) don nemo kuɗi diff --git a/translations/module-social-feed_ha.ts b/translations/module-social-feed_ha.ts index 921a735..1ffafb5 100644 --- a/translations/module-social-feed_ha.ts +++ b/translations/module-social-feed_ha.ts @@ -6,12 +6,12 @@ Videos - Videos + Bidiyo Run time: - Run time: + Lokacin gudu: @@ -19,12 +19,12 @@ Help and Learning - Help and Learning + Taimako da Koyo Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. - Want to see the experts show how to use Bitcoin Cash with Flowee Pay? Find all you want via this library of videos. + Kuna son ganin masana sun nuna yadda ake amfani da Bitcoin Cash tare da Pay na Flowee? Nemo duk abin da kuke so ta wannan ɗakin karatu na bidiyo. -- 2.54.0 From dfabcde813fa9cc02c25a1932f890c889a48cdfd Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Jun 2025 17:48:29 +0200 Subject: [PATCH 1378/1428] Use Basic QQ Controls theme The non-themed import basically is just a proxy using some auto-detection to find out which theme to use. As the app only uses the basic theme, this is what we'll import. --- guis/Flowee/AddressLabel.qml | 2 +- guis/Flowee/BigButton.qml | 2 +- guis/Flowee/BitcoinAmountLabel.qml | 2 +- guis/Flowee/BroadcastFeedback.qml | 2 +- guis/Flowee/Button.qml | 2 +- guis/Flowee/CFIcon.qml | 2 +- guis/Flowee/CheckBox.qml | 2 +- guis/Flowee/CheckBoxLabel.qml | 2 +- guis/Flowee/ComboBox.qml | 2 +- guis/Flowee/Dialog.qml | 2 +- guis/Flowee/DialogButtonBox.qml | 2 +- guis/Flowee/GroupBox.qml | 2 +- guis/Flowee/ImageButton.qml | 2 +- guis/Flowee/Label.qml | 2 +- guis/Flowee/LabelWithClipboard.qml | 2 +- guis/Flowee/MultilineTextField.qml | 2 +- guis/Flowee/RadioButton.qml | 2 +- guis/Flowee/ScrollThumb.qml | 2 +- guis/Flowee/TextField.qml | 2 +- guis/Flowee/WalletSecretsView.qml | 2 +- guis/Flowee/WarningLabel.qml | 2 +- guis/desktop/AccountConfigMenu.qml | 2 +- guis/desktop/AccountDetails.qml | 2 +- guis/desktop/AccountListItem.qml | 2 +- guis/desktop/AddressDbStats.qml | 2 +- guis/desktop/ConfigItem.qml | 2 +- guis/desktop/NetView.qml | 2 +- guis/desktop/NewAccountImportAccount.qml | 2 +- guis/desktop/NewAccountPane.qml | 2 +- guis/desktop/PaymentTweakingPanel.qml | 2 +- guis/desktop/ReceiveTransactionPane.qml | 2 +- guis/desktop/SendTransactionPane.qml | 2 +- guis/desktop/SettingsPane.qml | 2 +- guis/desktop/TabBarWidget.qml | 2 +- guis/desktop/Transaction.qml | 2 +- guis/desktop/TransactionDetails.qml | 2 +- guis/desktop/TransactionInfoSmall.qml | 2 +- guis/desktop/WalletEncryption.qml | 2 +- guis/desktop/WalletEncryptionStatus.qml | 2 +- guis/desktop/locked.qml | 2 +- guis/desktop/main.qml | 36 +++++++++++------------- guis/mobile/AccountHistory.qml | 2 +- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/AccountSelectorPopup.qml | 2 +- guis/mobile/AccountSelectorWidget.qml | 2 +- guis/mobile/AccountsList.qml | 2 +- guis/mobile/BackgroundSyncConfig.qml | 2 +- guis/mobile/ImportWalletPage.qml | 2 +- guis/mobile/LockApplication.qml | 2 +- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/MenuOverlay.qml | 2 +- guis/mobile/NumericKeyboardWidget.qml | 2 +- guis/mobile/Page.qml | 2 +- guis/mobile/PayWithQR.qml | 2 +- guis/mobile/PopupOverlay.qml | 2 +- guis/mobile/PriceInputWidget.qml | 2 +- guis/mobile/ReceiveTab.qml | 2 +- guis/mobile/ScanQRPage.qml | 2 +- guis/mobile/SendTransactionsTab.qml | 2 +- guis/mobile/Settings.qml | 2 +- guis/mobile/TransactionDetails.qml | 2 +- guis/mobile/TransactionInfoSmall.qml | 2 +- guis/mobile/UnlockApplication.qml | 2 +- guis/mobile/main.qml | 4 +-- 64 files changed, 80 insertions(+), 84 deletions(-) diff --git a/guis/Flowee/AddressLabel.qml b/guis/Flowee/AddressLabel.qml index 35e184f..b385eef 100644 --- a/guis/Flowee/AddressLabel.qml +++ b/guis/Flowee/AddressLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.Control { id: root diff --git a/guis/Flowee/BigButton.qml b/guis/Flowee/BigButton.qml index d53d1f2..ef573d3 100644 --- a/guis/Flowee/BigButton.qml +++ b/guis/Flowee/BigButton.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.Control { id: control diff --git a/guis/Flowee/BitcoinAmountLabel.qml b/guis/Flowee/BitcoinAmountLabel.qml index e74aaa1..576b260 100644 --- a/guis/Flowee/BitcoinAmountLabel.qml +++ b/guis/Flowee/BitcoinAmountLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts /** diff --git a/guis/Flowee/BroadcastFeedback.qml b/guis/Flowee/BroadcastFeedback.qml index 432edfe..26d3d63 100644 --- a/guis/Flowee/BroadcastFeedback.qml +++ b/guis/Flowee/BroadcastFeedback.qml @@ -17,7 +17,7 @@ */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import Flowee.org.pay import "../ControlColors.js" as ControlColors diff --git a/guis/Flowee/Button.qml b/guis/Flowee/Button.qml index 6156e3e..b5752f9 100644 --- a/guis/Flowee/Button.qml +++ b/guis/Flowee/Button.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../ControlColors.js" as ControlColors // This is silly to be needed, but we want to introduce a visual difference diff --git a/guis/Flowee/CFIcon.qml b/guis/Flowee/CFIcon.qml index db0db41..f2cf129 100644 --- a/guis/Flowee/CFIcon.qml +++ b/guis/Flowee/CFIcon.qml @@ -1,5 +1,5 @@ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 Image { id: fusedIcon diff --git a/guis/Flowee/CheckBox.qml b/guis/Flowee/CheckBox.qml index 35e8f4e..032097d 100644 --- a/guis/Flowee/CheckBox.qml +++ b/guis/Flowee/CheckBox.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Templates as T T.CheckBox { diff --git a/guis/Flowee/CheckBoxLabel.qml b/guis/Flowee/CheckBoxLabel.qml index 4e5e3eb..ef488bd 100644 --- a/guis/Flowee/CheckBoxLabel.qml +++ b/guis/Flowee/CheckBoxLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 /** * This is a buddy that goes with a CheckBox component for when diff --git a/guis/Flowee/ComboBox.qml b/guis/Flowee/ComboBox.qml index 8945343..5a75c1e 100644 --- a/guis/Flowee/ComboBox.qml +++ b/guis/Flowee/ComboBox.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.ComboBox { id: root diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index cce774e..de8257c 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts QQC2.Popup { diff --git a/guis/Flowee/DialogButtonBox.qml b/guis/Flowee/DialogButtonBox.qml index 95c84a4..b7b5969 100644 --- a/guis/Flowee/DialogButtonBox.qml +++ b/guis/Flowee/DialogButtonBox.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 /* * This is a buttonbox that swaps button order based on platform defaults. diff --git a/guis/Flowee/GroupBox.qml b/guis/Flowee/GroupBox.qml index 06e9f30..182f821 100644 --- a/guis/Flowee/GroupBox.qml +++ b/guis/Flowee/GroupBox.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts QQC2.Control { diff --git a/guis/Flowee/ImageButton.qml b/guis/Flowee/ImageButton.qml index 86f6306..834999b 100644 --- a/guis/Flowee/ImageButton.qml +++ b/guis/Flowee/ImageButton.qml @@ -17,7 +17,7 @@ */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.Control { implicitWidth: Math.max(iconSize + 8, label.contentWidth) diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml index a175026..f22f1d8 100644 --- a/guis/Flowee/Label.qml +++ b/guis/Flowee/Label.qml @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.Label { // With Qt6.4 on Android, this method is needed to diff --git a/guis/Flowee/LabelWithClipboard.qml b/guis/Flowee/LabelWithClipboard.qml index e69062f..f2ef4ec 100644 --- a/guis/Flowee/LabelWithClipboard.qml +++ b/guis/Flowee/LabelWithClipboard.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 Label { id: root diff --git a/guis/Flowee/MultilineTextField.qml b/guis/Flowee/MultilineTextField.qml index b3254b1..37b2f6a 100644 --- a/guis/Flowee/MultilineTextField.qml +++ b/guis/Flowee/MultilineTextField.qml @@ -15,7 +15,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts /* diff --git a/guis/Flowee/RadioButton.qml b/guis/Flowee/RadioButton.qml index 9645cee..4c432ae 100644 --- a/guis/Flowee/RadioButton.qml +++ b/guis/Flowee/RadioButton.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Templates as T T.RadioButton { diff --git a/guis/Flowee/ScrollThumb.qml b/guis/Flowee/ScrollThumb.qml index c072b46..0502cf1 100644 --- a/guis/Flowee/ScrollThumb.qml +++ b/guis/Flowee/ScrollThumb.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 QQC2.ScrollBar { id: root diff --git a/guis/Flowee/TextField.qml b/guis/Flowee/TextField.qml index d68747e..07e9c3b 100644 --- a/guis/Flowee/TextField.qml +++ b/guis/Flowee/TextField.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import Flowee.org.pay; /* diff --git a/guis/Flowee/WalletSecretsView.qml b/guis/Flowee/WalletSecretsView.qml index e55b7bb..c3cbe72 100644 --- a/guis/Flowee/WalletSecretsView.qml +++ b/guis/Flowee/WalletSecretsView.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 ListView { id: root diff --git a/guis/Flowee/WarningLabel.qml b/guis/Flowee/WarningLabel.qml index a2066c5..a91d5ca 100644 --- a/guis/Flowee/WarningLabel.qml +++ b/guis/Flowee/WarningLabel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 Item { id: warningLabel diff --git a/guis/desktop/AccountConfigMenu.qml b/guis/desktop/AccountConfigMenu.qml index b242722..0dd572a 100644 --- a/guis/desktop/AccountConfigMenu.qml +++ b/guis/desktop/AccountConfigMenu.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 ConfigItem { id: root diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index 04d3291..eb0bcf5 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/desktop/AccountListItem.qml b/guis/desktop/AccountListItem.qml index 2025b88..13dbcc1 100644 --- a/guis/desktop/AccountListItem.qml +++ b/guis/desktop/AccountListItem.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/desktop/AddressDbStats.qml b/guis/desktop/AddressDbStats.qml index df28848..811ef61 100644 --- a/guis/desktop/AddressDbStats.qml +++ b/guis/desktop/AddressDbStats.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee QQC2.ApplicationWindow { diff --git a/guis/desktop/ConfigItem.qml b/guis/desktop/ConfigItem.qml index b7b12b8..f6392ab 100644 --- a/guis/desktop/ConfigItem.qml +++ b/guis/desktop/ConfigItem.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee Item { diff --git a/guis/desktop/NetView.qml b/guis/desktop/NetView.qml index 6179574..8e34119 100644 --- a/guis/desktop/NetView.qml +++ b/guis/desktop/NetView.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/desktop/NewAccountImportAccount.qml b/guis/desktop/NewAccountImportAccount.qml index c7250e0..0337638 100644 --- a/guis/desktop/NewAccountImportAccount.qml +++ b/guis/desktop/NewAccountImportAccount.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay diff --git a/guis/desktop/NewAccountPane.qml b/guis/desktop/NewAccountPane.qml index d30f482..16d3d98 100644 --- a/guis/desktop/NewAccountPane.qml +++ b/guis/desktop/NewAccountPane.qml @@ -15,7 +15,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/desktop/PaymentTweakingPanel.qml b/guis/desktop/PaymentTweakingPanel.qml index 69128be..8147d11 100644 --- a/guis/desktop/PaymentTweakingPanel.qml +++ b/guis/desktop/PaymentTweakingPanel.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/desktop/ReceiveTransactionPane.qml b/guis/desktop/ReceiveTransactionPane.qml index 3e23a09..d74d51f 100644 --- a/guis/desktop/ReceiveTransactionPane.qml +++ b/guis/desktop/ReceiveTransactionPane.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import QtQuick.Shapes import Flowee.org.pay diff --git a/guis/desktop/SendTransactionPane.qml b/guis/desktop/SendTransactionPane.qml index 6cd081e..93aee6a 100644 --- a/guis/desktop/SendTransactionPane.qml +++ b/guis/desktop/SendTransactionPane.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import QtQuick.Window import "../Flowee" as Flowee diff --git a/guis/desktop/SettingsPane.qml b/guis/desktop/SettingsPane.qml index 727c0ad..58acb1e 100644 --- a/guis/desktop/SettingsPane.qml +++ b/guis/desktop/SettingsPane.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors diff --git a/guis/desktop/TabBarWidget.qml b/guis/desktop/TabBarWidget.qml index 8f9ec12..5fb3308 100644 --- a/guis/desktop/TabBarWidget.qml +++ b/guis/desktop/TabBarWidget.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee FocusScope { diff --git a/guis/desktop/Transaction.qml b/guis/desktop/Transaction.qml index 7b612b0..f726d71 100644 --- a/guis/desktop/Transaction.qml +++ b/guis/desktop/Transaction.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import "../Utils.js" as Utils diff --git a/guis/desktop/TransactionDetails.qml b/guis/desktop/TransactionDetails.qml index 8589fcf..0ac89ef 100644 --- a/guis/desktop/TransactionDetails.qml +++ b/guis/desktop/TransactionDetails.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay diff --git a/guis/desktop/TransactionInfoSmall.qml b/guis/desktop/TransactionInfoSmall.qml index 6e5b2b0..90cff16 100644 --- a/guis/desktop/TransactionInfoSmall.qml +++ b/guis/desktop/TransactionInfoSmall.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import "../Utils.js" as Utils diff --git a/guis/desktop/WalletEncryption.qml b/guis/desktop/WalletEncryption.qml index 0ff6d9f..fa7ccdb 100644 --- a/guis/desktop/WalletEncryption.qml +++ b/guis/desktop/WalletEncryption.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors diff --git a/guis/desktop/WalletEncryptionStatus.qml b/guis/desktop/WalletEncryptionStatus.qml index 60b1158..8a4bfea 100644 --- a/guis/desktop/WalletEncryptionStatus.qml +++ b/guis/desktop/WalletEncryptionStatus.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee Item { diff --git a/guis/desktop/locked.qml b/guis/desktop/locked.qml index 7d4d2dd..9b9d706 100644 --- a/guis/desktop/locked.qml +++ b/guis/desktop/locked.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 0b75bbd..7e55113 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -16,14 +16,13 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 +import QtQuick.Templates as T import QtQuick.Layouts import "../Flowee" as Flowee import "../ControlColors.js" as ControlColors -import QtQuick.Controls.Basic - -ApplicationWindow { +QQC2.ApplicationWindow { id: mainWindow visible: true width: Pay.windowWidth @@ -207,7 +206,7 @@ ApplicationWindow { id: tabbar anchors.fill: parent - Pane { + QQC2.Pane { id: activityTab property string title: qsTr("Activity") property string icon: "qrc:/activityIcon-light.png" @@ -351,7 +350,7 @@ ApplicationWindow { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - ScrollBar.vertical: Flowee.ScrollThumb { + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 20 / activityView.height visible: size < 0.9 @@ -360,7 +359,7 @@ ApplicationWindow { height: label.height + 12 radius: 5 color: palette.light - Label { + Flowee.Label { id: label anchors.centerIn: parent color: palette.dark @@ -417,7 +416,6 @@ ApplicationWindow { id: popupTabsOverlay property alias isOpen: thePopup.visible anchors.fill: parent - Rectangle { color: "black" opacity: 0.3 @@ -429,7 +427,7 @@ ApplicationWindow { loader.sourceComponent = sourceComponent; } - QQC2.Popup { + Flowee.Popup { id: thePopup property point anchorTopRight: Qt.point(0, 0); @@ -522,7 +520,7 @@ ApplicationWindow { Item { height: balanceLabel.height width: parent.width - Label { + Flowee.Label { id: balanceLabel text: qsTr("Balance"); height: implicitHeight / 10 * 7 @@ -592,7 +590,7 @@ ApplicationWindow { clip: true property QtObject account: mainWindow.isLoading ? null : portfolio.current - Label { + Flowee.Label { text: qsTr("Main", "balance (money), non specified") + ":" Layout.alignment: Qt.AlignRight } @@ -602,7 +600,7 @@ ApplicationWindow { showFiat: false Layout.fillWidth: true } - Label { + Flowee.Label { text: qsTr("Unconfirmed", "balance (money)") + ":" Layout.alignment: Qt.AlignRight } @@ -611,7 +609,7 @@ ApplicationWindow { colorize: false showFiat: false } - Label { + Flowee.Label { text: qsTr("Immature", "balance (money)") + ":" Layout.alignment: Qt.AlignRight } @@ -642,7 +640,7 @@ ApplicationWindow { Behavior on height { NumberAnimation {} } } - Label { + Flowee.Label { text: { if (mainWindow.isLoading || !Pay.isMainChain) return ""; @@ -658,7 +656,7 @@ ApplicationWindow { Item { width: parent.width height: fiatValue.height - Label { + Flowee.Label { id: fiatValue property double prevPrice: 0 text: { @@ -712,13 +710,13 @@ ApplicationWindow { id: leftColumn width: balances.width - Label { + Flowee.Label { id: netStatus text: qsTr("Network status") opacity: 0.6 visible: isLoading || !portfolio.current.uptodate } - Label { + Flowee.Label { visible: netStatus.visible id: syncIndicator text: { @@ -775,7 +773,7 @@ ApplicationWindow { transformOrigin: Item.Center Behavior on rotation { NumberAnimation {} } } - Label { + Flowee.Label { id: archivedLabel x: showArchivedWalletsList.width + 6 text: { @@ -812,7 +810,7 @@ ApplicationWindow { id: splashScreen color: palette.window anchors.fill: parent - Label { + Flowee.Label { text: qsTr("Preparing...") anchors.centerIn: parent font.pointSize: 20 diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 3721b08..d7198b1 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Shapes import "../Flowee" as Flowee import "../Utils.js" as Utils diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index a3ee7b4..ebded10 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 4209af1..002263d 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/mobile/AccountSelectorWidget.qml b/guis/mobile/AccountSelectorWidget.qml index 33960e9..5d4a920 100644 --- a/guis/mobile/AccountSelectorWidget.qml +++ b/guis/mobile/AccountSelectorWidget.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee /** diff --git a/guis/mobile/AccountsList.qml b/guis/mobile/AccountsList.qml index 2c61167..d3d16ea 100644 --- a/guis/mobile/AccountsList.qml +++ b/guis/mobile/AccountsList.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee Page { diff --git a/guis/mobile/BackgroundSyncConfig.qml b/guis/mobile/BackgroundSyncConfig.qml index 41f2ca5..d3d6a96 100644 --- a/guis/mobile/BackgroundSyncConfig.qml +++ b/guis/mobile/BackgroundSyncConfig.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 3fcbe2e..7b39d78 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay diff --git a/guis/mobile/LockApplication.qml b/guis/mobile/LockApplication.qml index 5c4ee52..e6b96a3 100644 --- a/guis/mobile/LockApplication.qml +++ b/guis/mobile/LockApplication.qml @@ -17,7 +17,7 @@ */ import QtQuick import "../Flowee" as Flowee -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import Flowee.org.pay Page { diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 8aa0a7e..4aa8342 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import QtQuick.Effects import "../Flowee" as Flowee diff --git a/guis/mobile/MenuOverlay.qml b/guis/mobile/MenuOverlay.qml index 4f19fba..778087b 100644 --- a/guis/mobile/MenuOverlay.qml +++ b/guis/mobile/MenuOverlay.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/NumericKeyboardWidget.qml b/guis/mobile/NumericKeyboardWidget.qml index 45895ac..3ca0228 100644 --- a/guis/mobile/NumericKeyboardWidget.qml +++ b/guis/mobile/NumericKeyboardWidget.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 Item { id: root diff --git a/guis/mobile/Page.qml b/guis/mobile/Page.qml index 5e74101..ab44f20 100644 --- a/guis/mobile/Page.qml +++ b/guis/mobile/Page.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index e838014..0f36099 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 8840c52..595f59a 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee FocusScope { diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index c273256..faa9438 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay diff --git a/guis/mobile/ReceiveTab.qml b/guis/mobile/ReceiveTab.qml index 4710fca..2fad10f 100644 --- a/guis/mobile/ReceiveTab.qml +++ b/guis/mobile/ReceiveTab.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Particles import "../Flowee" as Flowee import Flowee.org.pay diff --git a/guis/mobile/ScanQRPage.qml b/guis/mobile/ScanQRPage.qml index 705ce23..c37c41b 100644 --- a/guis/mobile/ScanQRPage.qml +++ b/guis/mobile/ScanQRPage.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; import "WorkflowStarter.js" as WorkflowStarter diff --git a/guis/mobile/SendTransactionsTab.qml b/guis/mobile/SendTransactionsTab.qml index 478ac19..cca628d 100644 --- a/guis/mobile/SendTransactionsTab.qml +++ b/guis/mobile/SendTransactionsTab.qml @@ -17,7 +17,7 @@ */ import QtQuick import QtQuick.Layouts -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; import "WorkflowStarter.js" as WorkflowStarter diff --git a/guis/mobile/Settings.qml b/guis/mobile/Settings.qml index 1fc5fd8..4cf48c6 100644 --- a/guis/mobile/Settings.qml +++ b/guis/mobile/Settings.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee diff --git a/guis/mobile/TransactionDetails.qml b/guis/mobile/TransactionDetails.qml index d64601f..89c58c0 100644 --- a/guis/mobile/TransactionDetails.qml +++ b/guis/mobile/TransactionDetails.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/TransactionInfoSmall.qml b/guis/mobile/TransactionInfoSmall.qml index 6298cab..64c38c1 100644 --- a/guis/mobile/TransactionInfoSmall.qml +++ b/guis/mobile/TransactionInfoSmall.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/UnlockApplication.qml b/guis/mobile/UnlockApplication.qml index 9112fb3..982edda 100644 --- a/guis/mobile/UnlockApplication.qml +++ b/guis/mobile/UnlockApplication.qml @@ -16,7 +16,7 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import "../Flowee" as Flowee import Flowee.org.pay; diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index b280e81..f7893d3 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -16,14 +16,12 @@ * along with this program. If not, see . */ import QtQuick -import QtQuick.Controls as QQC2 +import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../ControlColors.js" as ControlColors import "../Flowee" as Flowee import Flowee.org.pay; -import QtQuick.Controls.Basic - QQC2.ApplicationWindow { id: mainWindow -- 2.54.0 From 0ef4e8ed4f084b8c149b927a6747ccd1ff04d7ed Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 19 Jun 2025 14:47:19 +0200 Subject: [PATCH 1379/1428] Use our own Popup style The QML design of styling is that one extends the "Template" version of a component. With the Basic, the Fusion etc already provided. But after upgrading to Qt6.9 that styling for Popup changed and broke the look of Flowee Pay. So, we no longer use the Basic styled class, but the bare one which should never change. This additionally simplifies a lot of the workarounds surrounding insets and other stuff that existed in the Basic.Popup, and we just don't set in our own "style" of Popup. Beautiful result, lots of complexity removed from AccountDetails, TransactionListItem and very much from PopupOverlay --- guis/Flowee/Label.qml | 4 +-- guis/Flowee/Popup.qml | 48 ++++++++++++++++++++++++++++ guis/desktop/AccountDetails.qml | 2 +- guis/mobile/AccountHistory.qml | 24 ++++---------- guis/mobile/AccountPageListItem.qml | 2 +- guis/mobile/AccountSelectorPopup.qml | 5 +-- guis/mobile/ImportWalletPage.qml | 2 +- guis/mobile/PopupOverlay.qml | 29 +++++++---------- guis/mobile/TransactionListItem.qml | 14 ++++---- guis/mobile/main.qml | 2 +- guis/widgets.qrc | 1 + 11 files changed, 83 insertions(+), 50 deletions(-) create mode 100644 guis/Flowee/Popup.qml diff --git a/guis/Flowee/Label.qml b/guis/Flowee/Label.qml index f22f1d8..c78c22a 100644 --- a/guis/Flowee/Label.qml +++ b/guis/Flowee/Label.qml @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick.Controls.Basic as QQC2 +import QtQuick.Templates as T -QQC2.Label { +T.Label { // With Qt6.4 on Android, this method is needed to // get the label to follow the app-color-style color: { diff --git a/guis/Flowee/Popup.qml b/guis/Flowee/Popup.qml new file mode 100644 index 0000000..b24a5eb --- /dev/null +++ b/guis/Flowee/Popup.qml @@ -0,0 +1,48 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.impl // for Color +import QtQuick.Templates as T + +T.Popup { + id: control + + topInset: 0 + bottomInset: 0 + leftInset: 0 + rightInset: 0 + margins: 0 + + implicitWidth: Math.max(implicitBackgroundWidth + rightInset + leftInset, + implicitContentWidth + rightPadding + leftPadding) + implicitHeight: Math.max(implicitBackgroundHeight + bottomInset + topInset, + implicitContentHeight + bottomPadding + topPadding) + + background: Rectangle { + color: "blue" // control.palette.window + border.color: control.palette.dark + } + + T.Overlay.modal: Rectangle { + color: Color.transparent(control.palette.light, 0.5) + } + + T.Overlay.modeless: Rectangle { + color: Color.transparent(control.palette.light, 0.12) + } +} diff --git a/guis/desktop/AccountDetails.qml b/guis/desktop/AccountDetails.qml index eb0bcf5..e15d2e3 100644 --- a/guis/desktop/AccountDetails.qml +++ b/guis/desktop/AccountDetails.qml @@ -47,7 +47,7 @@ Item { Item { // non-layoutable items. - QQC2.Popup { + Flowee.Popup { id: qrPopup width: 270 height: 270 diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index d7198b1..3b6c6c2 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -234,7 +234,7 @@ ListView { return -20; } - radius: 20 + radius: 10 color: palette.light border.width: 1 border.color: palette.midlight @@ -296,6 +296,8 @@ ListView { TransactionListItem { id: txListItem anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 } // horizontal separator @@ -332,23 +334,11 @@ ListView { } Component { id: overlayTxListItem - Item { - height: 80 - width: 10 - Rectangle { - color: palette.base - width: parent.width - 18 - height: parent.height - x: 9 - } - TransactionListItem { - width: parent.width - implicitHeight: 60 - commentEditable: true - y: 10 - } + TransactionListItem { + width: parent.width + implicitHeight: 80 + commentEditable: true } - } } displaced: Transition { NumberAnimation { properties: "y"; duration: 400 } } diff --git a/guis/mobile/AccountPageListItem.qml b/guis/mobile/AccountPageListItem.qml index ebded10..9ac4e5a 100644 --- a/guis/mobile/AccountPageListItem.qml +++ b/guis/mobile/AccountPageListItem.qml @@ -173,7 +173,7 @@ QQC2.Control { Item { // non-layoutable items. - QQC2.Popup { + Flowee.Popup { id: qrPopup width: 270 height: 270 diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 002263d..3b37a98 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -21,7 +21,7 @@ import QtQuick.Layouts import "../Flowee" as Flowee // see also AccountSelectorWidget.qml -QQC2.Popup { +Flowee.Popup { id: root closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent height: columnLayout.height + 16 @@ -51,6 +51,7 @@ QQC2.Popup { text: qsTr("Your Wallets") font.bold: true visible: portfolio.accounts.length > 1 + x: 6 } Repeater { // portfolio holds all our accounts @@ -89,7 +90,7 @@ QQC2.Popup { } Rectangle { height: parent.height - width: 3 + width: 5 color: palette.highlight visible: selectedItemIndicator.visible } diff --git a/guis/mobile/ImportWalletPage.qml b/guis/mobile/ImportWalletPage.qml index 7b39d78..d116cc5 100644 --- a/guis/mobile/ImportWalletPage.qml +++ b/guis/mobile/ImportWalletPage.qml @@ -633,7 +633,7 @@ Page { * download the missing headers. Use the block-headers checker (from the module 'blocks') * to verify and initiate this. */ - QQC2.Popup { + Flowee.Popup { id: checkBlockchainPopup property int oldestBlock: 0 diff --git a/guis/mobile/PopupOverlay.qml b/guis/mobile/PopupOverlay.qml index 595f59a..7780201 100644 --- a/guis/mobile/PopupOverlay.qml +++ b/guis/mobile/PopupOverlay.qml @@ -61,14 +61,10 @@ FocusScope { } } - QQC2.Popup { + Flowee.Popup { id: thePopup width: parent.width height: 100 - leftInset: -2 - rightInset: -2 - topInset: 0 - bottomInset: 0 modal: true closePolicy: QQC2.Popup.CloseOnEscape + QQC2.Popup.CloseOnReleaseOutside property rect sourceRect: Qt.rect(0, 0, 0, 0) @@ -82,10 +78,9 @@ FocusScope { background: Item { } Loader { - // use offsets to counter the popup insets - width: parent.width + 42 - x: -21 id: overlayLoader + width: parent.width - 2 + x: 1 } Rectangle { color: palette.base @@ -94,15 +89,13 @@ FocusScope { radius: 5 anchors.fill: loader // the popup imposes a border on us, we take it back - anchors.topMargin: -10 - anchors.bottomMargin: -7 - anchors.leftMargin: -14 - anchors.rightMargin: -14 + anchors.margins: -10 } Loader { id: loader - width: parent.width + width: parent.width - 20 + x: 10 onLoaded: { thePopup.visible = true; root.forceActiveFocus(); @@ -112,7 +105,7 @@ FocusScope { if (item == null) return; var h = loader.item.implicitHeight - thePopup.height = h + 22; // 22 is for top and bottom margins of the popup + thePopup.height = h var rect = thePopup.sourceRect; if (overlayLoader.item) { // the overlay is supposed to be at the same position as the sourceRect var h2 = overlayLoader.height @@ -120,18 +113,18 @@ FocusScope { if (root.height - rect.bottom >= h) { // fits below thePopup.y = rect.bottom - h2; - overlayLoader.y = -12 + overlayLoader.y = 0 loader.y = h2; } else if (h < rect.y) { // fits above - thePopup.y = rect.y - h - 22; - overlayLoader.y = h + 10; + thePopup.y = rect.y - h + overlayLoader.y = h loader.y = 0; } else { thePopup.y = root.height - h; // make it bottom aligned, even if it overlaps overlayLoader.y = 0 // item above - loader.y = h2 - 10; + loader.y = h2; } return; diff --git a/guis/mobile/TransactionListItem.qml b/guis/mobile/TransactionListItem.qml index 4732c9b..5bd952b 100644 --- a/guis/mobile/TransactionListItem.qml +++ b/guis/mobile/TransactionListItem.qml @@ -53,7 +53,7 @@ Item { } width: 45 height: 45 - x: 20 + x: 10 smooth: true anchors.verticalCenter: ruler.verticalCenter opacity: isRejected ? 0.6 : 1 @@ -65,16 +65,16 @@ Item { width: 60 height: 60 rotation: mainIcon.receiving ? 45 : 0 - x: mainIcon.receiving ? 14 : -2 + x: mainIcon.receiving ? 4 : -2 y: -2 } Flowee.Label { id: commentLabel anchors.right: price.visible ? price.left : price.right - anchors.rightMargin: 30 + anchors.rightMargin: 20 anchors.left: parent.left - anchors.leftMargin: 72 + anchors.leftMargin: 62 y: { if (price.visible && Pay.activityShowsBch) // baseline align to price return price.y + price.baselineOffset - baselineOffset @@ -162,8 +162,8 @@ Item { return parent.height / 2 - height return parent.height / 2 - height / 2 // i.e my verticalCenter = parent.verticalCenter } - anchors.rightMargin: 20 - radius: 6 + anchors.rightMargin: 5 + radius: 5 baselineOffset: amount.baselineOffset + 4 // 4 is half the spacing added at height: visible: !model.isFused && Pay.isMainChain @@ -191,7 +191,7 @@ Item { Flowee.BitcoinAmountLabel { visible: !Pay.isMainChain || (price.visible && Pay.activityShowsBch) anchors.right: parent.right - anchors.rightMargin: 25 + anchors.rightMargin: 10 anchors.baseline: dateLine.baseline value: amountBch showFiat: false diff --git a/guis/mobile/main.qml b/guis/mobile/main.qml index f7893d3..33666ff 100644 --- a/guis/mobile/main.qml +++ b/guis/mobile/main.qml @@ -174,7 +174,7 @@ QQC2.ApplicationWindow { } } - QQC2.Popup { + Flowee.Popup { id: notificationPopup y: 110 x: 25 diff --git a/guis/widgets.qrc b/guis/widgets.qrc index 105f4ee..ee54eb3 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -62,5 +62,6 @@ Flowee/FiatTxInfo.qml Flowee/Progressbar.qml Flowee/ProgressCheckIcon.qml + Flowee/Popup.qml -- 2.54.0 From 9bbd841b3a379155f73da0f77b86ae913367004e Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 19 Jun 2025 15:20:22 +0200 Subject: [PATCH 1380/1428] UX fixlets in AccountSelectorPopup Elide wallet names when too long to fit Hide the last active sting if the wallet is empty --- guis/mobile/AccountSelectorPopup.qml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountSelectorPopup.qml b/guis/mobile/AccountSelectorPopup.qml index 3b37a98..8293991 100644 --- a/guis/mobile/AccountSelectorPopup.qml +++ b/guis/mobile/AccountSelectorPopup.qml @@ -112,8 +112,12 @@ Flowee.Popup { Flowee.Label { id: accountName y: 6 - x: lockIcon.visible ? 24: 6 text: modelData.name + anchors.left: parent.left + anchors.leftMargin: lockIcon.visible ? 24: 6 + anchors.right: fiat.left + anchors.rightMargin: 6 + elide: Text.ElideRight } Flowee.Label { id: fiat @@ -128,9 +132,10 @@ Flowee.Popup { anchors.top: accountName.bottom anchors.topMargin: 6 anchors.left: accountName.left - text: qsTr("last active") + ": " + Pay.formatDate(modelData.lastMinedTransaction) + property var lastMined: modelData.lastMinedTransaction; + text: qsTr("last active") + ": " + Pay.formatDate(lastMined) font.pixelSize: root.font.pixelSize * 0.8 - visible: modelData.isDecrypted || !modelData.needsPinToOpen + visible: !lockStatus.visible && !isNaN(lastMined) } Flowee.Label { id: lockStatus @@ -139,7 +144,7 @@ Flowee.Popup { anchors.left: accountName.left text: qsTr("Needs PIN to open") font.pixelSize: root.font.pixelSize * 0.8 - visible: !lastActive.visible + visible: modelData.needsPinToOpen && !modelData.isDecrypted } Flowee.BitcoinAmountLabel { id: bchAmountLabel -- 2.54.0 From 951ae9d3101ce54eca7c25b2141ccf7eda5eba36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 19 Jun 2025 17:06:09 +0200 Subject: [PATCH 1381/1428] Avoid complexity; just use the checkboxes User research showed that the bigger 'checkboxes' were not worth the extra clicks. As such they are removed again. --- guis/desktop/ActivityConfigBar.qml | 39 ++++++++------------------ guis/desktop/main.qml | 44 ------------------------------ 2 files changed, 11 insertions(+), 72 deletions(-) diff --git a/guis/desktop/ActivityConfigBar.qml b/guis/desktop/ActivityConfigBar.qml index 6a70121..36f02f0 100644 --- a/guis/desktop/ActivityConfigBar.qml +++ b/guis/desktop/ActivityConfigBar.qml @@ -34,7 +34,7 @@ Item { width: 40 anchors.bottom: parent.bottom radius: 10 - + Rectangle { width: parent.width height: parent.height @@ -45,7 +45,7 @@ Item { } } } - + property QtObject wallet: portfolio.current; onWalletChanged: wallet.transactions.filterString = filterText.text.trim(); function toggleFlag(flag, on) { @@ -109,11 +109,7 @@ Item { id: sendReceiveFilter anchors.left: cfCheckbox.right anchors.leftMargin: 10 - border.width: 2 color: palette.base - border.color: sendReceiveMouse.containsMouse - ? (Pay.useDarkSkin ? mainWindow.floweeSalmon : mainWindow.floweeBlue) - : palette.mid height: 44 width: grid.width + 12 y: 4 @@ -180,28 +176,15 @@ Item { MouseArea { id: sendReceiveMouse anchors.fill: parent - hoverEnabled: true - onClicked: popupTabsOverlay.open(sendReceivePopup, mapToGlobal(width, height)); - } - } - Component { - id: sendReceivePopup - GridLayout { - rowSpacing: 10 - columns: 2 - Flowee.CheckBox { - checked: sendReceiveFilter.includeReceived - onClicked: toggleFlag(Wallet.IncludeReceivedTransactions, checked); - } - Flowee.Label { - text: qsTr("Received") - } - Flowee.CheckBox { - checked: sendReceiveFilter.includeSent - onClicked: toggleFlag(Wallet.IncludeSentTransactions, checked); - } - Flowee.Label { - text: qsTr("Sent") + anchors.margins: -6 + onClicked: (event) => { + if (event.y < height / 2) { + root.toggleFlag(Wallet.IncludeReceivedTransactions, + !sendReceiveFilter.includeReceived); + } else { + root.toggleFlag(Wallet.IncludeSentTransactions, + !sendReceiveFilter.includeSent); + } } } } diff --git a/guis/desktop/main.qml b/guis/desktop/main.qml index 7e55113..4284b1d 100644 --- a/guis/desktop/main.qml +++ b/guis/desktop/main.qml @@ -412,50 +412,6 @@ QQC2.ApplicationWindow { } Behavior on opacity { NumberAnimation { } } visible: opacity > 0 - Item { - id: popupTabsOverlay - property alias isOpen: thePopup.visible - anchors.fill: parent - Rectangle { - color: "black" - opacity: 0.3 - } - visible: isOpen - - function open(sourceComponent, rightAnchor) { - thePopup.anchorTopRight = popupTabsOverlay.mapFromGlobal(rightAnchor); - loader.sourceComponent = sourceComponent; - } - - Flowee.Popup { - id: thePopup - property point anchorTopRight: Qt.point(0, 0); - - closePolicy: QQC2.Popup.CloseOnEscape + QQC2.Popup.CloseOnReleaseOutside - width: loader.width + 20 - height: loader.height + 20 - modal: true - leftPadding: 10 - topPadding: 10 - visible: false - onVisibleChanged: if (!visible) loader.sourceComponent = undefined; - background: Rectangle { - color: palette.light - border.color: palette.midlight - border.width: 1 - radius: 6 - } - Loader { - id: loader - onLoaded: thePopup.visible = true - onWidthChanged: { - height = loader.item.height - thePopup.x = thePopup.anchorTopRight.x - width - thePopup.y = thePopup.anchorTopRight.y - } - } - } - } } Loader { -- 2.54.0 From 2313b4b37b33206b669cb785815bfa4c0ae76036 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 19 Jun 2025 17:52:15 +0200 Subject: [PATCH 1382/1428] Make the checkbox Shape reusable Replace the font based checkbox in ActivityConfigBar with the vector based one for reproducable layout --- guis/Flowee/CheckShape.qml | 40 ++++++++++++++++++++++++++++++ guis/desktop/ActivityConfigBar.qml | 15 ++--------- guis/mobile/AccountHistory.qml | 18 +++----------- guis/widgets.qrc | 1 + 4 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 guis/Flowee/CheckShape.qml diff --git a/guis/Flowee/CheckShape.qml b/guis/Flowee/CheckShape.qml new file mode 100644 index 0000000..42aa458 --- /dev/null +++ b/guis/Flowee/CheckShape.qml @@ -0,0 +1,40 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Shapes + +Shape { + id: root + width: 12 + height: 12 + property alias color: path.strokeColor + + ShapePath { + id: path + fillColor: "#00000000" + strokeColor: Pay.useDarkSkin ? "#86ffa8" : "green"; + capStyle: ShapePath.FlatCap + joinStyle: ShapePath.RoundJoin + startX: 3 + startY: 6 + strokeWidth: 2.3 + PathLine { x: 5; y: root.height - 1 } + PathLine { x: 12; y: 2 } + } +} + diff --git a/guis/desktop/ActivityConfigBar.qml b/guis/desktop/ActivityConfigBar.qml index 36f02f0..f5493c7 100644 --- a/guis/desktop/ActivityConfigBar.qml +++ b/guis/desktop/ActivityConfigBar.qml @@ -138,13 +138,7 @@ Item { border.color: palette.button width: 14 height: 14 - - Flowee.Label { - text: "✓" - y: -6 - x: 2 - font.pixelSize: 16 - color: Pay.useDarkSkin ? "#86ffa8" : "green"; + Flowee.CheckShape { visible: sendReceiveFilter.includeReceived } } @@ -158,12 +152,7 @@ Item { border.color: palette.button width: 14 height: 14 - Flowee.Label { - text: "✓" - y: -6 - x: 2 - font.pixelSize: 16 - color: Pay.useDarkSkin ? "#86ffa8" : "green"; + Flowee.CheckShape { visible: sendReceiveFilter.includeSent } } diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 3b6c6c2..65ddfd2 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -17,7 +17,6 @@ */ import QtQuick import QtQuick.Controls.Basic as QQC2 -import QtQuick.Shapes import "../Flowee" as Flowee import "../Utils.js" as Utils import Flowee.org.pay; @@ -273,20 +272,9 @@ ListView { var c = checks.confirmations; return c < 5 ? c : 0; } - - Shape { - width: 12 - height: 12 - ShapePath { - fillColor: "#00000000" - strokeColor: Pay.useDarkSkin ? "#57a56c" : "green"; - capStyle: ShapePath.FlatCap - joinStyle: ShapePath.RoundJoin - startX: 3; startY: 6 - strokeWidth: 2 - PathLine { x: 5; y: 9 } - PathLine { x: 12; y: 2 } - } + Flowee.CheckShape { + color: "#57a56c" + height: 10 } } } diff --git a/guis/widgets.qrc b/guis/widgets.qrc index ee54eb3..a0de38b 100644 --- a/guis/widgets.qrc +++ b/guis/widgets.qrc @@ -63,5 +63,6 @@ Flowee/Progressbar.qml Flowee/ProgressCheckIcon.qml Flowee/Popup.qml + Flowee/CheckShape.qml -- 2.54.0 From bcb3967d5bddbcdfa8501bc1e8e792b249f0031a Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 19 Jun 2025 20:25:23 +0200 Subject: [PATCH 1383/1428] Clarify variable name This is not a txid, as that would be a hash, this is the index of the transaction as referred to by the wallet. --- src/Wallet.h | 13 +++++++------ src/Wallet_support.cpp | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Wallet.h b/src/Wallet.h index dec713c..9dd48b0 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -75,27 +75,27 @@ public: uint64_t encoded() const; inline int txIndex() const { - return m_txid; + return m_txIndex; } inline int outputIndex() const { return m_outputIndex; } inline void setTxIndex(int index) { - m_txid = index; + m_txIndex = index; } inline void setOutputIndex(int index) { m_outputIndex = index; } inline bool isValid() const { - return m_txid != 0; + return m_txIndex != 0; } Wallet::OutputRef& operator=(const OutputRef&) = default; private: - uint32_t m_txid = 0; // index in m_walletTransactions + uint32_t m_txIndex = 0; // index in m_walletTransactions uint16_t m_outputIndex = 0; }; @@ -143,9 +143,10 @@ public: /// returns true if the transaction is found and is a cash fusion transaction. /// throws if txIndex is not known. bool isCFTransaction(int txIndex) const; - /// Fetch UTXO output + /// Fetch UTXO output. Note, expensive! Loads Tx from disk. Tx::Output txOutput(OutputRef ref) const; - /// Fetch UTXO value (in sats) + /// Fetch UTXO value (in sats). + /// Throws if the ref is not unspent. qint64 utxoOutputValue(OutputRef ref) const; /// return the time first seen for a transaction. diff --git a/src/Wallet_support.cpp b/src/Wallet_support.cpp index 392d841..e87d06b 100644 --- a/src/Wallet_support.cpp +++ b/src/Wallet_support.cpp @@ -103,12 +103,12 @@ Wallet::OutputRef::OutputRef(uint64_t encoded) m_outputIndex = encoded & 0xFFFF; encoded >>= 16; assert(encoded < 0xFFFFFFFF); - m_txid = encoded & 0x7FFFFFFF; - assert(static_cast(m_txid) >= 0); + m_txIndex = encoded & 0x7FFFFFFF; + assert(static_cast(m_txIndex) >= 0); } Wallet::OutputRef::OutputRef(int txIndex, int outputIndex) - : m_txid(txIndex), + : m_txIndex(txIndex), m_outputIndex(outputIndex) { assert(txIndex >= 0); // zero is the 'invalid' state, which is allowed here to make an invalid OutputRef @@ -118,7 +118,7 @@ Wallet::OutputRef::OutputRef(int txIndex, int outputIndex) uint64_t Wallet::OutputRef::encoded() const { - uint64_t answer = m_txid; + uint64_t answer = m_txIndex; answer <<= 16; answer += m_outputIndex; return answer; -- 2.54.0 From dfc055b04bdbcab6f89529360e8271272f8ef122 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 20 Jun 2025 18:31:18 +0200 Subject: [PATCH 1384/1428] Add saving repeat payment details unit test and code. --- src/Payment.cpp | 36 +++++++++++++++---- src/RepeatPaymentDetails.cpp | 26 +++++++------- src/RepeatPaymentDetails.h | 30 +++++----------- src/WalletEnums.h | 14 ++++++++ testing/payment/TestPayment.cpp | 61 +++++++++++++++++++++++++++++++++ testing/payment/TestPayment.h | 1 + 6 files changed, 127 insertions(+), 41 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index eee7c6c..6109c4f 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -137,15 +137,36 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) // Repeat case RepeatType: curOut = nullptr; - // TODO + assert(m_repeatDetails == nullptr); + if (parser.intData() == WalletEnums::RepeatByDayOfMonth + || parser.intData() == WalletEnums::RepeatByDayOfWeek) { + m_repeatDetails = new RepeatPaymentDetails(this); + m_repeatDetails->setRepeatType(static_cast( + parser.intData())); + } break; case RepeatDaysOfWeek: + assert(m_repeatDetails); + m_repeatDetails->setDaysOfWeek(parser.longData()); + break; case RepeatWeeksOfMonth: + assert(m_repeatDetails); + m_repeatDetails->setWeeksOfMonth(parser.longData()); + break; case RepeatMonthsOfYear: + assert(m_repeatDetails); + m_repeatDetails->setMonthsOfYear(parser.longData()); + break; case RepeatDayOfMonth: // can occur multiple times + assert(m_repeatDetails); + m_repeatDetails->addDayOfMonth(parser.intData()); + break; case RepeatSunsetDate: + assert(m_repeatDetails); + m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData())); + break; case RepeatPaymentTxIndex: - assert(false); + assert(false); // TODO break; } } @@ -646,16 +667,17 @@ Streaming::ConstBuffer Payment::save() const if (m_repeatDetails && m_repeatDetails->repeatType() != WalletEnums::NoRepeat) { builder.add(RepeatType, static_cast(m_repeatDetails->repeatType())); - builder.add(RepeatMonthsOfYear, m_repeatDetails->monthOfYear()); switch (m_repeatDetails->repeatType()) { case WalletEnums::NoRepeat: assert(false); break; - case WalletEnums::RepeatByDayOfWeek: - builder.add(RepeatDaysOfWeek, m_repeatDetails->repeatDayOfWeek()); - builder.add(RepeatWeeksOfMonth, m_repeatDetails->repeatWeekOfMonth()); - break; case WalletEnums::RepeatByDayOfMonth: + builder.add(RepeatDaysOfWeek, m_repeatDetails->daysOfWeek()); + builder.add(RepeatWeeksOfMonth, m_repeatDetails->weeksOfMonth()); + builder.add(RepeatMonthsOfYear, m_repeatDetails->monthsOfYear()); + break; + case WalletEnums::RepeatByDayOfWeek: + builder.add(RepeatMonthsOfYear, m_repeatDetails->monthsOfYear()); for (const auto day : m_repeatDetails->dayOfMonth()) { builder.add(RepeatDayOfMonth, day); } diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index eb15c4d..c4a2c7e 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -38,34 +38,34 @@ void RepeatPaymentDetails::setRepeatType(WalletEnums::PaymentRepeatType newRepea emit repeatTypeChanged(); } -uint8_t RepeatPaymentDetails::repeatDayOfWeek() const +uint8_t RepeatPaymentDetails::daysOfWeek() const { - return m_repeatDayOfWeek; + return m_dayOfWeek; } -void RepeatPaymentDetails::setRepeatDayOfWeek(uint8_t newRepeatDayOfWeek) +void RepeatPaymentDetails::setDaysOfWeek(uint8_t dow) { - m_repeatDayOfWeek = newRepeatDayOfWeek; + m_dayOfWeek = dow; } -uint8_t RepeatPaymentDetails::repeatWeekOfMonth() const +uint8_t RepeatPaymentDetails::weeksOfMonth() const { - return m_repeatWeekOfMonth; + return m_weekOfMonth; } -void RepeatPaymentDetails::setRepeatWeekOfMonth(uint8_t newRepeatWeekOfMonth) +void RepeatPaymentDetails::setWeeksOfMonth(uint8_t wom) { - m_repeatWeekOfMonth = newRepeatWeekOfMonth; + m_weekOfMonth = wom; } -uint16_t RepeatPaymentDetails::monthOfYear() const +uint16_t RepeatPaymentDetails::monthsOfYear() const { return m_monthOfYear; } -void RepeatPaymentDetails::setMonthOfYear(uint16_t newMonthOfYear) +void RepeatPaymentDetails::setMonthsOfYear(uint16_t moy) { - m_monthOfYear = newMonthOfYear; + m_monthOfYear = moy; } QVector RepeatPaymentDetails::dayOfMonth() const @@ -73,9 +73,9 @@ QVector RepeatPaymentDetails::dayOfMonth() const return m_dayOfMonth; } -void RepeatPaymentDetails::setDayOfMonth(const QVector &newDayOfMonth) +void RepeatPaymentDetails::setDayOfMonth(const QVector &dom) { - m_dayOfMonth = newDayOfMonth; + m_dayOfMonth = dom; } void RepeatPaymentDetails::addDayOfMonth(int day) diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index 5a7e1f3..5233713 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -38,14 +38,14 @@ public: - uint8_t repeatDayOfWeek() const; - void setRepeatDayOfWeek(uint8_t newRepeatDayOfWeek); + uint8_t daysOfWeek() const; + void setDaysOfWeek(uint8_t dow); - uint8_t repeatWeekOfMonth() const; - void setRepeatWeekOfMonth(uint8_t newRepeatWeekOfMonth); + uint8_t weeksOfMonth() const; + void setWeeksOfMonth(uint8_t wom); - uint16_t monthOfYear() const; - void setMonthOfYear(uint16_t newMonthOfYear); + uint16_t monthsOfYear() const; + void setMonthsOfYear(uint16_t moy); QVector dayOfMonth() const; void setDayOfMonth(const QVector &newDayOfMonth); @@ -65,25 +65,13 @@ signals: private: const Payment *m_parent; QTime m_time; // time of day for scheduled item(s) - /* - * Repeat style should have options like: - * which day of week. - * which week of month - * which week of year - * - * so every day can be "every day of the week, ever week of the month". - * - * Alternatively; - * every 15th of the month. - * which month(s) of the year. - */ WalletEnums::PaymentRepeatType m_repeatType = WalletEnums::NoRepeat; // type = RepeatByDayOfWeek repeat every (nth) tuesday - uint8_t m_repeatDayOfWeek = 0x7f; // bitfield on which day of the week we repeat - uint8_t m_repeatWeekOfMonth = 15; // bitfield on which week of the month we repeat + uint8_t m_dayOfWeek = 0x7f; // bitfield on which day of the week we repeat + uint8_t m_weekOfMonth = 0x3f; // bitfield on which week of the month we repeat // type = RepeatByDayOfMonth Repeat every 15th of (every other) month - uint16_t m_monthOfYear = 0x0fff; // bitfield + uint16_t m_monthOfYear = 0x7ff; // bitfield QVector m_dayOfMonth; // list of days in the month we repeat // after this date+time we will not send any more payments. diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 8f686b8..6af9712 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -88,11 +88,25 @@ public: }; Q_ENUM(PeerValidity) + /* + * Repeat style for RepeatByDayOfWeek has features like + * which day of week.(or all) + * which week of month. (or all) + * which week of year. (or all) + * + * so a payment can repeat "every day of the week, every week of the month". + * Or only every 3th thursday of October every year. + * + * Alternatively, we use RepeatByDayOfMonth; + * every 15th of the month. + * which month(s) of the year. + */ enum PaymentRepeatType { NoRepeat, RepeatByDayOfWeek, // repeat every (nth) tuesday RepeatByDayOfMonth // Repeat every 15th of (every other) month }; + Q_ENUM(PaymentRepeatType) }; #endif diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index 76119d7..77c4642 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -136,6 +137,66 @@ void TestPayment::saveOutput() void TestPayment::saveRepeat() { + Streaming::ConstBuffer saveFile; + { + Payment payment; + payment.setFiatPrice(40000); // price per coin. + QCOMPARE(payment.repeatDetails(), nullptr); + payment.makeRepeating(); + auto *r = payment.repeatDetails(); + QVERIFY(r != nullptr); + // first a dummy type. + r->setRepeatType(WalletEnums::NoRepeat); + QCOMPARE(r->repeatType(), WalletEnums::NoRepeat); + saveFile = payment.save(); + } + auto dummy = createWallet(); + { + Payment payment; + QCOMPARE(payment.repeatDetails(), nullptr); + payment.makeRepeating(); + auto *r = payment.repeatDetails(); + QVERIFY(r != nullptr); + + // the type where we can do things like "3rd Tuesday in October" + r->setRepeatType(WalletEnums::RepeatByDayOfMonth); + QVERIFY((r->daysOfWeek() & 0x7f) == 0x7f); // by default all days of the week. + QVERIFY((r->weeksOfMonth() & 0x3f) == 0x3f); // by default 5 weeks in a month + QVERIFY((r->monthsOfYear() & 0x7ff) == 0x7ff); // all 12 months + r->setDaysOfWeek(4); + r->setWeeksOfMonth(2); + r->setMonthsOfYear(0x200); + r->setSunset(QDateTime(QDate(2030, 10, 30), QTime())); + + saveFile = payment.save(); + } + { + Payment payment(saveFile, dummy.get()); + auto *r = payment.repeatDetails(); + QVERIFY(r != nullptr); + QCOMPARE(r->repeatType(), WalletEnums::RepeatByDayOfMonth); + QCOMPARE(r->daysOfWeek(), 4); + QCOMPARE(r->weeksOfMonth(), 2); + QCOMPARE(r->monthsOfYear(), 0x200); + QCOMPARE(r->sunset(), QDateTime(QDate(2030, 10, 30), QTime())); + + r->setRepeatType(WalletEnums::RepeatByDayOfWeek); + r->setMonthsOfYear(0x60); + r->setDayOfMonth(QVector() << 7 << 9 << 30); + + saveFile = payment.save(); + } + { + Payment payment(saveFile, dummy.get()); + auto *r = payment.repeatDetails(); + QVERIFY(r != nullptr); + QCOMPARE(r->repeatType(), WalletEnums::RepeatByDayOfWeek); + QCOMPARE(r->sunset(), QDateTime(QDate(2030, 10, 30), QTime())); + QCOMPARE(r->dayOfMonth(), QVector() << 7 << 9 << 30); + QCOMPARE(r->monthsOfYear(), 0x60); + + saveFile = payment.save(); + } } diff --git a/testing/payment/TestPayment.h b/testing/payment/TestPayment.h index 305ab3e..ea57cb3 100644 --- a/testing/payment/TestPayment.h +++ b/testing/payment/TestPayment.h @@ -28,6 +28,7 @@ class TestPayment : public QObject Q_OBJECT private slots: void basic(); + // TODO test saving of inputs void saveOutput(); void saveRepeat(); -- 2.54.0 From 4575d4cf9beed632b29dd9104135a472c0835e44 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 21 Jun 2025 20:39:39 +0200 Subject: [PATCH 1385/1428] Add next() to return the next payment date. --- src/Payment.cpp | 4 +-- src/RepeatPaymentDetails.cpp | 48 ++++++++++++++++++++++++++++++--- src/RepeatPaymentDetails.h | 23 +++++++++++----- testing/payment/TestPayment.cpp | 35 +++++++++++++++++++++--- testing/payment/TestPayment.h | 1 + 5 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 6109c4f..1e493c2 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -163,7 +163,7 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) break; case RepeatSunsetDate: assert(m_repeatDetails); - m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData())); + m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData()).date()); break; case RepeatPaymentTxIndex: assert(false); // TODO @@ -684,7 +684,7 @@ Streaming::ConstBuffer Payment::save() const break; } builder.add(RepeatSunsetDate, - (uint64_t) m_repeatDetails->sunset().toSecsSinceEpoch()); + (uint64_t) QDateTime(m_repeatDetails->sunset(), QTime(0, 0)).toSecsSinceEpoch()); for (const auto txIndex : m_repeatDetails->historicalPayments()) { builder.add(RepeatPaymentTxIndex, txIndex); } diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index c4a2c7e..5010b26 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -82,16 +82,16 @@ void RepeatPaymentDetails::addDayOfMonth(int day) { if (m_dayOfMonth.contains(day)) return; - // TODO sort in. m_dayOfMonth.append(day); + std::sort(m_dayOfMonth.begin(), m_dayOfMonth.end()); } -QDateTime RepeatPaymentDetails::sunset() const +QDate RepeatPaymentDetails::sunset() const { return m_sunset; } -void RepeatPaymentDetails::setSunset(const QDateTime &newSunset) +void RepeatPaymentDetails::setSunset(const QDate &newSunset) { if (m_sunset == newSunset) return; @@ -114,3 +114,45 @@ void RepeatPaymentDetails::addPayment(int txIndex) assert(!m_historicalPayments.contains(txIndex)); m_historicalPayments.append(txIndex); } + +QDateTime RepeatPaymentDetails::baseDate() const +{ + return m_baseDate; +} + +void RepeatPaymentDetails::setBaseDate(const QDateTime &newBaseDate) +{ + m_baseDate = newBaseDate; +} + + +QDateTime RepeatPaymentDetails::next() const +{ + if (m_repeatType == WalletEnums::NoRepeat) + return QDateTime(); // invalid + if (!m_baseDate.isValid()) + throw std::runtime_error("Missing base date"); + + if (m_repeatType == WalletEnums::RepeatByDayOfMonth) { + for (int i = 0; i < 12; ++i) { + auto date = m_baseDate.date(); + // reset to start of month to try all m_daysOfMonth + date = QDate(date.year(), date.month(), 1); + date = date.addMonths(i); + for (int day : m_dayOfMonth) { + uint16_t month = 1; + month = month << date.month(); + if (m_monthOfYear & month) { + date = date.addDays(day - date.day()); + if (m_sunset.isValid() && date > m_sunset) + return QDateTime(); // no more! + if (date > m_baseDate.date() && date.day() == day) // it wasn't the 30th of Feb or something.. + return QDateTime(date, m_baseDate.time()); + } + } + } + return QDateTime(); // no valid date left in the next 12 months + } + + return QDateTime(); +} diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index 5233713..0c69faf 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -29,15 +29,13 @@ class RepeatPaymentDetails : public QObject { Q_OBJECT Q_PROPERTY(WalletEnums::PaymentRepeatType repeatType READ repeatType WRITE setRepeatType NOTIFY repeatTypeChanged FINAL) - Q_PROPERTY(QDateTime sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) + Q_PROPERTY(QDate sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); WalletEnums::PaymentRepeatType repeatType() const; void setRepeatType(WalletEnums::PaymentRepeatType newRepeatType); - - uint8_t daysOfWeek() const; void setDaysOfWeek(uint8_t dow); @@ -51,13 +49,24 @@ public: void setDayOfMonth(const QVector &newDayOfMonth); void addDayOfMonth(int day); - QDateTime sunset() const; - void setSunset(const QDateTime &newSunset); + QDate sunset() const; + void setSunset(const QDate &newSunset); QVector historicalPayments() const; void setHistoricalPayments(const QVector &newHistoricalPayments); void addPayment(int txIndex); + QDateTime baseDate() const; + /** + * This is useful for next(), as it is the date we start calculating the 'next' from. + * This should either be the date this item is created, or the last time a transaction + * was actually sent. So the next interval is calculated with this date as the base. + */ + void setBaseDate(const QDateTime &newBaseDate); + + /// \see setBaseDate() + QDateTime next() const; + signals: void repeatTypeChanged(); void sunsetChanged(); @@ -74,8 +83,10 @@ private: uint16_t m_monthOfYear = 0x7ff; // bitfield QVector m_dayOfMonth; // list of days in the month we repeat + // the date we either started, or when the last transaction was created. + QDateTime m_baseDate; // after this date+time we will not send any more payments. - QDateTime m_sunset; + QDate m_sunset; QVector m_historicalPayments; // txIndex's in the wallet }; diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index 77c4642..8fb5c64 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -166,7 +166,7 @@ void TestPayment::saveRepeat() r->setDaysOfWeek(4); r->setWeeksOfMonth(2); r->setMonthsOfYear(0x200); - r->setSunset(QDateTime(QDate(2030, 10, 30), QTime())); + r->setSunset(QDate(2030, 10, 30)); saveFile = payment.save(); } @@ -178,7 +178,7 @@ void TestPayment::saveRepeat() QCOMPARE(r->daysOfWeek(), 4); QCOMPARE(r->weeksOfMonth(), 2); QCOMPARE(r->monthsOfYear(), 0x200); - QCOMPARE(r->sunset(), QDateTime(QDate(2030, 10, 30), QTime())); + QCOMPARE(r->sunset(), QDate(2030, 10, 30)); r->setRepeatType(WalletEnums::RepeatByDayOfWeek); r->setMonthsOfYear(0x60); @@ -191,7 +191,7 @@ void TestPayment::saveRepeat() auto *r = payment.repeatDetails(); QVERIFY(r != nullptr); QCOMPARE(r->repeatType(), WalletEnums::RepeatByDayOfWeek); - QCOMPARE(r->sunset(), QDateTime(QDate(2030, 10, 30), QTime())); + QCOMPARE(r->sunset(), QDate(2030, 10, 30)); QCOMPARE(r->dayOfMonth(), QVector() << 7 << 9 << 30); QCOMPARE(r->monthsOfYear(), 0x60); @@ -200,4 +200,33 @@ void TestPayment::saveRepeat() } +void TestPayment::next() +{ + Payment payment; + payment.makeRepeating(); + auto *r = payment.repeatDetails(); + QVERIFY(r); + r->setRepeatType(WalletEnums::RepeatByDayOfMonth); + const QTime time(13, 34); + r->setBaseDate(QDateTime(QDate(2025, 3, 20), time)); + r->addDayOfMonth(13); + QCOMPARE(r->next(), QDateTime(QDate(2025, 4, 13), time)); + r->setMonthsOfYear(1 << 6); // only june + QCOMPARE(r->next(), QDateTime(QDate(2025, 6, 13), time)); + r->setMonthsOfYear(0xfff); // all months again + QCOMPARE(r->next(), QDateTime(QDate(2025, 4, 13), time)); + + r->addDayOfMonth(1); + r->addDayOfMonth(30); + r->setBaseDate(QDateTime(QDate(2025, 1, 20), time)); + r->setSunset(QDate(2025, 3, 1)); + QCOMPARE(r->next(), QDateTime(QDate(2025, 1, 30), time)); + r->setBaseDate(QDateTime(QDate(2025, 1, 30), time)); + QCOMPARE(r->next(), QDateTime(QDate(2025, 2, 1), time)); + r->setBaseDate(QDateTime(QDate(2025, 2, 1), time)); + QCOMPARE(r->next(), QDateTime(QDate(2025, 2, 13), time)); + r->setBaseDate(QDateTime(QDate(2025, 2, 13), time)); + QVERIFY(!r->next().isValid()); // passed sunset date +} + QTEST_MAIN(TestPayment) diff --git a/testing/payment/TestPayment.h b/testing/payment/TestPayment.h index ea57cb3..4fb6ece 100644 --- a/testing/payment/TestPayment.h +++ b/testing/payment/TestPayment.h @@ -31,6 +31,7 @@ private slots: // TODO test saving of inputs void saveOutput(); void saveRepeat(); + void next(); private: std::unique_ptr createWallet(); -- 2.54.0 From 10e48af86147cbc906949fa5714b45d222ab7b4d Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Jun 2025 19:51:19 +0200 Subject: [PATCH 1386/1428] Rework data struct for RepeatPayment, with test. --- src/Payment.cpp | 90 +++++++++---------- src/RepeatPaymentDetails.cpp | 147 +++++++++++++------------------- src/RepeatPaymentDetails.h | 55 +++++------- src/WalletEnums.h | 20 ----- testing/payment/TestPayment.cpp | 130 ++++++++++++++++------------ 5 files changed, 192 insertions(+), 250 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 1e493c2..7b02b5d 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -58,13 +58,15 @@ enum SaveTags { DetailComment, // either this one, or the next DetailCommentBytes, - // Repeat - RepeatType, - RepeatDaysOfWeek, - RepeatWeeksOfMonth, - RepeatMonthsOfYear, - RepeatDayOfMonth, // can occur multiple times + // Repeat (see RepeatPaymentDetails.h) RepeatSunsetDate, + RepeatConfigDayOfMonth, + RepeatConfigDayOfWeek, + RepeatConfigWeekInterval, + RepeatConfigMonthInterval, + RepeatConfigBaseDate, + RepeatConfigCloseTag, + RepeatPaymentTxIndex }; @@ -83,6 +85,7 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) PaymentDetailOutput *curOut = nullptr; Streaming::MessageParser parser(stored); + RepeatPaymentDetails::Config repeatConfig; while (parser.next() == Streaming::FoundTag) { switch (parser.tag()) { case UserComment: { @@ -133,37 +136,30 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) m_paymentDetails.append(curComment); break; } - - // Repeat - case RepeatType: - curOut = nullptr; - assert(m_repeatDetails == nullptr); - if (parser.intData() == WalletEnums::RepeatByDayOfMonth - || parser.intData() == WalletEnums::RepeatByDayOfWeek) { + case RepeatConfigCloseTag: + if (m_repeatDetails == nullptr) m_repeatDetails = new RepeatPaymentDetails(this); - m_repeatDetails->setRepeatType(static_cast( - parser.intData())); - } + m_repeatDetails->addRepeatConfig(repeatConfig); + repeatConfig = RepeatPaymentDetails::Config(); break; - case RepeatDaysOfWeek: - assert(m_repeatDetails); - m_repeatDetails->setDaysOfWeek(parser.longData()); + case RepeatConfigDayOfMonth: + repeatConfig.dayOfMonth = parser.intData(); break; - case RepeatWeeksOfMonth: - assert(m_repeatDetails); - m_repeatDetails->setWeeksOfMonth(parser.longData()); + case RepeatConfigDayOfWeek: + repeatConfig.dayOfWeek = parser.intData(); break; - case RepeatMonthsOfYear: - assert(m_repeatDetails); - m_repeatDetails->setMonthsOfYear(parser.longData()); + case RepeatConfigWeekInterval: + repeatConfig.weekInterval = parser.intData(); break; - case RepeatDayOfMonth: // can occur multiple times - assert(m_repeatDetails); - m_repeatDetails->addDayOfMonth(parser.intData()); + case RepeatConfigMonthInterval: + repeatConfig.monthInterval = parser.intData(); + break; + case RepeatConfigBaseDate: + repeatConfig.baseDate = QDateTime::fromSecsSinceEpoch(parser.longData()); break; case RepeatSunsetDate: - assert(m_repeatDetails); - m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData()).date()); + if (m_repeatDetails) + m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData()).date()); break; case RepeatPaymentTxIndex: assert(false); // TODO @@ -665,26 +661,22 @@ Streaming::ConstBuffer Payment::save() const } } - if (m_repeatDetails && m_repeatDetails->repeatType() != WalletEnums::NoRepeat) { - builder.add(RepeatType, static_cast(m_repeatDetails->repeatType())); - switch (m_repeatDetails->repeatType()) { - case WalletEnums::NoRepeat: - assert(false); - break; - case WalletEnums::RepeatByDayOfMonth: - builder.add(RepeatDaysOfWeek, m_repeatDetails->daysOfWeek()); - builder.add(RepeatWeeksOfMonth, m_repeatDetails->weeksOfMonth()); - builder.add(RepeatMonthsOfYear, m_repeatDetails->monthsOfYear()); - break; - case WalletEnums::RepeatByDayOfWeek: - builder.add(RepeatMonthsOfYear, m_repeatDetails->monthsOfYear()); - for (const auto day : m_repeatDetails->dayOfMonth()) { - builder.add(RepeatDayOfMonth, day); - } - break; + if (m_repeatDetails) { + for (const auto &conf : m_repeatDetails->repeatConfig()) { + if (conf.dayOfMonth >= 0) + builder.add(RepeatConfigDayOfMonth, conf.dayOfMonth); + if (conf.dayOfWeek >= 0) + builder.add(RepeatConfigDayOfWeek, conf.dayOfWeek); + if (conf.weekInterval >= 0) + builder.add(RepeatConfigWeekInterval, conf.weekInterval); + if (conf.monthInterval >= 0) + builder.add(RepeatConfigMonthInterval, conf.monthInterval); + builder.add(RepeatConfigBaseDate, (uint64_t) conf.baseDate.toSecsSinceEpoch()); + builder.add(RepeatConfigCloseTag, true); } - builder.add(RepeatSunsetDate, - (uint64_t) QDateTime(m_repeatDetails->sunset(), QTime(0, 0)).toSecsSinceEpoch()); + if (m_repeatDetails->sunset().isValid()) + builder.add(RepeatSunsetDate, + (uint64_t) QDateTime(m_repeatDetails->sunset(), QTime(0, 0)).toSecsSinceEpoch()); for (const auto txIndex : m_repeatDetails->historicalPayments()) { builder.add(RepeatPaymentTxIndex, txIndex); } diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index 5010b26..221596c 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -25,67 +25,6 @@ RepeatPaymentDetails::RepeatPaymentDetails(Payment *parent) { } -WalletEnums::PaymentRepeatType RepeatPaymentDetails::repeatType() const -{ - return m_repeatType; -} - -void RepeatPaymentDetails::setRepeatType(WalletEnums::PaymentRepeatType newRepeatType) -{ - if (m_repeatType == newRepeatType) - return; - m_repeatType = newRepeatType; - emit repeatTypeChanged(); -} - -uint8_t RepeatPaymentDetails::daysOfWeek() const -{ - return m_dayOfWeek; -} - -void RepeatPaymentDetails::setDaysOfWeek(uint8_t dow) -{ - m_dayOfWeek = dow; -} - -uint8_t RepeatPaymentDetails::weeksOfMonth() const -{ - return m_weekOfMonth; -} - -void RepeatPaymentDetails::setWeeksOfMonth(uint8_t wom) -{ - m_weekOfMonth = wom; -} - -uint16_t RepeatPaymentDetails::monthsOfYear() const -{ - return m_monthOfYear; -} - -void RepeatPaymentDetails::setMonthsOfYear(uint16_t moy) -{ - m_monthOfYear = moy; -} - -QVector RepeatPaymentDetails::dayOfMonth() const -{ - return m_dayOfMonth; -} - -void RepeatPaymentDetails::setDayOfMonth(const QVector &dom) -{ - m_dayOfMonth = dom; -} - -void RepeatPaymentDetails::addDayOfMonth(int day) -{ - if (m_dayOfMonth.contains(day)) - return; - m_dayOfMonth.append(day); - std::sort(m_dayOfMonth.begin(), m_dayOfMonth.end()); -} - QDate RepeatPaymentDetails::sunset() const { return m_sunset; @@ -115,43 +54,73 @@ void RepeatPaymentDetails::addPayment(int txIndex) m_historicalPayments.append(txIndex); } -QDateTime RepeatPaymentDetails::baseDate() const +QVector RepeatPaymentDetails::repeatConfig() const { - return m_baseDate; + return m_repeatConfig; } -void RepeatPaymentDetails::setBaseDate(const QDateTime &newBaseDate) +void RepeatPaymentDetails::setRepeatConfig(const QVector &newRepeatConfig) { - m_baseDate = newBaseDate; + m_repeatConfig = newRepeatConfig; } - -QDateTime RepeatPaymentDetails::next() const +void RepeatPaymentDetails::addRepeatConfig(const Config &conf) { - if (m_repeatType == WalletEnums::NoRepeat) - return QDateTime(); // invalid - if (!m_baseDate.isValid()) - throw std::runtime_error("Missing base date"); + m_repeatConfig.append(conf); + std::sort(m_repeatConfig.begin(), m_repeatConfig.end(), [](const Config &a, const Config &b) { + if (a.dayOfMonth == -1) + return a.dayOfWeek < b.dayOfWeek; + return a.dayOfMonth < b.dayOfMonth; + }); +} - if (m_repeatType == WalletEnums::RepeatByDayOfMonth) { - for (int i = 0; i < 12; ++i) { - auto date = m_baseDate.date(); - // reset to start of month to try all m_daysOfMonth - date = QDate(date.year(), date.month(), 1); - date = date.addMonths(i); - for (int day : m_dayOfMonth) { - uint16_t month = 1; - month = month << date.month(); - if (m_monthOfYear & month) { - date = date.addDays(day - date.day()); - if (m_sunset.isValid() && date > m_sunset) - return QDateTime(); // no more! - if (date > m_baseDate.date() && date.day() == day) // it wasn't the 30th of Feb or something.. - return QDateTime(date, m_baseDate.time()); - } - } +void RepeatPaymentDetails::addRepeatDayOfMonth(int dom, int weekInterval, int monthInterval) +{ + Config conf; + conf.dayOfMonth = dom; + conf.weekInterval = weekInterval; + conf.monthInterval = monthInterval; + addRepeatConfig(conf); +} + +void RepeatPaymentDetails::addRepeatRelative(int weekday, int weekInterval, int monthInterval) +{ + Config conf; + conf.dayOfWeek = weekday; + conf.weekInterval = weekInterval; + conf.monthInterval = monthInterval; + addRepeatConfig(conf); +} + +QDateTime RepeatPaymentDetails::Config::next() const +{ + auto date = baseDate.date(); + if (dayOfMonth > 0) { + // we only honor day of month and monty interval for this type. + date = QDate(date.year(), date.month(), dayOfMonth > 0 ? dayOfMonth : 1); + if (date <= baseDate.date()) // not going to the past + date = date.addMonths(monthInterval > 0 ? monthInterval : 1); + return QDateTime(date, baseDate.time()); + } + if (dayOfWeek >= 0) { + auto diff = dayOfWeek - date.dayOfWeek(); + if (diff < 0) + diff += 7; + if (diff > 0) + date = date.addDays(diff); + else if (weekInterval > 0) + date = date.addDays(weekInterval * 7); + else if (monthInterval > 0) { + // move to the next month, then set it to the day of week requested. + date = date.addMonths(monthInterval); + diff = dayOfWeek - date.dayOfWeek(); + if (diff < -3) // nearest day + diff += 7; + date = date.addDays(diff); } - return QDateTime(); // no valid date left in the next 12 months + else + date = date.addDays(7); // default, move one week + return QDateTime(date, baseDate.time()); } return QDateTime(); diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index 0c69faf..a6ff2a6 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -28,26 +28,24 @@ class Payment; class RepeatPaymentDetails : public QObject { Q_OBJECT - Q_PROPERTY(WalletEnums::PaymentRepeatType repeatType READ repeatType WRITE setRepeatType NOTIFY repeatTypeChanged FINAL) Q_PROPERTY(QDate sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); - WalletEnums::PaymentRepeatType repeatType() const; - void setRepeatType(WalletEnums::PaymentRepeatType newRepeatType); + struct Config { + int dayOfMonth = -1; // like, every 28th of the month + int dayOfWeek = -1; // like, every monday + int weekInterval = -1; // 3 for every 3 weeks. + int monthInterval = -1; // 2 for every other month + /** + * This is useful for next(), as it is the date we start calculating the 'next' from. + * This should either be the date this item is created, or the last time a transaction + * was actually sent. So the next interval is calculated with this date as the base. + */ + QDateTime baseDate; - uint8_t daysOfWeek() const; - void setDaysOfWeek(uint8_t dow); - - uint8_t weeksOfMonth() const; - void setWeeksOfMonth(uint8_t wom); - - uint16_t monthsOfYear() const; - void setMonthsOfYear(uint16_t moy); - - QVector dayOfMonth() const; - void setDayOfMonth(const QVector &newDayOfMonth); - void addDayOfMonth(int day); + QDateTime next() const; + }; QDate sunset() const; void setSunset(const QDate &newSunset); @@ -56,16 +54,11 @@ public: void setHistoricalPayments(const QVector &newHistoricalPayments); void addPayment(int txIndex); - QDateTime baseDate() const; - /** - * This is useful for next(), as it is the date we start calculating the 'next' from. - * This should either be the date this item is created, or the last time a transaction - * was actually sent. So the next interval is calculated with this date as the base. - */ - void setBaseDate(const QDateTime &newBaseDate); - - /// \see setBaseDate() - QDateTime next() const; + QVector repeatConfig() const; + void setRepeatConfig(const QVector &newRepeatConfig); + void addRepeatConfig(const Config &conf); + void addRepeatDayOfMonth(int dom, int weekInterval = -1, int monthInterval = -1); + void addRepeatRelative(int weekday, int weekInterval = -1, int monthInterval = -1); signals: void repeatTypeChanged(); @@ -73,18 +66,8 @@ signals: private: const Payment *m_parent; - QTime m_time; // time of day for scheduled item(s) - WalletEnums::PaymentRepeatType m_repeatType = WalletEnums::NoRepeat; + QVector m_repeatConfig; - // type = RepeatByDayOfWeek repeat every (nth) tuesday - uint8_t m_dayOfWeek = 0x7f; // bitfield on which day of the week we repeat - uint8_t m_weekOfMonth = 0x3f; // bitfield on which week of the month we repeat - // type = RepeatByDayOfMonth Repeat every 15th of (every other) month - uint16_t m_monthOfYear = 0x7ff; // bitfield - QVector m_dayOfMonth; // list of days in the month we repeat - - // the date we either started, or when the last transaction was created. - QDateTime m_baseDate; // after this date+time we will not send any more payments. QDate m_sunset; diff --git a/src/WalletEnums.h b/src/WalletEnums.h index 6af9712..55c22e9 100644 --- a/src/WalletEnums.h +++ b/src/WalletEnums.h @@ -87,26 +87,6 @@ public: // there is no rejected as those just get kicked. }; Q_ENUM(PeerValidity) - - /* - * Repeat style for RepeatByDayOfWeek has features like - * which day of week.(or all) - * which week of month. (or all) - * which week of year. (or all) - * - * so a payment can repeat "every day of the week, every week of the month". - * Or only every 3th thursday of October every year. - * - * Alternatively, we use RepeatByDayOfMonth; - * every 15th of the month. - * which month(s) of the year. - */ - enum PaymentRepeatType { - NoRepeat, - RepeatByDayOfWeek, // repeat every (nth) tuesday - RepeatByDayOfMonth // Repeat every 15th of (every other) month - }; - Q_ENUM(PaymentRepeatType) }; #endif diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index 8fb5c64..40539a1 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -145,9 +145,7 @@ void TestPayment::saveRepeat() payment.makeRepeating(); auto *r = payment.repeatDetails(); QVERIFY(r != nullptr); - // first a dummy type. - r->setRepeatType(WalletEnums::NoRepeat); - QCOMPARE(r->repeatType(), WalletEnums::NoRepeat); + // first an empty one. saveFile = payment.save(); } auto dummy = createWallet(); @@ -158,15 +156,40 @@ void TestPayment::saveRepeat() auto *r = payment.repeatDetails(); QVERIFY(r != nullptr); - // the type where we can do things like "3rd Tuesday in October" - r->setRepeatType(WalletEnums::RepeatByDayOfMonth); - QVERIFY((r->daysOfWeek() & 0x7f) == 0x7f); // by default all days of the week. - QVERIFY((r->weeksOfMonth() & 0x3f) == 0x3f); // by default 5 weeks in a month - QVERIFY((r->monthsOfYear() & 0x7ff) == 0x7ff); // all 12 months - r->setDaysOfWeek(4); - r->setWeeksOfMonth(2); - r->setMonthsOfYear(0x200); + r->addRepeatDayOfMonth(30, 1, 4); + r->addRepeatDayOfMonth(7); + r->addRepeatDayOfMonth(9, 2); + saveFile = payment.save(pool); + } + { + Payment payment(saveFile, dummy.get()); + auto *r = payment.repeatDetails(); + QVERIFY(r != nullptr); + QVERIFY(!r->sunset().isValid()); + auto confs = r->repeatConfig(); + QCOMPARE(confs.size(), 3); + auto conf = confs.at(0); // they get sorted on insert. + QCOMPARE(conf.dayOfMonth, 7); + QCOMPARE(conf.dayOfWeek, -1); + QCOMPARE(conf.weekInterval, -1); + QCOMPARE(conf.monthInterval, -1); + conf = confs.at(1); + QCOMPARE(conf.dayOfMonth, 9); + QCOMPARE(conf.dayOfWeek, -1); + QCOMPARE(conf.weekInterval, 2); + QCOMPARE(conf.monthInterval, -1); + conf = confs.at(2); + QCOMPARE(conf.dayOfMonth, 30); + QCOMPARE(conf.dayOfWeek, -1); + QCOMPARE(conf.weekInterval, 1); + QCOMPARE(conf.monthInterval, 4); + + r->setRepeatConfig(QVector()); // clear + r->addRepeatRelative(1, 4); r->setSunset(QDate(2030, 10, 30)); + confs = r->repeatConfig(); + confs[0].baseDate = QDateTime(QDate(2025, 05, 3), QTime(14,00)); + r->setRepeatConfig(confs); saveFile = payment.save(); } @@ -174,59 +197,54 @@ void TestPayment::saveRepeat() Payment payment(saveFile, dummy.get()); auto *r = payment.repeatDetails(); QVERIFY(r != nullptr); - QCOMPARE(r->repeatType(), WalletEnums::RepeatByDayOfMonth); - QCOMPARE(r->daysOfWeek(), 4); - QCOMPARE(r->weeksOfMonth(), 2); - QCOMPARE(r->monthsOfYear(), 0x200); QCOMPARE(r->sunset(), QDate(2030, 10, 30)); - - r->setRepeatType(WalletEnums::RepeatByDayOfWeek); - r->setMonthsOfYear(0x60); - r->setDayOfMonth(QVector() << 7 << 9 << 30); - - saveFile = payment.save(); + auto confs = r->repeatConfig(); + QCOMPARE(confs.size(), 1); + auto conf = confs.at(0); + QCOMPARE(conf.dayOfMonth, -1); + QCOMPARE(conf.dayOfWeek, 1); + QCOMPARE(conf.weekInterval, 4); + QCOMPARE(conf.monthInterval, -1); + QCOMPARE(conf.baseDate, QDateTime(QDate(2025, 05, 3), QTime(14,00))); + // saveFile = payment.save(pool); } - { - Payment payment(saveFile, dummy.get()); - auto *r = payment.repeatDetails(); - QVERIFY(r != nullptr); - QCOMPARE(r->repeatType(), WalletEnums::RepeatByDayOfWeek); - QCOMPARE(r->sunset(), QDate(2030, 10, 30)); - QCOMPARE(r->dayOfMonth(), QVector() << 7 << 9 << 30); - QCOMPARE(r->monthsOfYear(), 0x60); - - saveFile = payment.save(); - } - } void TestPayment::next() { - Payment payment; - payment.makeRepeating(); - auto *r = payment.repeatDetails(); - QVERIFY(r); - r->setRepeatType(WalletEnums::RepeatByDayOfMonth); const QTime time(13, 34); - r->setBaseDate(QDateTime(QDate(2025, 3, 20), time)); - r->addDayOfMonth(13); - QCOMPARE(r->next(), QDateTime(QDate(2025, 4, 13), time)); - r->setMonthsOfYear(1 << 6); // only june - QCOMPARE(r->next(), QDateTime(QDate(2025, 6, 13), time)); - r->setMonthsOfYear(0xfff); // all months again - QCOMPARE(r->next(), QDateTime(QDate(2025, 4, 13), time)); + RepeatPaymentDetails::Config config; + config.dayOfMonth = 13; + config.baseDate = QDateTime(QDate(2025, 3, 20), time); + QCOMPARE(config.next(), QDateTime(QDate(2025, 4, 13), time)); + config.monthInterval = 3; + QCOMPARE(config.next(), QDateTime(QDate(2025, 6, 13), time)); + config.monthInterval = -1; + QCOMPARE(config.next(), QDateTime(QDate(2025, 4, 13), time)); - r->addDayOfMonth(1); - r->addDayOfMonth(30); - r->setBaseDate(QDateTime(QDate(2025, 1, 20), time)); - r->setSunset(QDate(2025, 3, 1)); - QCOMPARE(r->next(), QDateTime(QDate(2025, 1, 30), time)); - r->setBaseDate(QDateTime(QDate(2025, 1, 30), time)); - QCOMPARE(r->next(), QDateTime(QDate(2025, 2, 1), time)); - r->setBaseDate(QDateTime(QDate(2025, 2, 1), time)); - QCOMPARE(r->next(), QDateTime(QDate(2025, 2, 13), time)); - r->setBaseDate(QDateTime(QDate(2025, 2, 13), time)); - QVERIFY(!r->next().isValid()); // passed sunset date + config.baseDate = QDateTime(QDate(2025, 1, 30), time); + QCOMPARE(config.next(), QDateTime(QDate(2025, 2, 13), time)); + config.baseDate = QDateTime(QDate(2025, 2, 13), time); + QCOMPARE(config.next(), QDateTime(QDate(2025, 3, 13), time)); + config.monthInterval = 3; + QCOMPARE(config.next(), QDateTime(QDate(2025, 5, 13), time)); + + + config = RepeatPaymentDetails::Config(); + config.dayOfWeek = 2; // tuesday + config.baseDate = QDateTime(QDate(2025, 3, 20), time); + QCOMPARE(config.next(), QDateTime(QDate(2025, 3, 25), time)); + config.baseDate = QDateTime(QDate(2025, 3, 25), time); + QCOMPARE(config.next(), QDateTime(QDate(2025, 4, 1), time)); + config.weekInterval = 2; + QCOMPARE(config.next(), QDateTime(QDate(2025, 4, 8), time)); + config.weekInterval = 3; + QCOMPARE(config.next(), QDateTime(QDate(2025, 4, 15), time)); + config.weekInterval = 7; + QCOMPARE(config.next(), QDateTime(QDate(2025, 5, 13), time)); + config.weekInterval = 0; + config.monthInterval = 2; + QCOMPARE(config.next(), QDateTime(QDate(2025, 5, 27), time)); } QTEST_MAIN(TestPayment) -- 2.54.0 From 179d1846408721d99979a1181653161c9d0e748a Mon Sep 17 00:00:00 2001 From: TomZ Date: Sun, 22 Jun 2025 21:44:33 +0200 Subject: [PATCH 1387/1428] Store repeat Payments on Flowee Pay and persist them. The application will thus be able to remember payments between restarts. --- src/FloweePay.cpp | 64 +++++++++++++++++++++++++++++---- src/FloweePay.h | 8 +++++ src/Payment.cpp | 5 +-- src/Payment.h | 2 +- testing/payment/TestPayment.cpp | 20 ++++++----- 5 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 1e89cea..5c43ce0 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -22,6 +22,9 @@ #include "PriceDataProvider.h" #include "IndexerServices.h" #include "TxInfoObject.h" +#include "Payment.h" +#include "RepeatPaymentDetails.h" +#include "AccountInfo.h" #include #include @@ -93,6 +96,9 @@ enum FileTags { WalletSetting_FiatInstaPayLimitCurrency, // string, ISO-currency-code WalletSetting_FiatInstaPayLimit, // int, cents. Has to be directly behind the currency. + PaymentWalletId, // the wallet that the following payment goes with + PaymentData, // see Payment.h + // security features AppProtectionType = 20, // see enum FloweePay::ApplicationProtection AppProtectionHash, // the hash of the password in case type is AppPassword @@ -382,6 +388,7 @@ void FloweePay::init() QFile in(m_basedir + AppdataFilename); Wallet *lastOpened = nullptr; QString currencyCode; // for wallet config + int paymentWalletId = -1; if (in.open(QIODevice::ReadOnly)) { const auto dataSize = in.size(); Streaming::BufferPool pool(dataSize); @@ -462,6 +469,20 @@ void FloweePay::init() else logWarning() << "Setting seen before walletId or currencyCode"; } + else if (parser.tag() == PaymentWalletId) { + paymentWalletId = parser.intData(); + } + else if (parser.tag() == PaymentData) { + assert(paymentWalletId >= 0); + for (auto *w : std::as_const(m_wallets)) { + assert(w->segment()); + if (w->segment()->segmentId() == paymentWalletId) { + auto *payment = new Payment(parser.bytesDataBuffer(), w); + m_repeatPayments.push_back(payment); + break; + } + } + } } } @@ -508,7 +529,13 @@ void FloweePay::saveData() if (m_wallets.isEmpty() && m_createStartWallet) { throw std::runtime_error("Save called before loading finished"); } - auto data = std::make_shared(m_wallets.size() * 100 + 50); + auto data = std::make_shared(m_wallets.size() * 100 + 50 + m_repeatPayments.size() * 100); + QList savedPayments; + savedPayments.reserve(m_repeatPayments.size()); + for (auto *payment : std::as_const(m_repeatPayments)) { + savedPayments.push_back(payment->save(data)); + } + Streaming::MessageBuilder builder(data); for (auto &wallet : m_wallets) { if (wallet->encryptionSeed() != 0) @@ -533,7 +560,6 @@ void FloweePay::saveData() builder.add(WalletSetting_FiatInstaPayLimit, iter.value()); } } - auto protection = m_appProtection; if (protection == AppUnlocked) // thats an in-memory only state. protection = AppPassword; @@ -542,7 +568,13 @@ void FloweePay::saveData() assert(!m_appProtectionHash.IsNull()); // it would be impossible to unlock if so. builder.add(AppProtectionHash, m_appProtectionHash); } - + assert(savedPayments.size() == m_repeatPayments.size()); + for (int i = 0; i < savedPayments.size(); ++i) { + auto *payment = m_repeatPayments.at(i); + assert(payment->currentAccount()); + builder.add(PaymentWalletId, (int) payment->currentAccount()->wallet()->segment()->segmentId()); + builder.add(PaymentData, savedPayments.at(i)); + } auto buf = builder.buffer(); // hash the new file and check if its different lest we can skip saving @@ -566,12 +598,13 @@ void FloweePay::saveData() } try { - std::string filebaseStr(filebase.toStdString()); - std::ofstream out(filebaseStr + "~"); + const std::string filebaseStr(filebase.toStdString()); + const std::string tmpFilename = filebaseStr + std::to_string(QCoreApplication::applicationPid()); + std::ofstream out(tmpFilename); out.write(buf.begin(), buf.size()); out.flush(); out.close(); - std::filesystem::rename(filebaseStr + "~", filebaseStr); + std::filesystem::rename(tmpFilename, filebaseStr); } catch (const std::exception &e) { logFatal() << "Failed to create data file. Permissions issue?" << e; } @@ -1020,6 +1053,11 @@ void FloweePay::setMnemonicProposals(const QStringList &proposals) emit mnemonicProposalsChanged(); } +QList FloweePay::repeatPayments() const +{ + return m_repeatPayments; +} + bool FloweePay::showBGSyncCard() const { return m_showBGSyncCard; @@ -1039,6 +1077,20 @@ void FloweePay::setShowBGSyncCard(bool now) emit showBGSyncCardChanged(); } +void FloweePay::addRepeatPayment(Payment *payment) +{ + assert(payment); + if (!payment->validate() || payment->currentAccount() == nullptr) + throw std::runtime_error("Payment not complete"); + if (!payment->repeatDetails() || payment->repeatDetails()->repeatConfig().isEmpty()) + throw std::runtime_error("Payment not repeating"); + // make a deep copy to allow the original owner to retain ownership + auto data = payment->save(Streaming::pool(0)); + Payment *copy = new Payment(data, payment->currentAccount()->wallet()); + m_repeatPayments.append(copy); + emit repeatPaymentsChanged(); +} + bool FloweePay::externalNotifications() const { return m_notifications.externalNotifications(); diff --git a/src/FloweePay.h b/src/FloweePay.h index b4de473..f527dc0 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -44,6 +44,7 @@ class PriceDataProvider; class CameraController; class IndexerServices; class TxInfoObject; +class Payment; const std::string &chainPrefix(); QString renderAddress(const KeyId &pubkeyhash); @@ -75,6 +76,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) + Q_PROPERTY(QList repeatPayments READ repeatPayments NOTIFY repeatPaymentsChanged FINAL) // notifications Q_PROPERTY(QObject *notification READ notification WRITE setNotification NOTIFY notificationChanged FINAL) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) @@ -465,6 +467,9 @@ public: void setShowBGSyncCard(bool newShowBGSyncCard); + Q_INVOKABLE void addRepeatPayment(Payment *payment); + QList repeatPayments() const; + signals: void loadComplete(); /// \internal @@ -497,6 +502,8 @@ signals: void externalNotificationsChanged(); void showBGSyncCardChanged(); + void repeatPaymentsChanged(); + private slots: void loadingCompleted(); void saveData(); @@ -539,6 +546,7 @@ private: IndexerServices *m_indexerServices; // the Electrum indexer index. QList m_wallets; QHash m_accountConfigs; // key is wallet-segment-id + QList m_repeatPayments; QLockFile m_lockFile; QStringList m_mnemonicProposals; int m_dspTimeout = 5000; diff --git a/src/Payment.cpp b/src/Payment.cpp index 7b02b5d..9958b60 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -621,9 +621,10 @@ void Payment::setRepeatDetails(RepeatPaymentDetails *newRepeatDetails) emit repeatDetailsChanged(); } -Streaming::ConstBuffer Payment::save() const +Streaming::ConstBuffer Payment::save(const std::shared_ptr &pool) const { - Streaming::MessageBuilder builder(Streaming::pool(400)); + pool->reserve(400); + Streaming::MessageBuilder builder(pool); if (!m_userComment.isEmpty()) { auto buf = m_userComment.toUtf8(); builder.addByteArray(UserComment, buf.constBegin(), buf.size()); diff --git a/src/Payment.h b/src/Payment.h index c9ecf3a..cb71e33 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -261,7 +261,7 @@ public: void setRepeatDetails(RepeatPaymentDetails *newRepeatDetails); // Store the Payment and its details in a blob. - Streaming::ConstBuffer save() const; + Streaming::ConstBuffer save(const std::shared_ptr &pool) const; private slots: void recalcAmounts(); diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index 40539a1..f6dffef 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -53,7 +53,8 @@ void TestPayment::basic() QCOMPARE(payment.paymentAmountFiat(), 400); QCOMPARE(payment.userComment(), "my comment"); - saveFile = payment.save(); + auto pool = std::make_shared(); + saveFile = payment.save(pool); } auto dummy = createWallet(); { @@ -67,6 +68,7 @@ void TestPayment::basic() void TestPayment::saveOutput() { + auto pool = std::make_shared(); Streaming::ConstBuffer saveFile; { Payment payment; @@ -79,7 +81,7 @@ void TestPayment::saveOutput() out->setFiatFollows(true); out->setAddress("hello"); - saveFile = payment.save(); + saveFile = payment.save(pool); } auto dummy = createWallet(); { @@ -97,12 +99,11 @@ void TestPayment::saveOutput() const auto copy = out->paymentAmountFiat(); out->setFiatFollows(false); out->setPaymentAmountFiat(copy); - saveFile = payment.save(); + saveFile = payment.save(pool); } - Streaming::BufferPool pool; - pool.write("someScript"); - Streaming::ConstBuffer outScript = pool.commit(); + pool->write("someScript"); + Streaming::ConstBuffer outScript = pool->commit(); QCOMPARE(outScript.size(), 10); { Payment payment(saveFile, dummy.get()); @@ -118,7 +119,7 @@ void TestPayment::saveOutput() out->setOutputScript(outScript); QVERIFY(out->address().isEmpty()); - saveFile = payment.save(); + saveFile = payment.save(pool); } { @@ -137,6 +138,7 @@ void TestPayment::saveOutput() void TestPayment::saveRepeat() { + auto pool = std::make_shared(); Streaming::ConstBuffer saveFile; { Payment payment; @@ -146,7 +148,7 @@ void TestPayment::saveRepeat() auto *r = payment.repeatDetails(); QVERIFY(r != nullptr); // first an empty one. - saveFile = payment.save(); + saveFile = payment.save(pool); } auto dummy = createWallet(); { @@ -191,7 +193,7 @@ void TestPayment::saveRepeat() confs[0].baseDate = QDateTime(QDate(2025, 05, 3), QTime(14,00)); r->setRepeatConfig(confs); - saveFile = payment.save(); + saveFile = payment.save(pool); } { Payment payment(saveFile, dummy.get()); -- 2.54.0 From 74285e3fa21d76c0a099276cf1cdacc4b114f2b0 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 23 Jun 2025 23:44:36 +0200 Subject: [PATCH 1388/1428] Backend for saved payments data. --- src/CMakeLists.txt | 1 + src/FloweePay.cpp | 11 ++- src/FloweePay.h | 6 +- src/Payment.cpp | 9 +- src/Payment.h | 2 +- src/PriceDataProvider.h | 1 + src/RepeatPaymentDetails.cpp | 13 +++ src/RepeatPaymentDetails.h | 7 ++ src/SavedPaymentsHandler.cpp | 187 +++++++++++++++++++++++++++++++++++ src/SavedPaymentsHandler.h | 87 ++++++++++++++++ 10 files changed, 309 insertions(+), 15 deletions(-) create mode 100644 src/SavedPaymentsHandler.cpp create mode 100644 src/SavedPaymentsHandler.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b24d46..bcf7eb6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set (PAY_SOURCES QRCreator.cpp QMLClipboardHelper.cpp QMLImportHelper.cpp + SavedPaymentsHandler.cpp TransactionInfo.cpp TxInfoObject.cpp Wallet.cpp diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 5c43ce0..b55f3de 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -477,7 +477,8 @@ void FloweePay::init() for (auto *w : std::as_const(m_wallets)) { assert(w->segment()); if (w->segment()->segmentId() == paymentWalletId) { - auto *payment = new Payment(parser.bytesDataBuffer(), w); + auto payment = std::make_shared(parser.bytesDataBuffer(), w); + payment->moveToThread(thread()); // owned by me, so move it to "my" thread. m_repeatPayments.push_back(payment); break; } @@ -532,7 +533,7 @@ void FloweePay::saveData() auto data = std::make_shared(m_wallets.size() * 100 + 50 + m_repeatPayments.size() * 100); QList savedPayments; savedPayments.reserve(m_repeatPayments.size()); - for (auto *payment : std::as_const(m_repeatPayments)) { + for (auto payment : std::as_const(m_repeatPayments)) { savedPayments.push_back(payment->save(data)); } @@ -570,7 +571,7 @@ void FloweePay::saveData() } assert(savedPayments.size() == m_repeatPayments.size()); for (int i = 0; i < savedPayments.size(); ++i) { - auto *payment = m_repeatPayments.at(i); + auto payment = m_repeatPayments.at(i); assert(payment->currentAccount()); builder.add(PaymentWalletId, (int) payment->currentAccount()->wallet()->segment()->segmentId()); builder.add(PaymentData, savedPayments.at(i)); @@ -1053,7 +1054,7 @@ void FloweePay::setMnemonicProposals(const QStringList &proposals) emit mnemonicProposalsChanged(); } -QList FloweePay::repeatPayments() const +QList> FloweePay::repeatPayments() const { return m_repeatPayments; } @@ -1086,7 +1087,7 @@ void FloweePay::addRepeatPayment(Payment *payment) throw std::runtime_error("Payment not repeating"); // make a deep copy to allow the original owner to retain ownership auto data = payment->save(Streaming::pool(0)); - Payment *copy = new Payment(data, payment->currentAccount()->wallet()); + auto copy = std::make_shared(data, payment->currentAccount()->wallet()); m_repeatPayments.append(copy); emit repeatPaymentsChanged(); } diff --git a/src/FloweePay.h b/src/FloweePay.h index f527dc0..91a9b32 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -76,7 +76,7 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) - Q_PROPERTY(QList repeatPayments READ repeatPayments NOTIFY repeatPaymentsChanged FINAL) + Q_PROPERTY(QList> repeatPayments READ repeatPayments NOTIFY repeatPaymentsChanged FINAL) // notifications Q_PROPERTY(QObject *notification READ notification WRITE setNotification NOTIFY notificationChanged FINAL) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) @@ -468,7 +468,7 @@ public: Q_INVOKABLE void addRepeatPayment(Payment *payment); - QList repeatPayments() const; + QList > repeatPayments() const; signals: void loadComplete(); @@ -546,7 +546,7 @@ private: IndexerServices *m_indexerServices; // the Electrum indexer index. QList m_wallets; QHash m_accountConfigs; // key is wallet-segment-id - QList m_repeatPayments; + QList> m_repeatPayments; QLockFile m_lockFile; QStringList m_mnemonicProposals; int m_dspTimeout = 5000; diff --git a/src/Payment.cpp b/src/Payment.cpp index 9958b60..41898a3 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -34,7 +34,7 @@ #include #include -#define DEBUG_TX_CREATION +// #define DEBUG_TX_CREATION #ifdef DEBUG_TX_CREATION # include # include @@ -77,12 +77,9 @@ Payment::Payment(QObject *parent) reset(); } -Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *parent) - : QObject(parent), - m_account(new AccountInfo(parent)) +Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *wallet) + : m_account(new AccountInfo(wallet, this)) { - assert(parent); - PaymentDetailOutput *curOut = nullptr; Streaming::MessageParser parser(stored); RepeatPaymentDetails::Config repeatConfig; diff --git a/src/Payment.h b/src/Payment.h index cb71e33..3d0706e 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -109,7 +109,7 @@ public: Q_ENUM(Warning) Payment(QObject *parent = nullptr); - Payment(const Streaming::ConstBuffer &stored, Wallet *parent); + Payment(const Streaming::ConstBuffer &stored, Wallet *assignedWallet); void setFeePerByte(int sats); int feePerByte(); diff --git a/src/PriceDataProvider.h b/src/PriceDataProvider.h index 7f1ec48..3321fde 100644 --- a/src/PriceDataProvider.h +++ b/src/PriceDataProvider.h @@ -38,6 +38,7 @@ class PriceDataProvider : public QObject Q_PROPERTY(bool displayCents READ displayCents NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPrefix READ currencySymbolPrefix NOTIFY currencySymbolChanged) Q_PROPERTY(QString currencySymbolPost READ currencySymbolPost NOTIFY currencySymbolChanged) + Q_PROPERTY(QString locale READ locale NOTIFY currencySymbolChanged) public: enum SignOption { NoSign, ///< Never include a minus or plus sign. diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index 221596c..38e622d 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -92,6 +92,19 @@ void RepeatPaymentDetails::addRepeatRelative(int weekday, int weekInterval, int addRepeatConfig(conf); } +QString RepeatPaymentDetails::currencyLocale() const +{ + return m_currencyLocale; +} + +void RepeatPaymentDetails::setCurrencyLocale(const QString &newCurrencyLocale) +{ + if (m_currencyLocale == newCurrencyLocale) + return; + m_currencyLocale = newCurrencyLocale; + emit currencyLocaleChanged(); +} + QDateTime RepeatPaymentDetails::Config::next() const { auto date = baseDate.date(); diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index a6ff2a6..fd4f38b 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -29,6 +29,7 @@ class RepeatPaymentDetails : public QObject { Q_OBJECT Q_PROPERTY(QDate sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) + Q_PROPERTY(QString currencyLocale READ currencyLocale WRITE setCurrencyLocale NOTIFY currencyLocaleChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); @@ -60,9 +61,13 @@ public: void addRepeatDayOfMonth(int dom, int weekInterval = -1, int monthInterval = -1); void addRepeatRelative(int weekday, int weekInterval = -1, int monthInterval = -1); + QString currencyLocale() const; + void setCurrencyLocale(const QString &newCurrencyLocale); + signals: void repeatTypeChanged(); void sunsetChanged(); + void currencyLocaleChanged(); private: const Payment *m_parent; @@ -72,6 +77,8 @@ private: QDate m_sunset; QVector m_historicalPayments; // txIndex's in the wallet + + QString m_currencyLocale; }; #endif diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp new file mode 100644 index 0000000..bba7acc --- /dev/null +++ b/src/SavedPaymentsHandler.cpp @@ -0,0 +1,187 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "SavedPaymentsHandler.h" +#include "FloweePay.h" +#include "PriceDataProvider.h" +#include "RepeatPaymentDetails.h" + +#include + + +SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent) + : QObject(parent), + m_payment(payment), + m_next(next) +{ + connect(this, &SavedPayment::approvedChanged, this, [=]() { + QTimer::singleShot(0, this, [=]() { + if (!m_approved) + return; + if (m_payment->txPrepared()) // don't do it twice. + return; + + auto *fp = FloweePay::instance(); + auto *prices = fp->prices(); + assert(prices); + if (prices->locale() != payment->repeatDetails()->currencyLocale()) { + logDebug() << "Refusing to send payment for a different currency than the app set one"; + return; + } + if (prices->oldData()) { + logDebug() << "Old price data, not sending transaction"; + return; + } + + try { + m_payment->setFiatPrice(prices->price()); + m_payment->prepare(); + m_payment->markUserApproved(); + } catch (const std::exception &e) { + logCritical() << "Failed to prepare the payment." << e; + } + }); + }); +} + +QDateTime SavedPayment::next() const +{ + return m_next; +} + +std::shared_ptr SavedPayment::payment() const +{ + return m_payment; +} + +Payment *SavedPayment::rawPayment() const +{ + return m_payment.get(); +} + +bool SavedPayment::approved() const +{ + return m_approved; +} + +void SavedPayment::setApproved(bool newApproved) +{ + if (m_approved == newApproved) + return; + m_approved = newApproved; + emit approvedChanged(); +} + +// -------------------------------------------------------- + +SavedPaymentsHandler::SavedPaymentsHandler(QObject *parent) + : QObject{parent} +{ + connect (FloweePay::instance(), &FloweePay::repeatPaymentsChanged, this, [=](){ + map(); + }); + map(); +} + +// create and update our lists. +void SavedPaymentsHandler::map() +{ + QList payments; + QList scheduled; + QList soon; + + const QDateTime nowIsh = QDateTime::currentDateTimeUtc().addSecs(3500); + const QDateTime soonIsh = nowIsh.addDays(3); + const auto set = FloweePay::instance()->repeatPayments(); + for (const auto &payment : set) { + auto *repeat = payment->repeatDetails(); + assert(repeat); + if (!repeat) + continue; + QDateTime firstPaymentDate; + const auto list = repeat->repeatConfig(); + for (const auto &config : list) { + auto next = config.next(); + if (!next.isValid()) + continue; + if (!firstPaymentDate.isValid() || next < firstPaymentDate) + firstPaymentDate = next; + } + if (firstPaymentDate.isValid()) { + SavedPayment *sp = nullptr; + for (auto *oldSp : payments) { + if (oldSp->payment() == payment) { + sp = oldSp; + break; + } + } + if (!sp) + sp = new SavedPayment(payment, firstPaymentDate, this); + payments.append(sp); + if (nowIsh > firstPaymentDate) { + scheduled.append(sp); + } else if (soonIsh > firstPaymentDate) { + soon.append(sp); + } + } + } + // if somehow the payment is removed then the SavedPayment object isn't deleted until + // this class is deleted, as it is a child. + // I don't see that happening IRL. This possible memory leak is not worth writing code over. + setPayments(payments); + setScheduled(scheduled); + setSoon(soon); +} + +QList SavedPaymentsHandler::payments() const +{ + return m_payments; +} + +void SavedPaymentsHandler::setPayments(const QList &newPayments) +{ + if (m_payments == newPayments) + return; + m_payments = newPayments; + emit paymentsChanged(); +} + +QList SavedPaymentsHandler::scheduled() const +{ + return m_scheduled; +} + +void SavedPaymentsHandler::setScheduled(const QList &newScheduled) +{ + if (m_scheduled == newScheduled) + return; + m_scheduled = newScheduled; + emit scheduledChanged(); +} + +QList SavedPaymentsHandler::soon() const +{ + return m_soon; +} + +void SavedPaymentsHandler::setSoon(const QList &newSoon) +{ + if (m_soon == newSoon) + return; + m_soon = newSoon; + emit soonChanged(); +} diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h new file mode 100644 index 0000000..df8a8d1 --- /dev/null +++ b/src/SavedPaymentsHandler.h @@ -0,0 +1,87 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef SAVEDPAYMENTSHANDLER_H +#define SAVEDPAYMENTSHANDLER_H + +#include "Payment.h" +#include +#include + +class SavedPayment : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDateTime next READ next CONSTANT FINAL) + Q_PROPERTY(Payment* payment READ rawPayment CONSTANT FINAL) + Q_PROPERTY(bool approved READ approved WRITE setApproved NOTIFY approvedChanged FINAL) +public: + explicit SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent = nullptr); + + QDateTime next() const; + Payment *rawPayment() const; + std::shared_ptr payment() const; + + bool approved() const; + void setApproved(bool newApproved); + +signals: + void approvedChanged(); + +private: + std::shared_ptr m_payment; + QDateTime m_next; + bool m_approved = false; +}; + + +/** + * This is a short-lived object that checks all Payments + * stores on the app singleton and provides them to the QML UI + * in categories and with next-transfer dates. + */ +class SavedPaymentsHandler : public QObject +{ + Q_OBJECT + Q_PROPERTY(QList payments READ payments WRITE setPayments NOTIFY paymentsChanged FINAL) + Q_PROPERTY(QList scheduled READ scheduled WRITE setScheduled NOTIFY scheduledChanged FINAL) + Q_PROPERTY(QList soon READ soon WRITE setSoon NOTIFY soonChanged FINAL) +public: + explicit SavedPaymentsHandler(QObject *parent = nullptr); + + QList payments() const; + void setPayments(const QList &newPayments); + + QList scheduled() const; + void setScheduled(const QList &newScheduled); + + QList soon() const; + void setSoon(const QList &newSoon); + +signals: + void paymentsChanged(); + void scheduledChanged(); + void soonChanged(); + +private: + void map(); + + QList m_payments; // all enabled payments. + QList m_scheduled; // To be sent right now + QList m_soon; // To be send in the next couple of days. +}; + +#endif -- 2.54.0 From cdff2f3e0ad2a1c7aec67fd6974737d2ae03fb4f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Jun 2025 08:47:06 +0200 Subject: [PATCH 1389/1428] Add more properties to the RepeatPaymentDetails --- src/Payment.cpp | 17 +++++++++++++++++ src/RepeatPaymentDetails.cpp | 26 ++++++++++++++++++++++++++ src/RepeatPaymentDetails.h | 18 +++++++++++++++--- testing/payment/TestPayment.cpp | 10 ++++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/Payment.cpp b/src/Payment.cpp index 41898a3..498bf9e 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -66,6 +66,9 @@ enum SaveTags { RepeatConfigMonthInterval, RepeatConfigBaseDate, RepeatConfigCloseTag, + RepeatCurrencyLocale, + RepeatEnabled, + RepeatAutoApprove, RepeatPaymentTxIndex }; @@ -161,6 +164,16 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *wallet) case RepeatPaymentTxIndex: assert(false); // TODO break; + case RepeatCurrencyLocale: + if (m_repeatDetails) + m_repeatDetails->setCurrencyLocale(QString::fromStdString(parser.stringData())); + break; + case RepeatEnabled: + m_repeatDetails->setEnabled(parser.boolData()); + break; + case RepeatAutoApprove: + m_repeatDetails->setAutoApprove(parser.boolData()); + break; } } } @@ -675,6 +688,10 @@ Streaming::ConstBuffer Payment::save(const std::shared_ptrsunset().isValid()) builder.add(RepeatSunsetDate, (uint64_t) QDateTime(m_repeatDetails->sunset(), QTime(0, 0)).toSecsSinceEpoch()); + + builder.add(RepeatCurrencyLocale, m_repeatDetails->currencyLocale().toStdString()); + builder.add(RepeatEnabled, m_repeatDetails->enabled()); + builder.add(RepeatAutoApprove, m_repeatDetails->autoApprove()); for (const auto txIndex : m_repeatDetails->historicalPayments()) { builder.add(RepeatPaymentTxIndex, txIndex); } diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index 38e622d..7cecfb4 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -105,6 +105,32 @@ void RepeatPaymentDetails::setCurrencyLocale(const QString &newCurrencyLocale) emit currencyLocaleChanged(); } +bool RepeatPaymentDetails::enabled() const +{ + return m_enabled; +} + +void RepeatPaymentDetails::setEnabled(bool newEnabled) +{ + if (m_enabled == newEnabled) + return; + m_enabled = newEnabled; + emit enabledChanged(); +} + +bool RepeatPaymentDetails::autoApprove() const +{ + return m_autoApprove; +} + +void RepeatPaymentDetails::setAutoApprove(bool newAutoApprove) +{ + if (m_autoApprove == newAutoApprove) + return; + m_autoApprove = newAutoApprove; + emit autoApproveChanged(); +} + QDateTime RepeatPaymentDetails::Config::next() const { auto date = baseDate.date(); diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index fd4f38b..bf49081 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -18,10 +18,8 @@ #ifndef REPEATPAYMENTDETAILS_H #define REPEATPAYMENTDETAILS_H -#include "WalletEnums.h" - -#include #include +#include class Payment; @@ -30,6 +28,8 @@ class RepeatPaymentDetails : public QObject Q_OBJECT Q_PROPERTY(QDate sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) Q_PROPERTY(QString currencyLocale READ currencyLocale WRITE setCurrencyLocale NOTIFY currencyLocaleChanged FINAL) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) + Q_PROPERTY(bool autoApprove READ autoApprove WRITE setAutoApprove NOTIFY autoApproveChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); @@ -64,11 +64,21 @@ public: QString currencyLocale() const; void setCurrencyLocale(const QString &newCurrencyLocale); + bool enabled() const; + void setEnabled(bool newEnabled); + + bool autoApprove() const; + void setAutoApprove(bool newAutoApprove); + signals: void repeatTypeChanged(); void sunsetChanged(); void currencyLocaleChanged(); + void enabledChanged(); + + void autoApproveChanged(); + private: const Payment *m_parent; QVector m_repeatConfig; @@ -79,6 +89,8 @@ private: QVector m_historicalPayments; // txIndex's in the wallet QString m_currencyLocale; + bool m_enabled = true; + bool m_autoApprove = false; }; #endif diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index f6dffef..df6b946 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -193,6 +193,13 @@ void TestPayment::saveRepeat() confs[0].baseDate = QDateTime(QDate(2025, 05, 3), QTime(14,00)); r->setRepeatConfig(confs); + QCOMPARE(r->currencyLocale(), ""); + QCOMPARE(r->enabled(), true); + QCOMPARE(r->autoApprove(), false); + r->setCurrencyLocale("en_FL"); + r->setEnabled(false); + r->setAutoApprove(true); + saveFile = payment.save(pool); } { @@ -208,6 +215,9 @@ void TestPayment::saveRepeat() QCOMPARE(conf.weekInterval, 4); QCOMPARE(conf.monthInterval, -1); QCOMPARE(conf.baseDate, QDateTime(QDate(2025, 05, 3), QTime(14,00))); + QCOMPARE(r->currencyLocale(), "en_FL"); + QCOMPARE(r->enabled(), false); + QCOMPARE(r->autoApprove(), true); // saveFile = payment.save(pool); } } -- 2.54.0 From 23ff50940115616367b760208f4da6ad8d401b42 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Jun 2025 17:37:22 +0200 Subject: [PATCH 1390/1428] Move the Scan QR button to Android specific button --- guis/mobile/AccountHistory.qml | 37 ++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 65ddfd2..dae55bf 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -29,9 +29,9 @@ ListView { Rectangle { id: backToTopButton - width: 60 - height: 60 - radius: 35 + width: 70 + height: 70 + radius: 30 anchors.right: parent.right anchors.rightMargin: 30 y: { @@ -69,6 +69,33 @@ ListView { } } + Rectangle { + id: startQRButton + width: height + height: { + if (backToTopButton.y < 0) + return 70; + return 0; + } + radius: 35 + clip: true + x: parent.width - width - 30 - (70 / 2 - width / 2) // almost right, but keep this centered around the max width + y: parent.height - height - 15 - (70 / 2 - height / 2) + color: mainWindow.floweeBlue + + Image { + source: "qrc:/qr-code-scan-light.svg" + anchors.centerIn: parent + width: 45 + height: 45 + } + MouseArea { + anchors.fill: parent + onClicked: thePile.push("ScanQRPage.qml") + } + Behavior on height { NumberAnimation { } } + } + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 0.05 @@ -98,7 +125,7 @@ ListView { header: Rectangle { color: palette.base width: root.width - height: column.height + height: column.height + 10 Column { id: column width: root.width - 20 @@ -147,6 +174,7 @@ ListView { width: parent.width } + /* Item { width: 10; height: 25 } // spacer Row { @@ -167,6 +195,7 @@ ListView { text: qsTr("Receive") } } + */ } } -- 2.54.0 From 949c57fa1c175f706b6ff5895778881067700b26 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Jun 2025 18:34:31 +0200 Subject: [PATCH 1391/1428] Make available to QML --- src/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 51a0229..ea30a2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "NewIndicatorProvider.h" #include "Payment.h" #include "PriceDataProvider.h" +#include "SavedPaymentsHandler.h" #include "TransactionInfo.h" #include "PortfolioDataProvider.h" #include "PaymentRequest.h" @@ -203,7 +204,10 @@ int main(int argc, char *argv[]) // the portfolio can only be properly constructed when the init completed portfolio = new PortfolioDataProvider(&engine); engine.rootContext()->setContextProperty("portfolio", portfolio); - +#if MOBILE + auto *payments = new SavedPaymentsHandler(&engine); + engine.rootContext()->setContextProperty("savedPayments", payments); +#endif } auto app = FloweePay::instance(); if (app->appProtection() != FloweePay::AppPassword) { -- 2.54.0 From 8d9f8eb65fb1a78b72af56c738effe77e9765a81 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 24 Jun 2025 22:19:37 +0200 Subject: [PATCH 1392/1428] Reorganize the activity tab on mobile --- guis/mobile.qrc | 2 + guis/mobile/AccountHistory.qml | 126 +------------------ guis/mobile/AccountSyncState.qml | 2 +- guis/mobile/ActivityTab.qml | 200 +++++++++++++++++++++++++++++++ guis/mobile/MainView.qml | 6 +- guis/mobile/MainViewBase.qml | 2 +- guis/mobile/PlannedPayments.qml | 42 +++++++ src/SavedPaymentsHandler.cpp | 3 + 8 files changed, 255 insertions(+), 128 deletions(-) create mode 100644 guis/mobile/ActivityTab.qml create mode 100644 guis/mobile/PlannedPayments.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 3cabeff..913fa9d 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -54,6 +54,7 @@ ControlColors.js Utils.js mobile/WorkflowStarter.js + mobile/ActivityTab.qml mobile/About.qml mobile/AccountHistory.qml mobile/AccountPageListItem.qml @@ -81,6 +82,7 @@ mobile/PageTitledBox.qml mobile/PayWithQR.qml mobile/PopupOverlay.qml + mobile/PlannedPayments.qml mobile/PriceDetails.qml mobile/PriceInputWidget.qml mobile/ReceiveTab.qml diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index dae55bf..4ff64e0 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -23,10 +23,7 @@ import Flowee.org.pay; ListView { id: root - - property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - property string title: qsTr("Home") - + property bool hideQRScanButton: backToTopButton.y > 0 Rectangle { id: backToTopButton width: 70 @@ -69,33 +66,6 @@ ListView { } } - Rectangle { - id: startQRButton - width: height - height: { - if (backToTopButton.y < 0) - return 70; - return 0; - } - radius: 35 - clip: true - x: parent.width - width - 30 - (70 / 2 - width / 2) // almost right, but keep this centered around the max width - y: parent.height - height - 15 - (70 / 2 - height / 2) - color: mainWindow.floweeBlue - - Image { - source: "qrc:/qr-code-scan-light.svg" - anchors.centerIn: parent - width: 45 - height: 45 - } - MouseArea { - anchors.fill: parent - onClicked: thePile.push("ScanQRPage.qml") - } - Behavior on height { NumberAnimation { } } - } - QQC2.ScrollBar.vertical: Flowee.ScrollThumb { id: thumb minimumSize: 0.05 @@ -116,99 +86,14 @@ ListView { } } - /* - The structure here is a bit funny. - But we want to have the entire page scroll in order to show all the transactions we can. - To avoid a mess of two scroll-areas (or Flickables) we simply make the top part into a - header of the listview. - */ - header: Rectangle { - color: palette.base - width: root.width - height: column.height + 10 - Column { - id: column - width: root.width - 20 - x: 10 - y: 10 - - Flowee.BitcoinAmountLabel { - id: bchPriceWidget - opacity: Pay.hideBalance ? 0.2 : 1 - fontPixelSize: 30 - anchors.horizontalCenter: parent.horizontalCenter - value: { - if (Pay.hideBalance) - return 88888888; - return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed - } - colorize: false - showFiat: false - } - Flowee.Label { // fiat price - opacity: Pay.hideBalance ? 0.2 : 1 - width: parent.width - horizontalAlignment: Text.AlignHCenter - text: { - if (Pay.hideBalance) - return Fiat.currencySymbolPrefix + "——" + Fiat.currencySymbolPost - Fiat.formattedPrice(portfolio.current.balanceConfirmed - + portfolio.current.balanceUnconfirmed, Fiat.price) - } - MouseArea { - // make the click area nice and large - width: parent.width - height: parent.height + bchPriceWidget.height + 10 - y: 0 - 5- bchPriceWidget.height - onClicked: popupOverlay.open(priceDetails, parent) - cursorShape: Qt.PointingHandCursor - } - Component { - id: priceDetails - PriceDetails { } - } - } - - AccountSyncState { - account: portfolio.current - width: parent.width - } - - /* - Item { width: 10; height: 25 } // spacer - - Row { - height: startScan.height + 20 - x: (parent.width - width) / 2 - spacing: 25 - Flowee.ImageButton { - id: startScan - source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); - onClicked: thePile.push("ScanQRPage.qml") - iconSize: 70 - text: qsTr("Pay") - } - Flowee.ImageButton { - iconSize: 70 - source: "qrc:/receive.svg" - onClicked: switchToTab(2) // receive tab - text: qsTr("Receive") - } - } - */ - } - } - model: portfolio.current.transactions clip: true - width: parent.width - height: contentHeight focus: true reuseItems: true section.property: "grouping" section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart section.delegate: Item { - height: label.height + 15 + height: label.height + 3 width: root.width Rectangle { color: palette.base @@ -217,18 +102,12 @@ ListView { Flowee.Label { id: label x: 10 - y: 12 font.bold: true font.pixelSize: mainWindow.font.pixelSize * 1.1 text: portfolio.current.transactions.groupingPeriod(section); } MouseArea { anchors.fill: parent } // eat all taps } - property bool itIsChristmas: { - var today = new Date(); - return today.getMonth() == 11 && - (today.getDate() == 24 || today.getDate() == 25 || today.getDate() == 26) - } delegate: Rectangle { id: transactionDelegate property var placementInGroup: model.placementInGroup @@ -309,7 +188,6 @@ ListView { } } - TransactionListItem { id: txListItem anchors.fill: parent diff --git a/guis/mobile/AccountSyncState.qml b/guis/mobile/AccountSyncState.qml index 9d536b5..931af20 100644 --- a/guis/mobile/AccountSyncState.qml +++ b/guis/mobile/AccountSyncState.qml @@ -82,7 +82,7 @@ Item { } Flowee.Label { id: indicator - width: parent.width + anchors.horizontalCenter: parent.horizontalCenter y: root.uptodate ? 0 : progressbar.height + 13 wrapMode: Text.Wrap text: { diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml new file mode 100644 index 0000000..87ea0c3 --- /dev/null +++ b/guis/mobile/ActivityTab.qml @@ -0,0 +1,200 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2023-2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.Basic as QQC2 +import "../Flowee" as Flowee +import "../Utils.js" as Utils +import Flowee.org.pay; + +Item { + property string icon: "qrc:/homeButtonIcon" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); + property string title: qsTr("Home") + property bool showFilterIcon: tabBar.currentIndex === 0 + + property bool itIsChristmas: { + var today = new Date(); + return today.getMonth() == 11 && today.getDate() >= 24 && today.getDate() <= 31 + } + + Column { + id: header + width: parent.width + x: 10 + y: 10 + + Flowee.BitcoinAmountLabel { + id: bchPriceWidget + opacity: Pay.hideBalance ? 0.2 : 1 + fontPixelSize: 30 + anchors.horizontalCenter: parent.horizontalCenter + value: { + if (Pay.hideBalance) + return 88888888; + return portfolio.current.balanceConfirmed + portfolio.current.balanceUnconfirmed + } + colorize: false + showFiat: false + } + Flowee.Label { // fiat price + opacity: Pay.hideBalance ? 0.2 : 1 + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: { + if (Pay.hideBalance) + return Fiat.currencySymbolPrefix + "——" + Fiat.currencySymbolPost + Fiat.formattedPrice(portfolio.current.balanceConfirmed + + portfolio.current.balanceUnconfirmed, Fiat.price) + } + MouseArea { + // make the click area nice and large + width: parent.width + height: parent.height + bchPriceWidget.height + 10 + y: 0 - 5- bchPriceWidget.height + onClicked: popupOverlay.open(priceDetails, parent) + cursorShape: Qt.PointingHandCursor + } + Component { + id: priceDetails + PriceDetails { } + } + } + + AccountSyncState { + account: portfolio.current + width: parent.width + } + } + + ListView { + id: tabBar + orientation: Qt.Horizontal + width: parent.width + height: { + if (count <= 1) + return 0; + if (currentItem === null) + return 40; + return currentItem.height; + } + visible: height > 0 + anchors.top: header.bottom + anchors.topMargin: 20 + + clip: true + boundsBehavior: Flickable.StopAtBounds + currentIndex: activityTabs.currentIndex + model: ListModel { + ListElement { + title: "Activity" + qml: "AccountHistory.qml" + } + /* + ListElement { + title: "Planned" + qml: "PlannedPayments.qml" + } + */ + } + + delegate: Item { + width: Math.max(tabName.width, 120) + height: tabName.height + 20 + + Rectangle { + x: 5 + height: 4 + width: parent.width - 10 + color: palette.highlight + visible: index === tabBar.currentIndex + anchors.bottom: parent.bottom + } + Rectangle { + anchors.fill: parent + color: palette.highlight + visible: index === tabBar.currentIndex + opacity: 0.15 + } + + Text { + id: tabName + color: index === tabBar.currentIndex ? palette.windowText : palette.brightText; + text: model.title + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: { + // fast move + activityTabs.positionViewAtIndex(index, ListView.Beginning) + // slow scroll + tabBar.currentIndex = index; + activityTabs.currentIndex = index; + } + } + } + } + + ListView { + id: activityTabs + anchors.top: tabBar.bottom + anchors.bottom: parent.bottom + width: parent.width + model: tabBar.model + + orientation: ListView.Horizontal + snapMode: ListView.SnapOneItem + onContentXChanged: currentIndex = Math.round(contentX / width); + boundsBehavior: Flickable.StopAtBounds + onCurrentItemChanged: currentItem.item.forceActiveFocus() + delegate: Loader { + width: activityTabs.width + height: activityTabs.height + source: model.qml + } + } + Rectangle { + id: startQRButton + width: height + height: { + let i = activityTabs.currentItem; + if (i != null) { // at creation this is the case. + let hide = i.item.hideQRScanButton + if (typeof(hide) === "boolean" && hide) + return 0; + } + return 70; + } + radius: 35 + clip: true + x: parent.width - width - 30 - (70 / 2 - width / 2) // almost right, but keep this centered around the max width + y: parent.height - height - 15 - (70 / 2 - height / 2) + color: mainWindow.floweeBlue + + Image { + source: "qrc:/qr-code-scan-light.svg" + anchors.centerIn: parent + width: 45 + height: 45 + } + MouseArea { + anchors.fill: parent + onClicked: thePile.push("ScanQRPage.qml") + } + Behavior on height { NumberAnimation { } } + } +} diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index 1f081be..e615f17 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -19,9 +19,10 @@ import QtQuick import "../Flowee" as Flowee MainViewBase { - showFilterIcon: currentIndex === 0 + showFilterIcon: activityTab.showFilterIcon && currentIndex === 0 - AccountHistory { + ActivityTab { + id: activityTab anchors.fill: parent PopupOverlay { id: popupOverlay } Component { @@ -29,6 +30,7 @@ MainViewBase { FilterPopup { } } } + SendTransactionsTab { anchors.fill: parent } diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 4aa8342..c482044 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -359,7 +359,7 @@ QQC2.Control { y: 28 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere - width: Math.min(220, contentWidth) + width: 220 } Rectangle { id: bgSyncButton diff --git a/guis/mobile/PlannedPayments.qml b/guis/mobile/PlannedPayments.qml new file mode 100644 index 0000000..e515096 --- /dev/null +++ b/guis/mobile/PlannedPayments.qml @@ -0,0 +1,42 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.Basic as QQC2 +import "../Flowee" as Flowee +import "../Utils.js" as Utils +import Flowee.org.pay; + +ListView { + id: root + QQC2.ScrollBar.vertical: Flowee.ScrollThumb { + minimumSize: 0.05 + visible: size < 0.9 + } + + // model: portfolio.current.transactions + clip: true + focus: true + reuseItems: true + delegate: Rectangle { + width: 100 + height: 10 + } + Keys.forwardTo: Flowee.ListViewKeyHandler { + target: root + } +} diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index bba7acc..379753f 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -34,6 +34,7 @@ SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateT return; if (m_payment->txPrepared()) // don't do it twice. return; + // TODO skip payments for wallets that are archived auto *fp = FloweePay::instance(); auto *prices = fp->prices(); @@ -104,6 +105,8 @@ void SavedPaymentsHandler::map() QList scheduled; QList soon; + // TODO filter out payments that belong to wallets which are hidden. + const QDateTime nowIsh = QDateTime::currentDateTimeUtc().addSecs(3500); const QDateTime soonIsh = nowIsh.addDays(3); const auto set = FloweePay::instance()->repeatPayments(); -- 2.54.0 From c4ba2e8836ddf081c11a80db8796a7c79f1dcf30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Jun 2025 00:17:08 +0200 Subject: [PATCH 1393/1428] Fixlet --- guis/mobile/MainViewBase.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index c482044..5a52d0b 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -417,7 +417,7 @@ QQC2.Control { Keys.onPressed: (event)=> { if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { // when we are on another tab, we can go to 'main' on back. - if (root.currentIndex != 0) { + if (root.currentIndex !== 0) { root.currentIndex = 0; event.accepted = true; } -- 2.54.0 From 64cca001c0cf6bbe9658e90b0ad21be8ab1cc7bf Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Jun 2025 00:17:25 +0200 Subject: [PATCH 1394/1428] Make spacing around price better --- guis/mobile/AccountHistory.qml | 17 ++++++++++++ guis/mobile/ActivityTab.qml | 50 +++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/guis/mobile/AccountHistory.qml b/guis/mobile/AccountHistory.qml index 4ff64e0..441fb81 100644 --- a/guis/mobile/AccountHistory.qml +++ b/guis/mobile/AccountHistory.qml @@ -24,6 +24,23 @@ import Flowee.org.pay; ListView { id: root property bool hideQRScanButton: backToTopButton.y > 0 + property int leewayHint: 30; + onContentYChanged: { + if (count === 0) + return 30; + let i = itemAtIndex(0); + if (i === null) + return 0; + let lw = contentY - i.y + 30; + if (lw > 0) + lw = Math.max(0, lw - 30); + lw = Math.min(30, lw); + lw = Math.max(-10, lw); + var curHint = leewayHint; + if ((curHint === 0 && lw < 0) || curHint !== 0) + leewayHint = 30 - lw; + } + Rectangle { id: backToTopButton width: 70 diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index 87ea0c3..d5339df 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -31,11 +31,20 @@ Item { return today.getMonth() == 11 && today.getDate() >= 24 && today.getDate() <= 31 } + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: tabBar.bottom + color: palette.window + } + Column { id: header + property int leeway: 30; width: parent.width x: 10 - y: 10 + y: leeway Flowee.BitcoinAmountLabel { id: bchPriceWidget @@ -78,6 +87,8 @@ Item { account: portfolio.current width: parent.width } + + Item { width: 1; height: parent.leeway + 1 } // spacer } ListView { @@ -93,7 +104,6 @@ Item { } visible: height > 0 anchors.top: header.bottom - anchors.topMargin: 20 clip: true boundsBehavior: Flickable.StopAtBounds @@ -103,12 +113,10 @@ Item { title: "Activity" qml: "AccountHistory.qml" } - /* ListElement { title: "Planned" qml: "PlannedPayments.qml" } - */ } delegate: Item { @@ -132,7 +140,7 @@ Item { Text { id: tabName - color: index === tabBar.currentIndex ? palette.windowText : palette.brightText; + color: palette.windowText text: model.title anchors.centerIn: parent } @@ -148,7 +156,6 @@ Item { } } } - ListView { id: activityTabs anchors.top: tabBar.bottom @@ -160,11 +167,30 @@ Item { snapMode: ListView.SnapOneItem onContentXChanged: currentIndex = Math.round(contentX / width); boundsBehavior: Flickable.StopAtBounds - onCurrentItemChanged: currentItem.item.forceActiveFocus() + onCurrentItemChanged: currentItem.makeActive(); delegate: Loader { + id: delegateRoot width: activityTabs.width height: activityTabs.height source: model.qml + function makeActive() { + item.forceActiveFocus(); + leewayHandler.target = item; + } + // Tabs that choose to do so can add the leewayHint property + // in order to make the spacing around the price compress as the + // user moves the list content. + Connections { + id: leewayHandler + // notice that this gives warnings before the item is loaded, + // but it works, so just ignore. + function onLeewayHintChanged() { + var hint = target.leewayHint + if (hint < 0) hint = 0; + if (hint > 40) hint = 40; + header.leeway = hint; + } + } } } Rectangle { @@ -197,4 +223,14 @@ Item { } Behavior on height { NumberAnimation { } } } + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { + // when we are on another tab, we can go to 'main' on back. + if (activityTabs.currentIndex !== 0) { + activityTabs.currentIndex = 0; + event.accepted = true; + } + } + } } -- 2.54.0 From 81db3c9f383a569ae9a8d592226439b538f23af1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 25 Jun 2025 12:29:08 +0200 Subject: [PATCH 1395/1428] UX fixes --- guis/mobile/ActivityTab.qml | 7 ++++--- guis/mobile/PlannedPayments.qml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index d5339df..edf4872 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -168,6 +168,7 @@ Item { onContentXChanged: currentIndex = Math.round(contentX / width); boundsBehavior: Flickable.StopAtBounds onCurrentItemChanged: currentItem.makeActive(); + onCurrentIndexChanged: tabBar.currentIndex = currentIndex delegate: Loader { id: delegateRoot width: activityTabs.width @@ -214,8 +215,8 @@ Item { Image { source: "qrc:/qr-code-scan-light.svg" anchors.centerIn: parent - width: 45 - height: 45 + width: 40 + height: 40 } MouseArea { anchors.fill: parent @@ -228,7 +229,7 @@ Item { if (event.key === Qt.Key_Escape || event.key === Qt.Key_Back) { // when we are on another tab, we can go to 'main' on back. if (activityTabs.currentIndex !== 0) { - activityTabs.currentIndex = 0; + activityTabs.positionViewAtBeginning() event.accepted = true; } } diff --git a/guis/mobile/PlannedPayments.qml b/guis/mobile/PlannedPayments.qml index e515096..2eb683c 100644 --- a/guis/mobile/PlannedPayments.qml +++ b/guis/mobile/PlannedPayments.qml @@ -28,7 +28,7 @@ ListView { visible: size < 0.9 } - // model: portfolio.current.transactions + model: savedPayments.scheduled + savedPayments.soon clip: true focus: true reuseItems: true -- 2.54.0 From f696138a8b12d1a654d70f5957a89a0428402152 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 26 Jun 2025 00:02:57 +0200 Subject: [PATCH 1396/1428] Start the details page --- guis/mobile.qrc | 1 + guis/mobile/ActivityTab.qml | 1 + guis/mobile/PlannedPayments.qml | 90 ++++++++++++++++++++++++++-- guis/mobile/RepeatPaymentDetails.qml | 56 +++++++++++++++++ src/Payment.cpp | 5 ++ src/RepeatPaymentDetails.cpp | 13 ++++ src/RepeatPaymentDetails.h | 9 +++ src/SavedPaymentsHandler.cpp | 83 ++++++++++++++++--------- src/SavedPaymentsHandler.h | 15 ++++- 9 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 guis/mobile/RepeatPaymentDetails.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 913fa9d..1321577 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -86,6 +86,7 @@ mobile/PriceDetails.qml mobile/PriceInputWidget.qml mobile/ReceiveTab.qml + mobile/RepeatPaymentDetails.qml mobile/ScanQRPage.qml mobile/SelectDefaultAccountPage.qml mobile/SelectDefaultConfigButton.qml diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index edf4872..07e2fe7 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -42,6 +42,7 @@ Item { Column { id: header property int leeway: 30; + Behavior on leeway { NumberAnimation {} } width: parent.width x: 10 y: leeway diff --git a/guis/mobile/PlannedPayments.qml b/guis/mobile/PlannedPayments.qml index 2eb683c..a7d6334 100644 --- a/guis/mobile/PlannedPayments.qml +++ b/guis/mobile/PlannedPayments.qml @@ -28,13 +28,95 @@ ListView { visible: size < 0.9 } - model: savedPayments.scheduled + savedPayments.soon + model: savedPayments.payments clip: true focus: true + spacing: 6 reuseItems: true - delegate: Rectangle { - width: 100 - height: 10 + section.property: "section" + section.labelPositioning: ViewSection.InlineLabels + ViewSection.CurrentLabelAtStart + section.delegate: Item { + height: label.height + 3 + width: root.width + Rectangle { + color: palette.base + anchors.fill: parent + } + Flowee.Label { + id: label + x: 10 + font.bold: true + font.pixelSize: mainWindow.font.pixelSize * 1.1 + text: { + if (section === savedPayments.ForApproval) + return qsTr("Need Approval", "a payment") + return qsTr("Upcoming"); + } + } + MouseArea { anchors.fill: parent } // eat all taps + } + delegate: Item { + id: delegateRoot + width: ListView.view.width + height: comment.height + 6 + nextDate.height + 6 + approvedCheck.height + + property QtObject payment: modelData.payment + + Flowee.Label { + id: comment + text: delegateRoot.payment.userComment + width: parent.width - 20 + elide: Text.ElideRight + x: 10 + height: text === "" ? 0: contentHeight + anchors.top: parent.top + } + Flowee.Label { + id: nextDate + text: Pay.formatDateTime(modelData.next) + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: comment.bottom + anchors.topMargin: 6 + color: { + if (modelData.approved) + return Pay.useDarkSkin ? "#86ffa8" : "green"; + return palette.text; + } + } + Flowee.BitcoinAmountLabel { + // check if the payment prefers fiat over sats + id: amountBch + value: delegateRoot.payment.paymentAmount + x: 10 + showFiat: false + colorize: false + anchors.top: comment.bottom + anchors.topMargin: 6 + visible: delegateRoot.payment.details[0].fiatFollows + } + Flowee.Label { + id: amountFiat + visible: !delegateRoot.payment.details[0].fiatFollows + text: Fiat.formattedPrice(delegateRoot.payment.paymentAmountFiat); + x: 10 + anchors.top: comment.bottom + anchors.topMargin: 6 + } + MouseArea { + anchors.fill: parent + onClicked: thePile.push("RepeatPaymentDetails.qml", { payment: delegateRoot.payment }) + } + Flowee.CheckBox { + id: approvedCheck + text: qsTr("Approve Payment") + x: 10 + checked: modelData.approved + onCheckedChanged: modelData.approved = checked + anchors.top: nextDate.bottom + anchors.topMargin: 6 + } + } Keys.forwardTo: Flowee.ListViewKeyHandler { target: root diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml new file mode 100644 index 0000000..cbf1e68 --- /dev/null +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -0,0 +1,56 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.Basic as QQC2 +import "../Flowee" as Flowee +import "../Utils.js" as Utils +import Flowee.org.pay; + +Page { + id: root + headerText: qsTr("Scheduled Payment") + required property QtObject payment; + + Flickable { + anchors.fill: parent + contentWidth: width + contentHeight: column.height + Column { + id: column + width: parent.width + + PageTitledBox { + title: qsTr("Comment") + width: parent.width + EditableLabel { + text: root.payment.userComment + width: parent.width + onEdited: root.payment.userComment = text + } + } + PageTitledBox { + title: qsTr("Advanced") + Flowee.Label { + text: "Fee per byte: " + root.payment.feePerByte + } + } + } + } + + +} diff --git a/src/Payment.cpp b/src/Payment.cpp index 498bf9e..14322af 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -68,6 +68,7 @@ enum SaveTags { RepeatConfigCloseTag, RepeatCurrencyLocale, RepeatEnabled, + RepeatApprovedOne, RepeatAutoApprove, RepeatPaymentTxIndex @@ -171,6 +172,9 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *wallet) case RepeatEnabled: m_repeatDetails->setEnabled(parser.boolData()); break; + case RepeatApprovedOne: + m_repeatDetails->setPreApproveOne(parser.boolData()); + break; case RepeatAutoApprove: m_repeatDetails->setAutoApprove(parser.boolData()); break; @@ -691,6 +695,7 @@ Streaming::ConstBuffer Payment::save(const std::shared_ptrcurrencyLocale().toStdString()); builder.add(RepeatEnabled, m_repeatDetails->enabled()); + builder.add(RepeatApprovedOne, m_repeatDetails->preApproveOne()); builder.add(RepeatAutoApprove, m_repeatDetails->autoApprove()); for (const auto txIndex : m_repeatDetails->historicalPayments()) { builder.add(RepeatPaymentTxIndex, txIndex); diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index 7cecfb4..c6d4ec0 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -131,6 +131,19 @@ void RepeatPaymentDetails::setAutoApprove(bool newAutoApprove) emit autoApproveChanged(); } +bool RepeatPaymentDetails::preApproveOne() const +{ + return m_preApproveOne; +} + +void RepeatPaymentDetails::setPreApproveOne(bool newPreApproveOne) +{ + if (m_preApproveOne == newPreApproveOne) + return; + m_preApproveOne = newPreApproveOne; + emit preApproveOneChanged(); +} + QDateTime RepeatPaymentDetails::Config::next() const { auto date = baseDate.date(); diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index bf49081..d15db63 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -29,6 +29,9 @@ class RepeatPaymentDetails : public QObject Q_PROPERTY(QDate sunset READ sunset WRITE setSunset NOTIFY sunsetChanged FINAL) Q_PROPERTY(QString currencyLocale READ currencyLocale WRITE setCurrencyLocale NOTIFY currencyLocaleChanged FINAL) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) + // approve the upcoming payment. + // this allows (background) Pay to send it without further interaction. + Q_PROPERTY(bool preApproveOne READ preApproveOne WRITE setPreApproveOne NOTIFY preApproveOneChanged FINAL) Q_PROPERTY(bool autoApprove READ autoApprove WRITE setAutoApprove NOTIFY autoApproveChanged FINAL) public: explicit RepeatPaymentDetails(Payment *parent = nullptr); @@ -70,6 +73,9 @@ public: bool autoApprove() const; void setAutoApprove(bool newAutoApprove); + bool preApproveOne() const; + void setPreApproveOne(bool newPreApproveOne); + signals: void repeatTypeChanged(); void sunsetChanged(); @@ -79,6 +85,8 @@ signals: void autoApproveChanged(); + void preApproveOneChanged(); + private: const Payment *m_parent; QVector m_repeatConfig; @@ -90,6 +98,7 @@ private: QString m_currencyLocale; bool m_enabled = true; + bool m_preApproveOne = false; bool m_autoApprove = false; }; diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 379753f..4f21cc8 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -28,33 +28,22 @@ SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateT m_payment(payment), m_next(next) { + assert(payment.get()); + assert(payment->repeatDetails()); connect(this, &SavedPayment::approvedChanged, this, [=]() { - QTimer::singleShot(0, this, [=]() { - if (!m_approved) - return; + if (m_payment->txPrepared()) // don't do it twice. + return; + const auto slotStart = QDateTime::currentDateTimeUtc().addSecs(120 * 60); + if (slotStart < next) // the planned send date is more than 2 hours in future. + return; + if (!approved()) + return; + QTimer::singleShot(1000, this, [=]() { if (m_payment->txPrepared()) // don't do it twice. return; - // TODO skip payments for wallets that are archived - - auto *fp = FloweePay::instance(); - auto *prices = fp->prices(); - assert(prices); - if (prices->locale() != payment->repeatDetails()->currencyLocale()) { - logDebug() << "Refusing to send payment for a different currency than the app set one"; + if (!approved()) return; - } - if (prices->oldData()) { - logDebug() << "Old price data, not sending transaction"; - return; - } - - try { - m_payment->setFiatPrice(prices->price()); - m_payment->prepare(); - m_payment->markUserApproved(); - } catch (const std::exception &e) { - logCritical() << "Failed to prepare the payment." << e; - } + startSend(); }); }); } @@ -76,17 +65,55 @@ Payment *SavedPayment::rawPayment() const bool SavedPayment::approved() const { - return m_approved; + return m_payment->repeatDetails()->preApproveOne(); } void SavedPayment::setApproved(bool newApproved) { - if (m_approved == newApproved) + if (m_payment->repeatDetails()->preApproveOne() == newApproved) return; - m_approved = newApproved; + m_payment->repeatDetails()->setPreApproveOne(newApproved); emit approvedChanged(); } +int SavedPayment::section() const +{ + return m_section; +} + +void SavedPayment::setSection(int section) +{ + m_section = section; +} + +void SavedPayment::startSend() +{ + if (m_payment->txPrepared()) // don't do it twice. + return; + // TODO skip payments for wallets that are archived + + auto *fp = FloweePay::instance(); + auto *prices = fp->prices(); + assert(prices); + if (prices->locale() != m_payment->repeatDetails()->currencyLocale()) { + logDebug() << "Refusing to send payment for a different currency than the app set one"; + return; + } + if (prices->oldData()) { + logDebug() << "Old price data, not sending transaction"; + return; + } + + try { + m_payment->setFiatPrice(prices->price()); + m_payment->prepare(); + m_payment->markUserApproved(); + } catch (const std::exception &e) { + logCritical() << "Failed to prepare the payment." << e; + } + m_payment->repeatDetails()->setPreApproveOne(false); +} + // -------------------------------------------------------- SavedPaymentsHandler::SavedPaymentsHandler(QObject *parent) @@ -108,7 +135,7 @@ void SavedPaymentsHandler::map() // TODO filter out payments that belong to wallets which are hidden. const QDateTime nowIsh = QDateTime::currentDateTimeUtc().addSecs(3500); - const QDateTime soonIsh = nowIsh.addDays(3); + const QDateTime soonIsh = nowIsh.addDays(7); const auto set = FloweePay::instance()->repeatPayments(); for (const auto &payment : set) { auto *repeat = payment->repeatDetails(); @@ -137,8 +164,10 @@ void SavedPaymentsHandler::map() payments.append(sp); if (nowIsh > firstPaymentDate) { scheduled.append(sp); + sp->setSection(ForApproval); } else if (soonIsh > firstPaymentDate) { soon.append(sp); + sp->setSection(NextWeek); } } } diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index df8a8d1..c349408 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -28,6 +28,7 @@ class SavedPayment : public QObject Q_PROPERTY(QDateTime next READ next CONSTANT FINAL) Q_PROPERTY(Payment* payment READ rawPayment CONSTANT FINAL) Q_PROPERTY(bool approved READ approved WRITE setApproved NOTIFY approvedChanged FINAL) + Q_PROPERTY(int section READ section CONSTANT FINAL) public: explicit SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent = nullptr); @@ -38,13 +39,19 @@ public: bool approved() const; void setApproved(bool newApproved); + int section() const; + void setSection(int section); + + // Actually send the payment. + void startSend(); + signals: void approvedChanged(); private: std::shared_ptr m_payment; QDateTime m_next; - bool m_approved = false; + int m_section = 0; }; @@ -60,6 +67,12 @@ class SavedPaymentsHandler : public QObject Q_PROPERTY(QList scheduled READ scheduled WRITE setScheduled NOTIFY scheduledChanged FINAL) Q_PROPERTY(QList soon READ soon WRITE setSoon NOTIFY soonChanged FINAL) public: + enum SectionTypes { + ForApproval, + NextWeek + }; + Q_ENUM(SectionTypes) + explicit SavedPaymentsHandler(QObject *parent = nullptr); QList payments() const; -- 2.54.0 From 77aa20a836f9fb53ef393fde1c98984735b8e79b Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 27 Jun 2025 01:02:10 +0200 Subject: [PATCH 1397/1428] Have most of the repeat payment details done. --- guis/Flowee/Dialog.qml | 10 +- guis/Flowee/Popup.qml | 4 +- guis/Flowee/RadioButton.qml | 1 + guis/mobile.qrc | 2 +- guis/mobile/MiniCalendarWidget.qml | 70 +++++++++ guis/mobile/PlannedPayments.qml | 2 +- guis/mobile/RepeatPaymentDetails.qml | 204 ++++++++++++++++++++++++++- src/SavedPaymentsHandler.cpp | 154 ++++++++++++++++++++ src/SavedPaymentsHandler.h | 59 ++++++++ 9 files changed, 494 insertions(+), 12 deletions(-) create mode 100644 guis/mobile/MiniCalendarWidget.qml diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index de8257c..3b3175b 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -19,7 +19,7 @@ import QtQuick import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts -QQC2.Popup { +Popup { id: root signal accepted @@ -55,13 +55,7 @@ QQC2.Popup { x = local.x; y = local.y; } - - background: Rectangle { - color: palette.light - border.color: palette.midlight - border.width: 1 - radius: 5 - } + padding: 12 Column { width: { diff --git a/guis/Flowee/Popup.qml b/guis/Flowee/Popup.qml index b24a5eb..26331f2 100644 --- a/guis/Flowee/Popup.qml +++ b/guis/Flowee/Popup.qml @@ -34,8 +34,10 @@ T.Popup { implicitContentHeight + bottomPadding + topPadding) background: Rectangle { - color: "blue" // control.palette.window + color: control.palette.window border.color: control.palette.dark + border.width: 1 + radius: 5 } T.Overlay.modal: Rectangle { diff --git a/guis/Flowee/RadioButton.qml b/guis/Flowee/RadioButton.qml index 4c432ae..9aca696 100644 --- a/guis/Flowee/RadioButton.qml +++ b/guis/Flowee/RadioButton.qml @@ -25,6 +25,7 @@ T.RadioButton { implicitWidth: 140 implicitHeight: contentItem.contentHeight + 10 spacing: 6 + property int textAlignOffset: spacing + indicator.implicitWidth contentItem: Label { id: textLabel diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 1321577..ae9eba5 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -102,7 +102,7 @@ mobile/UnlockWidget.qml mobile/VisualSeparator.qml mobile/NewIndicator.qml - desktop/locked.qml + mobile/MiniCalendarWidget.qml diff --git a/guis/mobile/MiniCalendarWidget.qml b/guis/mobile/MiniCalendarWidget.qml new file mode 100644 index 0000000..fe1ece6 --- /dev/null +++ b/guis/mobile/MiniCalendarWidget.qml @@ -0,0 +1,70 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.Basic as QQC2 +import "../Flowee" as Flowee + +Item { + id: root + // please only set it ever to be the first of the month! + required property date month + + // an array of ints. + property var highlights: [] + + width: 91 + height: monthLabel.height + 6 * 13 + Flowee.Label { + id: monthLabel + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: Qt.locale().toString(root.month, "MMM yy") + fontSizeMode: Text.HorizontalFit + } + + Flow { + id: days + anchors.top: monthLabel.bottom + width: parent.width + spacing: 3 + Repeater { + model: root.month.getDay() + Item { width: 10; height: 10 } + } + Repeater { + model: { + // a dirty JavaScript hack. Day zero is actually the last day in the previous month. + var hack = new Date(root.month.getFullYear(), root.month.getMonth() + 1, 0) + return hack.getDate(); + } + Rectangle { + color: root.highlights.includes(modelData + 1) ? mainWindow.floweeGreen : "#00000000" + width: theLabel.width + height: theLabel.height + radius: 6 + Flowee.Label { + id: theLabel + text: (modelData + 1) + width: 10 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 7 + } + } + } + } +} diff --git a/guis/mobile/PlannedPayments.qml b/guis/mobile/PlannedPayments.qml index a7d6334..1f2c5af 100644 --- a/guis/mobile/PlannedPayments.qml +++ b/guis/mobile/PlannedPayments.qml @@ -105,7 +105,7 @@ ListView { } MouseArea { anchors.fill: parent - onClicked: thePile.push("RepeatPaymentDetails.qml", { payment: delegateRoot.payment }) + onClicked: thePile.push("RepeatPaymentDetails.qml", { savedPayment: modelData }) } Flowee.CheckBox { id: approvedCheck diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index cbf1e68..4ed4bb9 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -24,7 +24,9 @@ import Flowee.org.pay; Page { id: root headerText: qsTr("Scheduled Payment") - required property QtObject payment; + // This is supposed to be an instance of the C++ SavedPayment class (SavedPaymentsHandler.h) + required property QtObject savedPayment; + property Payment payment: savedPayment.payment; Flickable { anchors.fill: parent @@ -34,6 +36,206 @@ Page { id: column width: parent.width + PageTitledBox { + title: qsTr("Planning") + width: parent.width + Repeater { + model: root.savedPayment.configs + + Item { + width: column.width + height: planningPane.height + Column { + id: planningPane + width: parent.width + spacing: 10 + + Flowee.RadioButton { + id: aRadioButton + text: "By weekday" + checked: modelData.dayOfWeek !== -1 + visible: index === 0 // don't repeat this for each config + width: parent.width + onClicked: root.savedPayment.switchToWeek(); + } + Flowee.RadioButton { + text: "By day of month" + checked: modelData.dayOfMonth !== -1 + visible: index === 0// don't repeat this for each config + width: parent.width + onClicked: root.savedPayment.switchToMonth(); + } + Flowee.Label { + id: startTimeLabel + x: aRadioButton.textAlignOffset + height: contentHeight + 5 + text: { + var day = modelData.dayOfWeek; + if (day >= 0) { + return Qt.locale().standaloneDayName(day, Locale.LongFormat); + } + day = modelData.dayOfMonth; + if (day > 0) + return Pay.formatDate(modelData.next); + return ""; + } + MouseArea { + width: root.width / 2 + x: -5 + y: -5 + height: parent.height + 10 + onClicked: picker.startDayPicker(); + } + } + Row { + spacing: 6 + x: aRadioButton.textAlignOffset + Flowee.Label { + text: qsTr("Repeat Interval") + ":" + font.bold: true + MouseArea { + width: root.width - aRadioButton.textAlignOffset - 40 + height: parent.height + 10 + y: -5 + x: -5 + onClicked: picker.startRepeatPicker(); + } + } + Flowee.Label { + text: { + var week = modelData.dayOfWeek; + if (week >= 0) { + let interval = modelData.weekInterval + if (interval <= 0) + return qsTr("Every week", "repetition"); + return qsTr("Once per %1 weeks", "repetition", interval).arg(interval); + } + var month = modelData.dayOfMonth + if (month >= 0) { + let interval = modelData.monthInterval + if (interval <= 0) + return qsTr("Every month", "repetition"); + return qsTr("Once per %1 months", "repetition", interval).arg(interval); + } + return ""; + } + } + } + + ListView { + id: calendarsList + width: parent.width + height: 100 + orientation: ListView.Horizontal + model: 12 + clip: true + property QtObject config: modelData + spacing: 10 + delegate: Item { // wrap the widget since the 'index' attached property fails otherwise (qt69). + width: miniCalendar.width + height: miniCalendar.height + MiniCalendarWidget { + id: miniCalendar + month: { + var date = new Date(); // now + date.setDate(1); + date.setMonth(date.getMonth() + index); + return date; + } + highlights: { + // TODO + var hls = [1]; + return hls; + } + } + } + } + + } + Flowee.CloseIcon { + visible: index > 0 + scale: 0.6 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.bottom: parent.bottom + anchors.bottomMargin: startTimeLabel.height + 8 + // onClicked: TODO how to remove the config?? + } + + Flowee.Dialog { + id: picker + property bool isDayPicker: true + + function startDayPicker() { + isDayPicker = true; + open(); + } + function startRepeatPicker() { + isDayPicker = false; + open(); + } + + property QtObject config: modelData + + standardButtons: QQC2.DialogButtonBox.Cancel + contentComponent: Flickable { + width: 160 + height: Math.min(400, chooserColumn.height) + contentWidth: width + contentHeight: chooserColumn.height + clip: true + Column { + id: chooserColumn + width: parent.width + Repeater { + model: picker.isDayPicker ? (modelData.dayOfWeek >= 0 ? 7 : 31) : 12 + Item { + width: 160 + height: chooserLabel.height + 12 + Flowee.Label { + id: chooserLabel + anchors.centerIn: parent + text: { + if (!picker.isDayPicker) + return "" + modelData + if (picker.config.dayOfMonth >= 0) + return "" + (modelData + 1); + return Qt.locale().standaloneDayName(modelData, Locale.LongFormat); + } + } + MouseArea { + anchors.fill: parent + onClicked: { + if (picker.isDayPicker) { + if (picker.config.dayOfMonth >= 0) + picker.config.dayOfMonth = modelData + 1; + else + picker.config.dayOfWeek = modelData; + } + else { + if (picker.config.dayOfMonth >= 0) + picker.config.monthInterval = modelData; + else + picker.config.weekInterval = modelData; + } + + picker.close(); + } + } + } + } + } + } + } + } + } + } + + + // TODO add a flow of the last 5 plus the next 5 expected dates. + // simple date-only texts with a colored background. Past ones without background + // and a little faded. Future ones with a blue or yellow background and normal text. + PageTitledBox { title: qsTr("Comment") width: parent.width diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 4f21cc8..a4fb251 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -22,6 +22,92 @@ #include +RepeatPaymentConfig::RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent) + : QObject(parent), + m_config(config) +{ +} + +void RepeatPaymentConfig::setConfig(const RepeatPaymentDetails::Config &newConfig) +{ + setDayOfMonth(newConfig.dayOfMonth); + setDayOfWeek(newConfig.dayOfWeek); + setWeekInterval(newConfig.weekInterval); + setMonthInterval(newConfig.monthInterval); + m_config.baseDate = newConfig.baseDate; + if (!m_config.baseDate.isValid()) + m_config.baseDate = QDateTime::currentDateTime(); +} + +RepeatPaymentDetails::Config RepeatPaymentConfig::config() const +{ + return m_config; +} + +int RepeatPaymentConfig::dayOfMonth() const +{ + return m_config.dayOfMonth; +} + +void RepeatPaymentConfig::setDayOfMonth(int newDayOfMonth) +{ + if (m_config.dayOfMonth == newDayOfMonth) + return; + m_config.dayOfMonth = newDayOfMonth; + if (m_config.dayOfMonth >= 0) + setDayOfWeek(-1); + emit dayOfMonthChanged(); + emit nextChanged(); +} + +int RepeatPaymentConfig::dayOfWeek() const +{ + return m_config.dayOfWeek; +} + +void RepeatPaymentConfig::setDayOfWeek(int newDayOfWeek) +{ + if (m_config.dayOfWeek == newDayOfWeek) + return; + m_config.dayOfWeek = newDayOfWeek; + if (m_config.dayOfWeek >= 0) + setDayOfMonth(-1); + emit dayOfWeekChanged(); + emit nextChanged(); +} + +int RepeatPaymentConfig::weekInterval() const +{ + return m_config.weekInterval; +} + +void RepeatPaymentConfig::setWeekInterval(int newWeekInterval) +{ + if (m_config.weekInterval == newWeekInterval) + return; + m_config.weekInterval = newWeekInterval; + emit weekIntervalChanged(); +} + +int RepeatPaymentConfig::monthInterval() const +{ + return m_config.monthInterval; +} + +void RepeatPaymentConfig::setMonthInterval(int newMonthInterval) +{ + if (m_config.monthInterval == newMonthInterval) + return; + m_config.monthInterval = newMonthInterval; + emit monthIntervalChanged(); +} + +QDateTime RepeatPaymentConfig::next() const +{ + return m_config.next(); +} + +// -------------------------------------------------------- SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent) : QObject(parent), @@ -30,6 +116,10 @@ SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateT { assert(payment.get()); assert(payment->repeatDetails()); + for (const auto &conf : payment->repeatDetails()->repeatConfig()) { + m_configs.append(new RepeatPaymentConfig(conf, this)); + } + connect(this, &SavedPayment::approvedChanged, this, [=]() { if (m_payment->txPrepared()) // don't do it twice. return; @@ -114,6 +204,70 @@ void SavedPayment::startSend() m_payment->repeatDetails()->setPreApproveOne(false); } +QList SavedPayment::configs() const +{ + return m_configs; +} + +void SavedPayment::setConfigs(const QList &newConfigs) +{ + if (m_configs == newConfigs) + return; + m_configs = newConfigs; + emit configsChanged(); +} + +void SavedPayment::switchToMonth() +{ + assert(m_backupWeekConfigs.isEmpty()); + for (auto *conf : std::as_const(m_configs)) { + m_backupWeekConfigs.append(conf->config()); + } + auto newConfigs = m_backupMonthConfigs; + m_backupMonthConfigs.clear(); + + if (newConfigs.isEmpty()) { + newConfigs.resize(1); + newConfigs[0].dayOfMonth = 1; + } + applyConfigs(newConfigs); +} + +void SavedPayment::switchToWeek() +{ + assert(m_backupMonthConfigs.isEmpty()); + for (auto *conf : std::as_const(m_configs)) { + m_backupMonthConfigs.append(conf->config()); + } + auto newConfigs = m_backupWeekConfigs; + m_backupWeekConfigs.clear(); + + if (newConfigs.isEmpty()) { + newConfigs.resize(1); + newConfigs[0].dayOfWeek = 3; + } + applyConfigs(newConfigs); + +} + +void SavedPayment::applyConfigs(const QVector &newConfigs) +{ + const bool numConfigsChanged = m_configs.size() != newConfigs.size(); + if (m_configs.size() > newConfigs.size()) { + m_configs.resize(newConfigs.size()); + } + while (m_configs.size() < newConfigs.size()) { + m_configs.append(new RepeatPaymentConfig(newConfigs.at(0), this)); + } + assert(m_configs.size() == newConfigs.size()); + for (int i = 0; i < newConfigs.size(); ++i) { + m_configs.at(i)->setConfig(newConfigs.at(i)); + } + if (numConfigsChanged) + emit configsChanged(); + +} + // -------------------------------------------------------- SavedPaymentsHandler::SavedPaymentsHandler(QObject *parent) diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index c349408..6967924 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -19,9 +19,50 @@ #define SAVEDPAYMENTSHANDLER_H #include "Payment.h" +#include "RepeatPaymentDetails.h" #include #include +class RepeatPaymentConfig : public QObject +{ + Q_OBJECT + Q_PROPERTY(int dayOfMonth READ dayOfMonth WRITE setDayOfMonth NOTIFY dayOfMonthChanged FINAL) + Q_PROPERTY(int dayOfWeek READ dayOfWeek WRITE setDayOfWeek NOTIFY dayOfWeekChanged FINAL) + Q_PROPERTY(int weekInterval READ weekInterval WRITE setWeekInterval NOTIFY weekIntervalChanged FINAL) + Q_PROPERTY(int monthInterval READ monthInterval WRITE setMonthInterval NOTIFY monthIntervalChanged FINAL) + Q_PROPERTY(QDateTime next READ next NOTIFY nextChanged FINAL) +public: + explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent = nullptr); + + + void setConfig(const RepeatPaymentDetails::Config &newConfig); + RepeatPaymentDetails::Config config() const; + + int dayOfMonth() const; + void setDayOfMonth(int newDayOfMonth); + + int dayOfWeek() const; + void setDayOfWeek(int newDayOfWeek); + + int weekInterval() const; + void setWeekInterval(int newWeekInterval); + + int monthInterval() const; + void setMonthInterval(int newMonthInterval); + + QDateTime next() const; + +signals: + void dayOfMonthChanged(); + void dayOfWeekChanged(); + void weekIntervalChanged(); + void monthIntervalChanged(); + void nextChanged(); + +private: + RepeatPaymentDetails::Config m_config; +}; + class SavedPayment : public QObject { Q_OBJECT @@ -29,6 +70,7 @@ class SavedPayment : public QObject Q_PROPERTY(Payment* payment READ rawPayment CONSTANT FINAL) Q_PROPERTY(bool approved READ approved WRITE setApproved NOTIFY approvedChanged FINAL) Q_PROPERTY(int section READ section CONSTANT FINAL) + Q_PROPERTY(QList configs READ configs WRITE setConfigs NOTIFY configsChanged FINAL) public: explicit SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent = nullptr); @@ -45,13 +87,30 @@ public: // Actually send the payment. void startSend(); + QList configs() const; + void setConfigs(const QList &newConfigs); + + // Convert the payment repeat config(s) to be month based. + Q_INVOKABLE void switchToMonth(); + Q_INVOKABLE void switchToWeek(); + signals: void approvedChanged(); + void configsChanged(); private: + void applyConfigs(const QVector &configs); + std::shared_ptr m_payment; QDateTime m_next; int m_section = 0; + QList m_configs; + + // the UX improves a lot if it becomes free to switch between the + // week and the month based setups without losing all configs. + // so we provide the switch* methods and we backup the unused configs here. + QVector m_backupWeekConfigs; + QVector m_backupMonthConfigs; }; -- 2.54.0 From 5e3fb8da96c9c9a80a2f9c6b3ec1743ae3f2f3ca Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 27 Jun 2025 21:03:06 +0200 Subject: [PATCH 1398/1428] Many tweaks and more features --- guis/mobile/MiniCalendarWidget.qml | 24 ++++++++++++------- guis/mobile/RepeatPaymentDetails.qml | 35 ++++++++++++++++------------ src/RepeatPaymentDetails.cpp | 14 +++++++---- src/SavedPaymentsHandler.cpp | 26 +++++++++++++++++++++ src/SavedPaymentsHandler.h | 4 ++++ testing/payment/TestPayment.cpp | 9 +++++++ 6 files changed, 85 insertions(+), 27 deletions(-) diff --git a/guis/mobile/MiniCalendarWidget.qml b/guis/mobile/MiniCalendarWidget.qml index fe1ece6..6dde513 100644 --- a/guis/mobile/MiniCalendarWidget.qml +++ b/guis/mobile/MiniCalendarWidget.qml @@ -29,12 +29,13 @@ Item { width: 91 height: monthLabel.height + 6 * 13 - Flowee.Label { + QQC2.Label { id: monthLabel width: parent.width horizontalAlignment: Text.AlignHCenter text: Qt.locale().toString(root.month, "MMM yy") fontSizeMode: Text.HorizontalFit + color: palette.dark } Flow { @@ -52,17 +53,24 @@ Item { var hack = new Date(root.month.getFullYear(), root.month.getMonth() + 1, 0) return hack.getDate(); } - Rectangle { - color: root.highlights.includes(modelData + 1) ? mainWindow.floweeGreen : "#00000000" - width: theLabel.width - height: theLabel.height - radius: 6 - Flowee.Label { + Item { + width: 10 + height: theLabel.contentHeight + property bool highlighted: root.highlights.includes(modelData + 1) + Rectangle { + color: parent.highlighted ? mainWindow.floweeGreen : "#00000000" + width: parent.width + height: width + anchors.centerIn: parent + radius: 6 + } + QQC2.Label { id: theLabel text: (modelData + 1) - width: 10 + width: parent.width horizontalAlignment: Text.AlignHCenter font.pixelSize: 7 + color: parent.highlighted ? "black" : palette.dark } } } diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 4ed4bb9..54f41a9 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -70,13 +70,14 @@ Page { x: aRadioButton.textAlignOffset height: contentHeight + 5 text: { + let loc = Qt.locale(); var day = modelData.dayOfWeek; if (day >= 0) { - return Qt.locale().standaloneDayName(day, Locale.LongFormat); + return loc.standaloneDayName(day, Locale.LongFormat); } day = modelData.dayOfMonth; if (day > 0) - return Pay.formatDate(modelData.next); + return loc.toString(modelData.next, loc.dateFormat(Locale.ShortFormat)); return ""; } MouseArea { @@ -87,9 +88,10 @@ Page { onClicked: picker.startDayPicker(); } } - Row { + Flow { spacing: 6 x: aRadioButton.textAlignOffset + width: parent.width - x Flowee.Label { text: qsTr("Repeat Interval") + ":" font.bold: true @@ -108,14 +110,14 @@ Page { let interval = modelData.weekInterval if (interval <= 0) return qsTr("Every week", "repetition"); - return qsTr("Once per %1 weeks", "repetition", interval).arg(interval); + return qsTr("Once every %1 weeks", "repetition", interval + 1).arg(interval + 1); } var month = modelData.dayOfMonth if (month >= 0) { let interval = modelData.monthInterval if (interval <= 0) return qsTr("Every month", "repetition"); - return qsTr("Once per %1 months", "repetition", interval).arg(interval); + return qsTr("Once every %1 months", "repetition", interval + 1).arg(interval + 1); } return ""; } @@ -128,10 +130,9 @@ Page { height: 100 orientation: ListView.Horizontal model: 12 - clip: true property QtObject config: modelData spacing: 10 - delegate: Item { // wrap the widget since the 'index' attached property fails otherwise (qt69). + delegate: Item { width: miniCalendar.width height: miniCalendar.height MiniCalendarWidget { @@ -143,14 +144,20 @@ Page { return date; } highlights: { - // TODO - var hls = [1]; + var hls = []; + var source = calendarsList.config.predictedPayments + + var thisMonth = month; + for (let date of source) { + if (date.getFullYear() === thisMonth.getFullYear() + && date.getMonth() === thisMonth.getMonth()) + hls.push(date.getDate()); + } return hls; } } } } - } Flowee.CloseIcon { visible: index > 0 @@ -179,7 +186,7 @@ Page { standardButtons: QQC2.DialogButtonBox.Cancel contentComponent: Flickable { - width: 160 + width: picker.width - 40 height: Math.min(400, chooserColumn.height) contentWidth: width contentHeight: chooserColumn.height @@ -190,15 +197,13 @@ Page { Repeater { model: picker.isDayPicker ? (modelData.dayOfWeek >= 0 ? 7 : 31) : 12 Item { - width: 160 + width: picker.width - 40 height: chooserLabel.height + 12 Flowee.Label { id: chooserLabel anchors.centerIn: parent text: { - if (!picker.isDayPicker) - return "" + modelData - if (picker.config.dayOfMonth >= 0) + if (!picker.isDayPicker || picker.config.dayOfMonth >= 0) return "" + (modelData + 1); return Qt.locale().standaloneDayName(modelData, Locale.LongFormat); } diff --git a/src/RepeatPaymentDetails.cpp b/src/RepeatPaymentDetails.cpp index c6d4ec0..196b189 100644 --- a/src/RepeatPaymentDetails.cpp +++ b/src/RepeatPaymentDetails.cpp @@ -148,11 +148,17 @@ QDateTime RepeatPaymentDetails::Config::next() const { auto date = baseDate.date(); if (dayOfMonth > 0) { - // we only honor day of month and monty interval for this type. - date = QDate(date.year(), date.month(), dayOfMonth > 0 ? dayOfMonth : 1); - if (date <= baseDate.date()) // not going to the past + const auto day = std::min(dayOfMonth, 31); + date = QDate(date.year(), date.month(), 1); + // we only honor day of month and monthly interval for this type. + for (int i = 0; i < 20; ++i) { + QDate newDate(date.year(), date.month(), day); + // only valid and future dates allowed. + if (newDate.isValid() && newDate > baseDate.date()) // ok + return QDateTime(newDate, baseDate.time()); + // try next period date = date.addMonths(monthInterval > 0 ? monthInterval : 1); - return QDateTime(date, baseDate.time()); + } } if (dayOfWeek >= 0) { auto diff = dayOfWeek - date.dayOfWeek(); diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index a4fb251..88e9b1f 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -26,6 +26,7 @@ RepeatPaymentConfig::RepeatPaymentConfig(const RepeatPaymentDetails::Config &con : QObject(parent), m_config(config) { + updatePredictedPayments(); } void RepeatPaymentConfig::setConfig(const RepeatPaymentDetails::Config &newConfig) @@ -37,6 +38,8 @@ void RepeatPaymentConfig::setConfig(const RepeatPaymentDetails::Config &newConfi m_config.baseDate = newConfig.baseDate; if (!m_config.baseDate.isValid()) m_config.baseDate = QDateTime::currentDateTime(); + updatePredictedPayments(); + emit nextChanged(); } RepeatPaymentDetails::Config RepeatPaymentConfig::config() const @@ -57,6 +60,7 @@ void RepeatPaymentConfig::setDayOfMonth(int newDayOfMonth) if (m_config.dayOfMonth >= 0) setDayOfWeek(-1); emit dayOfMonthChanged(); + updatePredictedPayments(); emit nextChanged(); } @@ -73,6 +77,7 @@ void RepeatPaymentConfig::setDayOfWeek(int newDayOfWeek) if (m_config.dayOfWeek >= 0) setDayOfMonth(-1); emit dayOfWeekChanged(); + updatePredictedPayments(); emit nextChanged(); } @@ -87,6 +92,7 @@ void RepeatPaymentConfig::setWeekInterval(int newWeekInterval) return; m_config.weekInterval = newWeekInterval; emit weekIntervalChanged(); + updatePredictedPayments(); } int RepeatPaymentConfig::monthInterval() const @@ -100,6 +106,7 @@ void RepeatPaymentConfig::setMonthInterval(int newMonthInterval) return; m_config.monthInterval = newMonthInterval; emit monthIntervalChanged(); + updatePredictedPayments(); } QDateTime RepeatPaymentConfig::next() const @@ -107,6 +114,25 @@ QDateTime RepeatPaymentConfig::next() const return m_config.next(); } +QList RepeatPaymentConfig::predictedPayments() const +{ + return m_predictedPayments; +} + +void RepeatPaymentConfig::updatePredictedPayments() +{ + m_predictedPayments.clear(); + auto copy = m_config; + const auto end = copy.baseDate.addMonths(12); + for (int i =0; i < 100; ++i) { + auto next = copy.next(); + if (next > end) + break; + m_predictedPayments.append(next.date()); + copy.baseDate = next; + } +} + // -------------------------------------------------------- SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent) diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index 6967924..e9e626a 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -31,6 +31,7 @@ class RepeatPaymentConfig : public QObject Q_PROPERTY(int weekInterval READ weekInterval WRITE setWeekInterval NOTIFY weekIntervalChanged FINAL) Q_PROPERTY(int monthInterval READ monthInterval WRITE setMonthInterval NOTIFY monthIntervalChanged FINAL) Q_PROPERTY(QDateTime next READ next NOTIFY nextChanged FINAL) + Q_PROPERTY(QList predictedPayments READ predictedPayments NOTIFY nextChanged FINAL) public: explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent = nullptr); @@ -51,6 +52,7 @@ public: void setMonthInterval(int newMonthInterval); QDateTime next() const; + QList predictedPayments() const; signals: void dayOfMonthChanged(); @@ -60,7 +62,9 @@ signals: void nextChanged(); private: + void updatePredictedPayments(); RepeatPaymentDetails::Config m_config; + QList m_predictedPayments; }; class SavedPayment : public QObject diff --git a/testing/payment/TestPayment.cpp b/testing/payment/TestPayment.cpp index df6b946..bf120dc 100644 --- a/testing/payment/TestPayment.cpp +++ b/testing/payment/TestPayment.cpp @@ -241,6 +241,15 @@ void TestPayment::next() config.monthInterval = 3; QCOMPARE(config.next(), QDateTime(QDate(2025, 5, 13), time)); + // impossible date handling + config.baseDate = QDateTime(QDate(2025, 2, 4), time); + config.dayOfMonth = 31; + config.monthInterval = 3; + // As feb 31 is impossible, I expect it to be May due to the 3 month interval + QCOMPARE(config.next(), QDateTime(QDate(2025, 5, 31), time)); + config.monthInterval = 12; // an impossible repeat + QVERIFY(!config.next().isValid()); + config = RepeatPaymentDetails::Config(); config.dayOfWeek = 2; // tuesday -- 2.54.0 From 31bece0738aca275907ab9f9c9d3998eacb10c30 Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 30 Jun 2025 21:06:48 +0200 Subject: [PATCH 1399/1428] Finish up backend part of repeat Conf struct. --- guis/mobile/RepeatPaymentDetails.qml | 28 +++++-- src/RepeatPaymentDetails.h | 3 - src/SavedPaymentsHandler.cpp | 111 ++++++++++++++++++++++----- src/SavedPaymentsHandler.h | 16 +++- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 54f41a9..a6c478b 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -110,14 +110,14 @@ Page { let interval = modelData.weekInterval if (interval <= 0) return qsTr("Every week", "repetition"); - return qsTr("Once every %1 weeks", "repetition", interval + 1).arg(interval + 1); + return qsTr("Once every %1 weeks", "repetition", interval).arg(interval); } var month = modelData.dayOfMonth if (month >= 0) { let interval = modelData.monthInterval if (interval <= 0) return qsTr("Every month", "repetition"); - return qsTr("Once every %1 months", "repetition", interval + 1).arg(interval + 1); + return qsTr("Once every %1 months", "repetition", interval).arg(interval); } return ""; } @@ -164,9 +164,23 @@ Page { scale: 0.6 anchors.right: parent.right anchors.rightMargin: 20 - anchors.bottom: parent.bottom + anchors.top: parent.top anchors.bottomMargin: startTimeLabel.height + 8 - // onClicked: TODO how to remove the config?? + onClicked: root.savedPayment.remove(modelData) + } + + Rectangle { + color: "red" + width: 30 + height: 30 + anchors.right: parent.right + anchors.rightMargin: 30 + y: 30 + visible: index === 0 + MouseArea { + anchors.fill: parent + onClicked: root.savedPayment.add() + } } Flowee.Dialog { @@ -217,11 +231,11 @@ Page { else picker.config.dayOfWeek = modelData; } - else { + else { // repeat interval picker if (picker.config.dayOfMonth >= 0) - picker.config.monthInterval = modelData; + picker.config.monthInterval = modelData + 1; else - picker.config.weekInterval = modelData; + picker.config.weekInterval = modelData + 1; } picker.close(); diff --git a/src/RepeatPaymentDetails.h b/src/RepeatPaymentDetails.h index d15db63..7cb0e2a 100644 --- a/src/RepeatPaymentDetails.h +++ b/src/RepeatPaymentDetails.h @@ -80,11 +80,8 @@ signals: void repeatTypeChanged(); void sunsetChanged(); void currencyLocaleChanged(); - void enabledChanged(); - void autoApproveChanged(); - void preApproveOneChanged(); private: diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 88e9b1f..13271ec 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -22,6 +22,24 @@ #include +namespace { +QDateTime calcNext(const std::shared_ptr &payment) +{ + QDateTime firstPaymentDate; + auto *repeat = payment->repeatDetails(); + assert(repeat); + const auto list = repeat->repeatConfig(); + for (const auto &config : list) { + auto next = config.next(); + if (!next.isValid()) + continue; + if (!firstPaymentDate.isValid() || next < firstPaymentDate) + firstPaymentDate = next; + } + return firstPaymentDate; +} +} + RepeatPaymentConfig::RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent) : QObject(parent), m_config(config) @@ -124,13 +142,14 @@ void RepeatPaymentConfig::updatePredictedPayments() m_predictedPayments.clear(); auto copy = m_config; const auto end = copy.baseDate.addMonths(12); - for (int i =0; i < 100; ++i) { + for (int i = 0; i < 100; ++i) { auto next = copy.next(); if (next > end) break; m_predictedPayments.append(next.date()); copy.baseDate = next; } + emit predicatedPaymentsChanged(); } // -------------------------------------------------------- @@ -143,7 +162,7 @@ SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateT assert(payment.get()); assert(payment->repeatDetails()); for (const auto &conf : payment->repeatDetails()->repeatConfig()) { - m_configs.append(new RepeatPaymentConfig(conf, this)); + createNewConfig(conf); } connect(this, &SavedPayment::approvedChanged, this, [=]() { @@ -169,6 +188,14 @@ QDateTime SavedPayment::next() const return m_next; } +void SavedPayment::setNext(const QDateTime &next) +{ + if (m_next == next) + return; + m_next = next; + emit nextChanged(); +} + std::shared_ptr SavedPayment::payment() const { return m_payment; @@ -253,8 +280,9 @@ void SavedPayment::switchToMonth() m_backupMonthConfigs.clear(); if (newConfigs.isEmpty()) { - newConfigs.resize(1); - newConfigs[0].dayOfMonth = 1; + newConfigs.resize(m_configs.size()); + for (int i = 0; i < newConfigs.size(); ++i) + newConfigs[i].dayOfMonth = i + 1; } applyConfigs(newConfigs); } @@ -269,11 +297,52 @@ void SavedPayment::switchToWeek() m_backupWeekConfigs.clear(); if (newConfigs.isEmpty()) { - newConfigs.resize(1); - newConfigs[0].dayOfWeek = 3; + newConfigs.resize(m_configs.size()); + for (int i = 0; i < newConfigs.size(); ++i) + newConfigs[i].dayOfWeek = (i + 3) % 7; } applyConfigs(newConfigs); +} +void SavedPayment::remove(RepeatPaymentConfig *config) +{ + config->deleteLater(); + if (m_configs.removeAll(config)) { + emit configsChanged(); + saveConfigs(); + } + // the backups are there just for the 2 radio buttons + m_backupWeekConfigs.clear(); + m_backupMonthConfigs.clear(); +} + +void SavedPayment::add() +{ + if (m_configs.size() > 12) // arbitary chosen maximum + return; + assert(m_configs.size() > 0); + createNewConfig(m_configs.at(m_configs.size() - 1)->config()); + emit configsChanged(); + saveConfigs(); + // the backups are there just for the 2 radio buttons + m_backupWeekConfigs.clear(); + m_backupMonthConfigs.clear(); +} + +void SavedPayment::createNewConfig(const RepeatPaymentDetails::Config &data) +{ + auto *rpc = new RepeatPaymentConfig(data, this); + auto handler = [=]() { + if (m_saveStarted) return; + m_saveStarted = true; + QTimer::singleShot(100, this, &SavedPayment::saveConfigs); + }; + connect(rpc, &RepeatPaymentConfig::dayOfMonthChanged, this, handler); + connect(rpc, &RepeatPaymentConfig::monthIntervalChanged, this, handler); + connect(rpc, &RepeatPaymentConfig::weekIntervalChanged, this, handler); + connect(rpc, &RepeatPaymentConfig::dayOfWeekChanged, this, handler); + connect(rpc, &RepeatPaymentConfig::nextChanged, this, handler); + m_configs.append(rpc); } void SavedPayment::applyConfigs(const QVector &newConfigs) @@ -283,7 +352,7 @@ void SavedPayment::applyConfigs(const QVector &new m_configs.resize(newConfigs.size()); } while (m_configs.size() < newConfigs.size()) { - m_configs.append(new RepeatPaymentConfig(newConfigs.at(0), this)); + createNewConfig(newConfigs.at(m_configs.size())); } assert(m_configs.size() == newConfigs.size()); for (int i = 0; i < newConfigs.size(); ++i) { @@ -292,6 +361,20 @@ void SavedPayment::applyConfigs(const QVector &new if (numConfigsChanged) emit configsChanged(); + saveConfigs(); +} + +void SavedPayment::saveConfigs() +{ + QVector repeatConfig; + for (auto * rpc : std::as_const(m_configs)) { + repeatConfig.append(rpc->config()); + } + assert(m_payment->repeatDetails()); + m_payment->repeatDetails()->setRepeatConfig(repeatConfig); + m_saveStarted = false; + + setNext(calcNext(m_payment)); } // -------------------------------------------------------- @@ -318,19 +401,7 @@ void SavedPaymentsHandler::map() const QDateTime soonIsh = nowIsh.addDays(7); const auto set = FloweePay::instance()->repeatPayments(); for (const auto &payment : set) { - auto *repeat = payment->repeatDetails(); - assert(repeat); - if (!repeat) - continue; - QDateTime firstPaymentDate; - const auto list = repeat->repeatConfig(); - for (const auto &config : list) { - auto next = config.next(); - if (!next.isValid()) - continue; - if (!firstPaymentDate.isValid() || next < firstPaymentDate) - firstPaymentDate = next; - } + const QDateTime firstPaymentDate = calcNext(payment); if (firstPaymentDate.isValid()) { SavedPayment *sp = nullptr; for (auto *oldSp : payments) { diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index e9e626a..b410dc1 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -31,7 +31,7 @@ class RepeatPaymentConfig : public QObject Q_PROPERTY(int weekInterval READ weekInterval WRITE setWeekInterval NOTIFY weekIntervalChanged FINAL) Q_PROPERTY(int monthInterval READ monthInterval WRITE setMonthInterval NOTIFY monthIntervalChanged FINAL) Q_PROPERTY(QDateTime next READ next NOTIFY nextChanged FINAL) - Q_PROPERTY(QList predictedPayments READ predictedPayments NOTIFY nextChanged FINAL) + Q_PROPERTY(QList predictedPayments READ predictedPayments NOTIFY predicatedPaymentsChanged FINAL) public: explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent = nullptr); @@ -60,6 +60,7 @@ signals: void weekIntervalChanged(); void monthIntervalChanged(); void nextChanged(); + void predicatedPaymentsChanged(); private: void updatePredictedPayments(); @@ -70,7 +71,7 @@ private: class SavedPayment : public QObject { Q_OBJECT - Q_PROPERTY(QDateTime next READ next CONSTANT FINAL) + Q_PROPERTY(QDateTime next READ next NOTIFY nextChanged FINAL) Q_PROPERTY(Payment* payment READ rawPayment CONSTANT FINAL) Q_PROPERTY(bool approved READ approved WRITE setApproved NOTIFY approvedChanged FINAL) Q_PROPERTY(int section READ section CONSTANT FINAL) @@ -79,6 +80,7 @@ public: explicit SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent = nullptr); QDateTime next() const; + void setNext(const QDateTime &next); Payment *rawPayment() const; std::shared_ptr payment() const; @@ -98,17 +100,27 @@ public: Q_INVOKABLE void switchToMonth(); Q_INVOKABLE void switchToWeek(); + // remove one config + Q_INVOKABLE void remove(RepeatPaymentConfig *config); + // add a new config + Q_INVOKABLE void add(); + + signals: void approvedChanged(); void configsChanged(); + void nextChanged(); private: + void createNewConfig(const RepeatPaymentDetails::Config &data); void applyConfigs(const QVector &configs); + void saveConfigs(); std::shared_ptr m_payment; QDateTime m_next; int m_section = 0; QList m_configs; + bool m_saveStarted = false; // the UX improves a lot if it becomes free to switch between the // week and the month based setups without losing all configs. -- 2.54.0 From 1a9e6302f7cd3ac12ea2f8a660ad29dc17eea54b Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 3 Jul 2025 22:51:45 +0200 Subject: [PATCH 1400/1428] Add selection of enddate (sunset date) --- guis/mobile.qrc | 1 + guis/mobile/CalendarWidget.qml | 81 ++++++++++++++++++++++++++++ guis/mobile/MiniCalendarWidget.qml | 30 ++++++++--- guis/mobile/RepeatPaymentDetails.qml | 67 +++++++++++++++++++++-- src/SavedPaymentsHandler.cpp | 13 +++++ src/SavedPaymentsHandler.h | 7 +++ 6 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 guis/mobile/CalendarWidget.qml diff --git a/guis/mobile.qrc b/guis/mobile.qrc index ae9eba5..44446b9 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -104,5 +104,6 @@ mobile/NewIndicator.qml desktop/locked.qml mobile/MiniCalendarWidget.qml + mobile/CalendarWidget.qml diff --git a/guis/mobile/CalendarWidget.qml b/guis/mobile/CalendarWidget.qml new file mode 100644 index 0000000..bd10e5c --- /dev/null +++ b/guis/mobile/CalendarWidget.qml @@ -0,0 +1,81 @@ +/* + * This file is part of the Flowee project + * Copyright (C) 2025 Tom Zander + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls.Basic as QQC2 +import "../Flowee" as Flowee + +Item { + id: root + + property date selected: new Date() + + width: implicitWidth + height: implicitHeight + implicitWidth: mini.implicitWidth * mini.scale + implicitHeight: mini.implicitHeight * mini.scale + + MiniCalendarWidget { + id: mini + y: 5 + scale: 2 + month: { + if (isNaN(root.selected)) + root.selected = new Date(); + var s = root.selected; + return new Date(s.getFullYear(), s.getMonth(), 1); + } + transformOrigin: Item.Top//Left + x: (parent.width - width) / 2 + monthFormat: "MMMM yyyy" + highlights: { + var day = []; + if (root.selected.getFullYear() == month.getFullYear() + && root.selected.getMonth() == month.getMonth()) { + day.push(root.selected.getDate()); + } + return day; + } + onSelectedChanged: { + if (selected !== 0) { + var m = month; + root.selected = new Date(m.getFullYear(), m.getMonth(), selected); + } + } + onMonthChanged: selected = 0; + } + + Flowee.Label { + text: "<" + y: 10 + MouseArea { + anchors.fill: parent + anchors.margins: -8 + onClicked: mini.month.setMonth(mini.month.getMonth() - 1); + } + } + Flowee.Label { + text: ">" + x: parent.width - width + y: 10 + MouseArea { + anchors.fill: parent + anchors.margins: -8 + onClicked: mini.month.setMonth(mini.month.getMonth() + 1); + } + } +} diff --git a/guis/mobile/MiniCalendarWidget.qml b/guis/mobile/MiniCalendarWidget.qml index 6dde513..723fd66 100644 --- a/guis/mobile/MiniCalendarWidget.qml +++ b/guis/mobile/MiniCalendarWidget.qml @@ -23,21 +23,35 @@ Item { id: root // please only set it ever to be the first of the month! required property date month + // when the user clicks on a date. + property int selected: 0 // an array of ints. property var highlights: [] + property string monthFormat: "MMM yy" - width: 91 - height: monthLabel.height + 6 * 13 + width: implicitWidth + height: implicitHeight + implicitWidth: 91 + implicitHeight: days.y + 6 * 13 + onVisibleChanged: if (visible) selected = 0; QQC2.Label { id: monthLabel - width: parent.width - horizontalAlignment: Text.AlignHCenter - text: Qt.locale().toString(root.month, "MMM yy") - fontSizeMode: Text.HorizontalFit + anchors.horizontalCenter: parent.horizontalCenter + text: Qt.locale().toString(root.month, root.monthFormat) + font.pixelSize: 9 color: palette.dark } + Rectangle { + anchors.top: days.top + anchors.topMargin: root.month.getDay() === 0 ? 0 : 10 + anchors.bottom: days.bottom + width: 10 + color: "red" + opacity: 0.2 + radius: 3 + } Flow { id: days anchors.top: monthLabel.bottom @@ -72,6 +86,10 @@ Item { font.pixelSize: 7 color: parent.highlighted ? "black" : palette.dark } + MouseArea { + anchors.fill: parent + onClicked: root.selected = modelData + 1; + } } } } diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index a6c478b..4168be1 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -170,13 +170,30 @@ Page { } Rectangle { - color: "red" + color: "#00000000" + radius: 15 width: 30 height: 30 anchors.right: parent.right - anchors.rightMargin: 30 - y: 30 + anchors.rightMargin: 20 + anchors.top: parent.top + anchors.bottomMargin: startTimeLabel.height + 8 visible: index === 0 + border.width: 1 + border.color: palette.text + Rectangle { + color: palette.text + width: 1 + height: parent.height * 0.5 + anchors.centerIn: parent + } + Rectangle { + color: palette.text + height: 1 + width: parent.width * 0.5 + anchors.centerIn: parent + } + MouseArea { anchors.fill: parent onClicked: root.savedPayment.add() @@ -248,6 +265,50 @@ Page { } } } + + Flow { + spacing: 6 + width: parent.width + Flowee.Label { + text: qsTr("End Date") + ":" + } + Flowee.Label { + text: { + var sunset = root.payment.repeat.sunset + if (isNaN(sunset)) + return qsTr("None") + return Pay.formatDate(sunset) + } + MouseArea { + anchors.fill: parent + anchors.margins: -8 + onClicked: dateTimeDialog.start() + } + + Flowee.Dialog { + id: dateTimeDialog + property var selectedDate: null; + function start() { + selectedDate = root.payment.repeat.sunset; + open(); + } + + contentComponent: CalendarWidget { + onSelectedChanged: dateTimeDialog.selectedDate = selected; + onVisibleChanged: { + if (visible) + selected = dateTimeDialog.selectedDate + } + } + onAccepted: root.payment.repeat.sunset = selectedDate + } + } + } + } + + PageTitledBox { + title: qsTr("Payment Amount") + // TODO } diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 13271ec..62356a3 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -152,6 +152,19 @@ void RepeatPaymentConfig::updatePredictedPayments() emit predicatedPaymentsChanged(); } +QTime RepeatPaymentConfig::time() const +{ + return m_time; +} + +void RepeatPaymentConfig::setTime(const QTime &newTime) +{ + if (m_time == newTime) + return; + m_time = newTime; + emit timeChanged(); +} + // -------------------------------------------------------- SavedPayment::SavedPayment(const std::shared_ptr &payment, const QDateTime &next, QObject *parent) diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index b410dc1..49513ff 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -32,6 +32,7 @@ class RepeatPaymentConfig : public QObject Q_PROPERTY(int monthInterval READ monthInterval WRITE setMonthInterval NOTIFY monthIntervalChanged FINAL) Q_PROPERTY(QDateTime next READ next NOTIFY nextChanged FINAL) Q_PROPERTY(QList predictedPayments READ predictedPayments NOTIFY predicatedPaymentsChanged FINAL) + Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged FINAL) public: explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent = nullptr); @@ -54,6 +55,9 @@ public: QDateTime next() const; QList predictedPayments() const; + QTime time() const; + void setTime(const QTime &newTime); + signals: void dayOfMonthChanged(); void dayOfWeekChanged(); @@ -62,10 +66,13 @@ signals: void nextChanged(); void predicatedPaymentsChanged(); + void timeChanged(); + private: void updatePredictedPayments(); RepeatPaymentDetails::Config m_config; QList m_predictedPayments; + QTime m_time; }; class SavedPayment : public QObject -- 2.54.0 From 6d5901c915c24d09a4d5341f967724d57ccda5fd Mon Sep 17 00:00:00 2001 From: TomZ Date: Mon, 7 Jul 2025 16:17:28 +0200 Subject: [PATCH 1401/1428] Add saturdays too --- guis/mobile/MiniCalendarWidget.qml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/guis/mobile/MiniCalendarWidget.qml b/guis/mobile/MiniCalendarWidget.qml index 723fd66..7288de8 100644 --- a/guis/mobile/MiniCalendarWidget.qml +++ b/guis/mobile/MiniCalendarWidget.qml @@ -32,7 +32,7 @@ Item { width: implicitWidth height: implicitHeight - implicitWidth: 91 + implicitWidth: 88 implicitHeight: days.y + 6 * 13 onVisibleChanged: if (visible) selected = 0; QQC2.Label { @@ -43,7 +43,7 @@ Item { color: palette.dark } - Rectangle { + Rectangle { // sundays anchors.top: days.top anchors.topMargin: root.month.getDay() === 0 ? 0 : 10 anchors.bottom: days.bottom @@ -52,6 +52,19 @@ Item { opacity: 0.2 radius: 3 } + Rectangle { // saturdays + anchors.top: days.top + anchors.bottom: days.bottom + anchors.bottomMargin: { + var last = new Date(root.month.getFullYear(), root.month.getMonth() + 1, 0).getDay(); + return last === 6 ? 0 : 13; + } + anchors.right: days.right + width: 10 + color: "red" + opacity: 0.13 + radius: 3 + } Flow { id: days anchors.top: monthLabel.bottom -- 2.54.0 From 17128faa8de61bdfc4bb3c588fa62719bf808e4e Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 14:13:10 +0200 Subject: [PATCH 1402/1428] Remove unneeded override. The parent object sets this the same. --- guis/mobile/PriceInputWidget.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/guis/mobile/PriceInputWidget.qml b/guis/mobile/PriceInputWidget.qml index faa9438..a44ba0b 100644 --- a/guis/mobile/PriceInputWidget.qml +++ b/guis/mobile/PriceInputWidget.qml @@ -68,7 +68,6 @@ FocusScope { id: priceBch y: data.fiatFollowsSats ? 5 : 68 value: paymentBackend.paymentAmount - focus: true fontPixelSize: size property double size: data.fiatFollowsSats ? 38 : mainWindow.font.pixelSize* 0.8 onActiveFocusChanged: if (activeFocus) data.fiatFollowsSats = true @@ -93,7 +92,6 @@ FocusScope { id: priceFiat value: paymentBackend.paymentAmountFiat y: data.fiatFollowsSats ? 68 : 5 - focus: true fontPixelSize: size property double size: !data.fiatFollowsSats ? 38 : mainWindow.font.pixelSize * 0.8 onActiveFocusChanged: if (activeFocus) data.fiatFollowsSats = false -- 2.54.0 From cf1d865e9bbef13cd5a6723477224783e143b019 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 14:13:57 +0200 Subject: [PATCH 1403/1428] Add price input --- guis/mobile.qrc | 1 + guis/mobile/RepeatPaymentDetails.qml | 122 +++++++++++++++++++++++---- guis/mobile/images/pin-green.svg | 20 +++++ 3 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 guis/mobile/images/pin-green.svg diff --git a/guis/mobile.qrc b/guis/mobile.qrc index 44446b9..09891e0 100644 --- a/guis/mobile.qrc +++ b/guis/mobile.qrc @@ -48,6 +48,7 @@ mobile/images/more.svg mobile/images/more-light.svg mobile/images/play-button.svg + mobile/images/pin-green.svg mobile/images/star.png mobile/main.qml mobile/defaults.ini diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 4168be1..fa6fa81 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -17,6 +17,7 @@ */ import QtQuick import QtQuick.Controls.Basic as QQC2 +import QtQuick.Layouts import "../Flowee" as Flowee import "../Utils.js" as Utils import Flowee.org.pay; @@ -36,6 +37,16 @@ Page { id: column width: parent.width + PageTitledBox { + title: qsTr("Comment") + width: parent.width + EditableLabel { + text: root.payment.userComment + width: parent.width + onEdited: root.payment.userComment = text + } + } + PageTitledBox { title: qsTr("Planning") width: parent.width @@ -52,6 +63,7 @@ Page { Flowee.RadioButton { id: aRadioButton + focus: true text: "By weekday" checked: modelData.dayOfWeek !== -1 visible: index === 0 // don't repeat this for each config @@ -60,6 +72,7 @@ Page { } Flowee.RadioButton { text: "By day of month" + focus: true checked: modelData.dayOfMonth !== -1 visible: index === 0// don't repeat this for each config width: parent.width @@ -308,31 +321,110 @@ Page { PageTitledBox { title: qsTr("Payment Amount") - // TODO - } - - - // TODO add a flow of the last 5 plus the next 5 expected dates. - // simple date-only texts with a colored background. Past ones without background - // and a little faded. Future ones with a blue or yellow background and normal text. - - PageTitledBox { - title: qsTr("Comment") + id: amountsBox width: parent.width - EditableLabel { - text: root.payment.userComment + + property var detail: { + let details = root.payment.details + for (let index in details) { + let detail = details[index]; + if (detail.isOutput) + return detail; + } + return null; + } + + Row { + spacing: 10 + Rectangle { + property bool main: amountsBox.detail.fiatFollows + width: Math.max(priceBch.implicitWidth, 140) + height: 80 + color: "#00000000" + radius: 6 + border.width: 1.3 + border.color: main ? palette.highlight : color + opacity: main ? 1 : 0.7 + Image { + id: pinButton + y: -5 + width: 21 + height: 30 + anchors.horizontalCenter: parent.horizontalCenter + visible: parent.main + source: "qrc:/pin-green.svg"; + } + + MouseArea { + anchors.fill: parent + onClicked: priceBch.forceActiveFocus() + } + + Flowee.BitcoinValueField { + id: priceBch + value: amountsBox.detail.paymentAmount + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: pinButton.bottom + anchors.topMargin: 10 + focus: false + onValueEdited: amountsBox.detail.paymentAmount = value + onActiveFocusChanged: if (activeFocus) amountsBox.detail.fiatFollows = true + } + } + Rectangle { + property bool main: !amountsBox.detail.fiatFollows + width: Math.max(priceFiat.implicitWidth, 140) + height: 80 + color: "#00000000" + radius: 6 + border.width: 1.3 + opacity: main ? 1 : 0.7 + border.color: main ? palette.highlight : color + + Image { + id: pinButton2 + y: -5 + width: 21 + height: 30 + anchors.horizontalCenter: parent.horizontalCenter + visible: parent.main + source: "qrc:/pin-green.svg"; + transform: Scale { xScale: -1; yScale: 1; origin.x: 15 } + } + MouseArea { + anchors.fill: parent + onClicked: priceFiat.forceActiveFocus() + } + + Flowee.FiatValueField { + id: priceFiat + value: amountsBox.detail.paymentAmountFiat + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: pinButton2.bottom + anchors.topMargin: 10 + focus: false + onValueEdited: amountsBox.detail.paymentAmountFiat = value + visible: Pay.isMainChain + onActiveFocusChanged: if (activeFocus) amountsBox.detail.fiatFollows = false + } + } + } + Flowee.Label { width: parent.width - onEdited: root.payment.userComment = text + text: qsTr("Pinned amount will be protected from exchange rate fluctuations.") + font.italic: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere } } + + /* PageTitledBox { title: qsTr("Advanced") Flowee.Label { text: "Fee per byte: " + root.payment.feePerByte } } + */ } } - - } diff --git a/guis/mobile/images/pin-green.svg b/guis/mobile/images/pin-green.svg new file mode 100644 index 0000000..0cab6d9 --- /dev/null +++ b/guis/mobile/images/pin-green.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + -- 2.54.0 From 45ac8eecd230b9ca50dc6bc9e4ba39ea0ecaf5ab Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 14:29:18 +0200 Subject: [PATCH 1404/1428] Save and restore the price on repeat Payment objects. --- src/PaymentDetailOutput.cpp | 4 ++++ src/SavedPaymentsHandler.cpp | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index cc1ecc2..355973f 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -266,6 +266,10 @@ void PaymentDetailOutput::setFiatFollows(bool on) { if (m_fiatFollows == on) return; + if (on) + m_paymentAmount = paymentAmount(); + else + m_fiatAmount = paymentAmountFiat(); m_fiatFollows = on; emit fiatFollowsChanged(); } diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 62356a3..0c494d0 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -399,17 +399,25 @@ SavedPaymentsHandler::SavedPaymentsHandler(QObject *parent) map(); }); map(); + + auto *prices = FloweePay::instance()->prices(); + connect (prices, &PriceDataProvider::priceChanged, this, [=]() { + const auto price = prices->price(); + for (auto *payment : std::as_const(m_payments)) { + payment->payment()->setFiatPrice(price); + } + }); } // create and update our lists. void SavedPaymentsHandler::map() { + auto *prices = FloweePay::instance()->prices(); QList payments; QList scheduled; QList soon; // TODO filter out payments that belong to wallets which are hidden. - const QDateTime nowIsh = QDateTime::currentDateTimeUtc().addSecs(3500); const QDateTime soonIsh = nowIsh.addDays(7); const auto set = FloweePay::instance()->repeatPayments(); @@ -423,8 +431,10 @@ void SavedPaymentsHandler::map() break; } } - if (!sp) + if (!sp) { sp = new SavedPayment(payment, firstPaymentDate, this); + payment->setFiatPrice(prices->price()); + } payments.append(sp); if (nowIsh > firstPaymentDate) { scheduled.append(sp); -- 2.54.0 From ff1ec16f3e01b91628423b88b605c93bddcb01ef Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 16:17:58 +0200 Subject: [PATCH 1405/1428] Add num keyboard for price input. --- guis/mobile/RepeatPaymentDetails.qml | 49 ++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index fa6fa81..0f204b6 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -30,6 +30,7 @@ Page { property Payment payment: savedPayment.payment; Flickable { + id: scrollPane anchors.fill: parent contentWidth: width contentHeight: column.height @@ -368,7 +369,12 @@ Page { anchors.topMargin: 10 focus: false onValueEdited: amountsBox.detail.paymentAmount = value - onActiveFocusChanged: if (activeFocus) amountsBox.detail.fiatFollows = true + onActiveFocusChanged: { + if (activeFocus) { + amountsBox.detail.fiatFollows = true + numKeyboardWidget.start(money) + } + } } } Rectangle { @@ -405,18 +411,49 @@ Page { focus: false onValueEdited: amountsBox.detail.paymentAmountFiat = value visible: Pay.isMainChain - onActiveFocusChanged: if (activeFocus) amountsBox.detail.fiatFollows = false + onActiveFocusChanged: { + if (activeFocus) { + amountsBox.detail.fiatFollows = false + numKeyboardWidget.start(money) + } + } } } } + NumericKeyboardWidget { + id: numKeyboardWidget + width: parent.width + dataInput: ourData + opacity: 0 + visible: opacity > 0 + function start(money) { + ourData.editor = money; + if (!numKeyboardWidget.visible) { + var ch = scrollPane.contentHeight; + ch += numKeyboardWidget.implicitHeight + numKeyboardWidget.opacity = 1 // will make the item visible + scrollPane.contentY = ch - root.height; + } + } + + Item { + id: ourData + property QtObject editor: null// Item {} + function shake() {} + } + Behavior on opacity { NumberAnimation { } } + } Flowee.Label { + id: valueComment width: parent.width text: qsTr("Pinned amount will be protected from exchange rate fluctuations.") font.italic: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere + focus: true } } + /* PageTitledBox { title: qsTr("Advanced") @@ -427,4 +464,12 @@ Page { */ } } + Keys.onPressed: (event)=> { + if ((event.key === Qt.Key_Escape || event.key === Qt.Key_Back) + && numKeyboardWidget.visible) { + valueComment.forceActiveFocus(); // move focus away from the input widgets to avoid blinking cursor + numKeyboardWidget.opacity = 0; + event.accepted = true; + } + } } -- 2.54.0 From 062c21232b1604b9a574d78a4e39b1dd429c8c1b Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 16:28:36 +0200 Subject: [PATCH 1406/1428] fixlets --- guis/Flowee/RadioButton.qml | 2 +- guis/mobile/RepeatPaymentDetails.qml | 17 +++++++++++++---- guis/mobile/images/pin-green.svg | 2 +- src/FloweePay.h | 1 - 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/guis/Flowee/RadioButton.qml b/guis/Flowee/RadioButton.qml index 9aca696..1541e49 100644 --- a/guis/Flowee/RadioButton.qml +++ b/guis/Flowee/RadioButton.qml @@ -23,7 +23,7 @@ T.RadioButton { id: control implicitWidth: 140 - implicitHeight: contentItem.contentHeight + 10 + implicitHeight: Math.max(60, contentItem.contentHeight + 10) spacing: 6 property int textAlignOffset: spacing + indicator.implicitWidth diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 0f204b6..1dbb209 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -60,7 +60,6 @@ Page { Column { id: planningPane width: parent.width - spacing: 10 Flowee.RadioButton { id: aRadioButton @@ -82,7 +81,8 @@ Page { Flowee.Label { id: startTimeLabel x: aRadioButton.textAlignOffset - height: contentHeight + 5 + height: Math.max(60, contentHeight + 10) + verticalAlignment: Text.AlignVCenter text: { let loc = Qt.locale(); var day = modelData.dayOfWeek; @@ -107,8 +107,11 @@ Page { x: aRadioButton.textAlignOffset width: parent.width - x Flowee.Label { + id: repeatLabel text: qsTr("Repeat Interval") + ":" font.bold: true + height: Math.max(60, contentHeight + 10) + verticalAlignment: Text.AlignVCenter MouseArea { width: root.width - aRadioButton.textAlignOffset - 40 height: parent.height + 10 @@ -118,11 +121,13 @@ Page { } } Flowee.Label { + height: repeatLabel.height + verticalAlignment: Text.AlignVCenter text: { var week = modelData.dayOfWeek; if (week >= 0) { let interval = modelData.weekInterval - if (interval <= 0) + if (interval <= 1) return qsTr("Every week", "repetition"); return qsTr("Once every %1 weeks", "repetition", interval).arg(interval); } @@ -141,7 +146,7 @@ Page { ListView { id: calendarsList width: parent.width - height: 100 + height: 90 orientation: ListView.Horizontal model: 12 property QtObject config: modelData @@ -285,6 +290,8 @@ Page { width: parent.width Flowee.Label { text: qsTr("End Date") + ":" + height: Math.max(60, contentHeight + 10) + verticalAlignment: Text.AlignVCenter } Flowee.Label { text: { @@ -293,6 +300,8 @@ Page { return qsTr("None") return Pay.formatDate(sunset) } + height: Math.max(60, contentHeight + 10) + verticalAlignment: Text.AlignVCenter MouseArea { anchors.fill: parent anchors.margins: -8 diff --git a/guis/mobile/images/pin-green.svg b/guis/mobile/images/pin-green.svg index 0cab6d9..e6fec96 100644 --- a/guis/mobile/images/pin-green.svg +++ b/guis/mobile/images/pin-green.svg @@ -1,5 +1,5 @@ - + diff --git a/src/FloweePay.h b/src/FloweePay.h index 91a9b32..7cc2b07 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -501,7 +501,6 @@ signals: void mnemonicProposalsChanged(); void externalNotificationsChanged(); void showBGSyncCardChanged(); - void repeatPaymentsChanged(); private slots: -- 2.54.0 From 8980986ed9521111cc598f66a8d40f5424f5d32f Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 17:42:52 +0200 Subject: [PATCH 1407/1428] Make end date be previewed too in the minicalendars. --- guis/Flowee/Dialog.qml | 2 ++ guis/mobile/CalendarWidget.qml | 3 ++- guis/mobile/RepeatPaymentDetails.qml | 14 ++++++++------ src/SavedPaymentsHandler.cpp | 22 ++++++++++++++++++++++ src/SavedPaymentsHandler.h | 6 +++--- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/guis/Flowee/Dialog.qml b/guis/Flowee/Dialog.qml index 3b3175b..7e56bdc 100644 --- a/guis/Flowee/Dialog.qml +++ b/guis/Flowee/Dialog.qml @@ -40,6 +40,8 @@ Popup { // make sure any content gets focus on open if (visible && content.item) content.item.forceActiveFocus() + if (!visible && root.parent != null) + root.parent.forceActiveFocus() } onAboutToShow: reposition() onHeightChanged: reposition() diff --git a/guis/mobile/CalendarWidget.qml b/guis/mobile/CalendarWidget.qml index bd10e5c..9c8a09e 100644 --- a/guis/mobile/CalendarWidget.qml +++ b/guis/mobile/CalendarWidget.qml @@ -62,6 +62,7 @@ Item { Flowee.Label { text: "<" y: 10 + x: 15 MouseArea { anchors.fill: parent anchors.margins: -8 @@ -70,7 +71,7 @@ Item { } Flowee.Label { text: ">" - x: parent.width - width + x: parent.width - width - 15 y: 10 MouseArea { anchors.fill: parent diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 1dbb209..3bba689 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -358,8 +358,8 @@ Page { Image { id: pinButton y: -5 - width: 21 - height: 30 + width: 28 + height: 40 anchors.horizontalCenter: parent.horizontalCenter visible: parent.main source: "qrc:/pin-green.svg"; @@ -375,7 +375,7 @@ Page { value: amountsBox.detail.paymentAmount anchors.horizontalCenter: parent.horizontalCenter anchors.top: pinButton.bottom - anchors.topMargin: 10 + anchors.topMargin: 5 focus: false onValueEdited: amountsBox.detail.paymentAmount = value onActiveFocusChanged: { @@ -399,8 +399,8 @@ Page { Image { id: pinButton2 y: -5 - width: 21 - height: 30 + width: 28 + height: 40 anchors.horizontalCenter: parent.horizontalCenter visible: parent.main source: "qrc:/pin-green.svg"; @@ -416,7 +416,7 @@ Page { value: amountsBox.detail.paymentAmountFiat anchors.horizontalCenter: parent.horizontalCenter anchors.top: pinButton2.bottom - anchors.topMargin: 10 + anchors.topMargin: 5 focus: false onValueEdited: amountsBox.detail.paymentAmountFiat = value visible: Pay.isMainChain @@ -464,6 +464,8 @@ Page { /* + TODO time of day for the payment + PageTitledBox { title: qsTr("Advanced") Flowee.Label { diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 0c494d0..98f484c 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -29,10 +29,13 @@ QDateTime calcNext(const std::shared_ptr &payment) auto *repeat = payment->repeatDetails(); assert(repeat); const auto list = repeat->repeatConfig(); + const auto end = repeat->sunset(); for (const auto &config : list) { auto next = config.next(); if (!next.isValid()) continue; + if (end.isValid() && next.date() > end) + continue; if (!firstPaymentDate.isValid() || next < firstPaymentDate) firstPaymentDate = next; } @@ -44,7 +47,18 @@ RepeatPaymentConfig::RepeatPaymentConfig(const RepeatPaymentDetails::Config &con : QObject(parent), m_config(config) { + assert(parent); + auto *sp = qobject_cast(parent); + assert(sp); + m_payment = sp->payment(); + assert(m_payment.get()); + auto *repeatDetails = m_payment->repeatDetails(); + assert(repeatDetails); updatePredictedPayments(); + + connect (repeatDetails, &RepeatPaymentDetails::sunsetChanged, this, [=]() { + updatePredictedPayments(); + }); } void RepeatPaymentConfig::setConfig(const RepeatPaymentDetails::Config &newConfig) @@ -142,10 +156,14 @@ void RepeatPaymentConfig::updatePredictedPayments() m_predictedPayments.clear(); auto copy = m_config; const auto end = copy.baseDate.addMonths(12); + + const auto sunset = m_payment->repeatDetails()->sunset(); for (int i = 0; i < 100; ++i) { auto next = copy.next(); if (next > end) break; + if (sunset.isValid() && next.date() > sunset) + break; m_predictedPayments.append(next.date()); copy.baseDate = next; } @@ -285,6 +303,8 @@ void SavedPayment::setConfigs(const QList &newConfigs) void SavedPayment::switchToMonth() { + if (!m_configs.isEmpty() && m_configs.at(0)->dayOfMonth() != -1) + return; assert(m_backupWeekConfigs.isEmpty()); for (auto *conf : std::as_const(m_configs)) { m_backupWeekConfigs.append(conf->config()); @@ -302,6 +322,8 @@ void SavedPayment::switchToMonth() void SavedPayment::switchToWeek() { + if (!m_configs.isEmpty() && m_configs.at(0)->dayOfWeek() != -1) + return; assert(m_backupMonthConfigs.isEmpty()); for (auto *conf : std::as_const(m_configs)) { m_backupMonthConfigs.append(conf->config()); diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index 49513ff..c76a52f 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -21,6 +21,7 @@ #include "Payment.h" #include "RepeatPaymentDetails.h" #include +#include #include class RepeatPaymentConfig : public QObject @@ -34,8 +35,7 @@ class RepeatPaymentConfig : public QObject Q_PROPERTY(QList predictedPayments READ predictedPayments NOTIFY predicatedPaymentsChanged FINAL) Q_PROPERTY(QTime time READ time WRITE setTime NOTIFY timeChanged FINAL) public: - explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *parent = nullptr); - + explicit RepeatPaymentConfig(const RepeatPaymentDetails::Config &config, QObject *savedPayment = nullptr); void setConfig(const RepeatPaymentDetails::Config &newConfig); RepeatPaymentDetails::Config config() const; @@ -65,7 +65,6 @@ signals: void monthIntervalChanged(); void nextChanged(); void predicatedPaymentsChanged(); - void timeChanged(); private: @@ -73,6 +72,7 @@ private: RepeatPaymentDetails::Config m_config; QList m_predictedPayments; QTime m_time; + std::shared_ptr m_payment; }; class SavedPayment : public QObject -- 2.54.0 From ad62cd75e37aeb2f13fdcefc466d563b1ed540cf Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 19:10:49 +0200 Subject: [PATCH 1408/1428] Whitespace --- .../build-transaction/DestinationEditPage.qml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/build-transaction/DestinationEditPage.qml b/modules/build-transaction/DestinationEditPage.qml index 7405df7..97504ea 100644 --- a/modules/build-transaction/DestinationEditPage.qml +++ b/modules/build-transaction/DestinationEditPage.qml @@ -25,19 +25,19 @@ import Flowee.org.pay; Page { id: destinationEditPage headerText: qsTr("Edit Destination") - + property QtObject sendAllAction: QQC2.Action { checkable: true checked: paymentDetail.maxSelected text: qsTr("Send All", "all money in wallet") onTriggered: paymentDetail.maxSelected = checked } - + Flowee.Label { id: destinationLabel text: qsTr("Bitcoin Cash Address") + ":" } - + Flowee.MultilineTextField { id: destination anchors.top: destinationLabel.bottom @@ -60,13 +60,13 @@ Page { return palette.windowText } } - + Flowee.LabelWithClipboard { id: nativeLabel width: parent.width anchors.top: destination.bottom anchors.topMargin: 10 - + // only show if its substantially different visible: text!== "" && text !== destination.text && destination.text !== paymentDetail.formattedTarget text: paymentDetail.niceAddress @@ -88,17 +88,17 @@ Page { fiatFollowsSats: paymentDetail.fiatFollows onFiatFollowsSatsChanged: paymentDetail.fiatFollows = fiatFollowsSats } - + AccountSelectorWidget { id: walletNameBackground anchors.bottom: numericKeyboard.top anchors.bottomMargin: 10 stickyAccount: true onSelectedAccountChanged: payment.account = selectedAccount - + balanceActions: [ sendAllAction ] } - + NumericKeyboardWidget { id: numericKeyboard anchors.bottom: parent.bottom @@ -107,7 +107,7 @@ Page { enabled: !paymentDetail.maxSelected dataInput: priceInput } - + Rectangle { color: mainWindow.errorRedBg radius: 15 @@ -118,7 +118,7 @@ Page { visible: (destination.addressType === Wallet.LegacySH || destination.addressType === Wallet.LegacyPKH) && paymentDetail.forceLegacyOk === false; - + Column { id: warningColumn x: 10 -- 2.54.0 From 1460075bb92de889ea4b8dd31d7b22e85815ec52 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 19:11:13 +0200 Subject: [PATCH 1409/1428] Add wallet selector. --- guis/mobile/RepeatPaymentDetails.qml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 3bba689..85c3544 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -462,6 +462,16 @@ Page { } } + PageTitledBox { + title: qsTr("Wallet for Payment") + id: walletSelector + width: parent.width + visible: !portfolio.singleAccountSetup + AccountSelectorWidget { + onSelectedAccountChanged: payment.account = selectedAccount + startingAccount: root.payment.account + } + } /* TODO time of day for the payment -- 2.54.0 From b455ed01a03c8b1a143b75f3571b33a9d5d6ab97 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 20:34:08 +0200 Subject: [PATCH 1410/1428] Add a context menu to pay screen for a planned payment --- guis/mobile/PayWithQR.qml | 22 ++++++++++++++++++---- guis/mobile/RepeatPaymentDetails.qml | 23 ++++++++++++++++++++--- src/Payment.cpp | 28 ++++++++++++++++++++++------ src/Payment.h | 7 ++++++- src/PaymentDetailOutput.cpp | 4 ++-- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 0f36099..05948c2 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -28,7 +28,7 @@ Page { property QtObject sendAllAction: QQC2.Action { checkable: true checked: payment.details[0].maxSelected - text: qsTr("Send All", "all money in wallet") + text: qsTr("Select All", "all money in wallet") onTriggered: { payment.details[0].maxSelected = checked if (payment.isValid) @@ -40,6 +40,17 @@ Page { checked: true text: qsTr("Show Address", "to show a bitcoincash address") } + property QtObject makeRepeating: QQC2.Action { + text: qsTr("Schedule Payment") + enabled: payment.isValid + onTriggered: { + var repeatingCopy = payment.copyToRepeating(root); + thePile.push("RepeatPaymentDetails.qml", { "savedPayment": repeatingCopy, + "withScheduleButton": true, + "acceptHandler": function handler() { thePile.pop(); thePile.pop(); } // remove current page too + }); + } + } property QtObject editPrice: QQC2.Action { text: qsTr("Edit Amount", "Edit amount of money to send") onTriggered: { @@ -54,14 +65,17 @@ Page { menuItems: { // only have menu items as long as we are effectively // showing this page and not some overlay. + var items = []; if (payment.broadcastStatus === Payment.NotStarted) { // a QR _with_ a bch-amount will turn off editing of amount-to-send if (allowEditAmount) - return [ showTargetAddress, sendAllAction ]; + items.push(sendAllAction); else - return [ showTargetAddress, editPrice ]; + items.push(editPrice); + items.push(showTargetAddress); + items.push(makeRepeating); } - return []; + return items; } // if true, show widgets to edit the amount-to-send property bool allowEditAmount: true diff --git a/guis/mobile/RepeatPaymentDetails.qml b/guis/mobile/RepeatPaymentDetails.qml index 85c3544..e3fcf52 100644 --- a/guis/mobile/RepeatPaymentDetails.qml +++ b/guis/mobile/RepeatPaymentDetails.qml @@ -28,12 +28,15 @@ Page { // This is supposed to be an instance of the C++ SavedPayment class (SavedPaymentsHandler.h) required property QtObject savedPayment; property Payment payment: savedPayment.payment; + property bool withScheduleButton: false + property var acceptHandler: function handler() { thePile.pop(); } Flickable { id: scrollPane anchors.fill: parent contentWidth: width contentHeight: column.height + Column { id: column width: parent.width @@ -438,10 +441,9 @@ Page { function start(money) { ourData.editor = money; if (!numKeyboardWidget.visible) { - var ch = scrollPane.contentHeight; - ch += numKeyboardWidget.implicitHeight + var pos = valueComment.mapToItem(scrollPane.contentItem, 0, implicitHeight) // bottom of keyboard numKeyboardWidget.opacity = 1 // will make the item visible - scrollPane.contentY = ch - root.height; + scrollPane.contentY = 10 + pos.y - scrollPane.height } } @@ -483,6 +485,21 @@ Page { } } */ + + Item { width: 1; height: 20; visible: scheduleButton.visible } // spacer + Flowee.BigButton { + id: scheduleButton + width: parent.width * 0.75 + anchors.horizontalCenter: parent.horizontalCenter + isMainButton: true + text: qsTr("Schedule") + visible: root.withScheduleButton + onClicked: { + Pay.addRepeatPayment(root.payment); + root.acceptHandler(); + } + } + Item { width: 1; height: 20 } // spacer } } Keys.onPressed: (event)=> { diff --git a/src/Payment.cpp b/src/Payment.cpp index 14322af..0e09d98 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -26,6 +26,7 @@ #include "TxInfoObject.h" #include "PaymentDetailComment_p.h" #include "RepeatPaymentDetails.h" +#include "SavedPaymentsHandler.h" #include #include @@ -159,15 +160,13 @@ Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *wallet) repeatConfig.baseDate = QDateTime::fromSecsSinceEpoch(parser.longData()); break; case RepeatSunsetDate: - if (m_repeatDetails) - m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData()).date()); + m_repeatDetails->setSunset(QDateTime::fromSecsSinceEpoch(parser.longData()).date()); break; case RepeatPaymentTxIndex: assert(false); // TODO break; case RepeatCurrencyLocale: - if (m_repeatDetails) - m_repeatDetails->setCurrencyLocale(QString::fromStdString(parser.stringData())); + m_repeatDetails->setCurrencyLocale(QString::fromStdString(parser.stringData())); break; case RepeatEnabled: m_repeatDetails->setEnabled(parser.boolData()); @@ -299,7 +298,7 @@ void Payment::decrypt(const QString &password) doAutoPrepare(); } -bool Payment::validate() +bool Payment::validate() const { int64_t output = 0; for (auto detail : std::as_const(m_paymentDetails)) { @@ -676,7 +675,7 @@ Streaming::ConstBuffer Payment::save(const std::shared_ptrrepeatConfig().isEmpty()) { for (const auto &conf : m_repeatDetails->repeatConfig()) { if (conf.dayOfMonth >= 0) builder.add(RepeatConfigDayOfMonth, conf.dayOfMonth); @@ -705,6 +704,23 @@ Streaming::ConstBuffer Payment::save(const std::shared_ptr(data, currentAccount()->wallet()); + copy->makeRepeating(); + auto *r = copy->repeatDetails(); + RepeatPaymentDetails::Config config; + config.baseDate = QDateTime::currentDateTimeUtc(); + auto next = config.baseDate.addDays(7); + config.dayOfMonth = next.date().day(); + r->addRepeatConfig(config); + return new SavedPayment(copy, next, parent); +} + bool Payment::simpleAddressTarget() const { return m_simpleAddressTarget; diff --git a/src/Payment.h b/src/Payment.h index 3d0706e..eb61f2c 100644 --- a/src/Payment.h +++ b/src/Payment.h @@ -164,7 +164,7 @@ public: bool walletNeedsPin() const; /// return true if all fields are correctly populated and we can prepare() - bool validate(); + bool validate() const; /// A user error occured during prepare() const QString &error() const; @@ -263,6 +263,11 @@ public: // Store the Payment and its details in a blob. Streaming::ConstBuffer save(const std::shared_ptr &pool) const; + /** + * Deep copy this Payment object and make it repeating, then return an instance of SavedPayment + */ + Q_INVOKABLE QObject *copyToRepeating(QObject *parent) const; + private slots: void recalcAmounts(); void broadcast(); diff --git a/src/PaymentDetailOutput.cpp b/src/PaymentDetailOutput.cpp index 355973f..701a642 100644 --- a/src/PaymentDetailOutput.cpp +++ b/src/PaymentDetailOutput.cpp @@ -85,10 +85,10 @@ void PaymentDetailOutput::setPaymentAmount(double amount_) if (cur == amount) return; } - m_paymentAmount = amount; // implicit changes first, it changes the representation setFiatFollows(true); setMaxSelected(false); + m_paymentAmount = amount; emit paymentAmountChanged(); emit paymentAmountFiatChanged(); checkValid(); @@ -308,10 +308,10 @@ void PaymentDetailOutput::setPaymentAmountFiat(qint64 amount) { if (m_fiatAmount == amount) return; - m_fiatAmount = amount; // implicit changes first, it changes the representation setFiatFollows(false); setMaxSelected(false); + m_fiatAmount = amount; emit paymentAmountChanged(); emit paymentAmountFiatChanged(); checkValid(); -- 2.54.0 From dd0c0a81e7d96e393bef573499ff4a212a565082 Mon Sep 17 00:00:00 2001 From: TomZ Date: Tue, 8 Jul 2025 20:54:56 +0200 Subject: [PATCH 1411/1428] Make tab bar on Home screen optional. --- guis/mobile/ActivityTab.qml | 12 ++++++------ guis/mobile/PayWithQR.qml | 9 +++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index 07e2fe7..9080972 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -92,18 +92,18 @@ Item { Item { width: 1; height: parent.leeway + 1 } // spacer } + + property bool addPaymentsTab: savedPayments.payments.length > 0 ListView { id: tabBar orientation: Qt.Horizontal width: parent.width height: { - if (count <= 1) + if (!visible || count <= 1) return 0; - if (currentItem === null) - return 40; return currentItem.height; } - visible: height > 0 + visible: parent.addPaymentsTab anchors.top: header.bottom clip: true @@ -111,11 +111,11 @@ Item { currentIndex: activityTabs.currentIndex model: ListModel { ListElement { - title: "Activity" + title: qsTr("Activity") qml: "AccountHistory.qml" } ListElement { - title: "Planned" + title: qsTr("Planned") qml: "PlannedPayments.qml" } } diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index 05948c2..b1af6c8 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -47,7 +47,12 @@ Page { var repeatingCopy = payment.copyToRepeating(root); thePile.push("RepeatPaymentDetails.qml", { "savedPayment": repeatingCopy, "withScheduleButton": true, - "acceptHandler": function handler() { thePile.pop(); thePile.pop(); } // remove current page too + "acceptHandler": function handler() { + var mainView = thePile.get(0); + mainView.currentIndex = 0; // go to the 'Home' tab. + thePile.pop(); + thePile.pop(); // remove current page too + } }); } } @@ -463,7 +468,7 @@ Page { onCloseButtonPressed: { var mainView = thePile.get(0); - mainView.currentIndex = 0; // go to the 'main' tab. + mainView.currentIndex = 0; // go to the 'Home' tab. thePile.pop(); } } -- 2.54.0 From 7e6c4609bd626c38ab321d795753cdd2ef1e00ba Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Jul 2025 14:22:17 +0200 Subject: [PATCH 1412/1428] Make work sending of scheduled payments in background --- src/FloweePay.cpp | 2 +- src/FloweePay.h | 1 - src/Payment.cpp | 23 +++++++++++++++++++++-- src/Periodic.cpp | 12 +++++++++++- src/Periodic.h | 4 ++++ src/SavedPaymentsHandler.h | 5 +++++ src/headless.cpp | 2 +- 7 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index b55f3de..3cc7364 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -479,7 +479,7 @@ void FloweePay::init() if (w->segment()->segmentId() == paymentWalletId) { auto payment = std::make_shared(parser.bytesDataBuffer(), w); payment->moveToThread(thread()); // owned by me, so move it to "my" thread. - m_repeatPayments.push_back(payment); + m_repeatPayments.append(payment); break; } } diff --git a/src/FloweePay.h b/src/FloweePay.h index 7cc2b07..425cc96 100644 --- a/src/FloweePay.h +++ b/src/FloweePay.h @@ -76,7 +76,6 @@ class FloweePay : public QObject, public WorkerThreads, public HeaderSyncInterfa Q_PROPERTY(UnitOfBitcoin unit READ unit WRITE setUnit NOTIFY unitChanged) Q_PROPERTY(QString unitName READ unitName NOTIFY unitChanged) Q_PROPERTY(UnlockingKeyboard unlockingKeyboard READ unlockingKeyboard WRITE setUnlockingKeyboard NOTIFY unlockingKeyboardChanged FINAL) - Q_PROPERTY(QList> repeatPayments READ repeatPayments NOTIFY repeatPaymentsChanged FINAL) // notifications Q_PROPERTY(QObject *notification READ notification WRITE setNotification NOTIFY notificationChanged FINAL) Q_PROPERTY(bool privateMode READ privateMode WRITE setPrivateMode NOTIFY privateModeChanged) diff --git a/src/Payment.cpp b/src/Payment.cpp index 0e09d98..1a8d10f 100644 --- a/src/Payment.cpp +++ b/src/Payment.cpp @@ -83,7 +83,12 @@ Payment::Payment(QObject *parent) } Payment::Payment(const Streaming::ConstBuffer &stored, Wallet *wallet) - : m_account(new AccountInfo(wallet, this)) + : m_account(new AccountInfo(wallet, this)), + m_txPrepared(false), + m_txBroadcastStarted(false), + m_preferSchnorr(true), + m_fee(1), + m_assignedFee(0) { PaymentDetailOutput *curOut = nullptr; Streaming::MessageParser parser(stored); @@ -535,8 +540,21 @@ void Payment::markUserApproved() // give external factors the opportunity to object. m_error.clear(); emit approvedByUser(); - if (m_error.isEmpty()) // nobody objected. + if (m_error.isEmpty()) {// nobody objected. broadcast(); + if (m_repeatDetails) { + // update base dates for the repeat payments. + auto configs = m_repeatDetails->repeatConfig(); + const QDateTime now = QDateTime::currentDateTimeUtc(); + const QDateTime nowIsh = now.addSecs(3500); + for (auto &config : configs) { + if (config.next() <= nowIsh) { + config.baseDate.setDate(now.date()); // don't change the time. only the date + } + } + m_repeatDetails->setRepeatConfig(configs); + } + } } void Payment::broadcast() @@ -713,6 +731,7 @@ QObject *Payment::copyToRepeating(QObject *parent) const auto copy = std::make_shared(data, currentAccount()->wallet()); copy->makeRepeating(); auto *r = copy->repeatDetails(); + r->setCurrencyLocale(FloweePay::instance()->prices()->locale()); RepeatPaymentDetails::Config config; config.baseDate = QDateTime::currentDateTimeUtc(); auto next = config.baseDate.addDays(7); diff --git a/src/Periodic.cpp b/src/Periodic.cpp index a69ec11..1cd6ecd 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -18,6 +18,7 @@ #include "Periodic.h" #include "FloweePay.h" #include "PriceDataProvider.h" +#include "SavedPaymentsHandler.h" #include "Wallet.h" #include @@ -84,7 +85,7 @@ int Periodic::run(RunType rt) this, &Periodic::checkFinished, Qt::QueuedConnection); } QObject::connect(app, &FloweePay::blockHeightCertaintyChanged, - this, &Periodic::checkFinished); + this, &Periodic::checkFinished, Qt::QueuedConnection); m_walletInitFinished = true; run2(); @@ -111,6 +112,15 @@ void Periodic::run2() return; FloweePay::instance()->startNet(); // lets go! + + assert (!m_savedPaymentsHandler); + m_savedPaymentsHandler = new SavedPaymentsHandler(this); + for (auto *payment : m_savedPaymentsHandler->scheduled()) { + if (payment->approved()) { + payment->startSend(); + auto *rp = payment->rawPayment(); + } + } } void Periodic::confirmFiat() diff --git a/src/Periodic.h b/src/Periodic.h index e6e0186..eb0480e 100644 --- a/src/Periodic.h +++ b/src/Periodic.h @@ -22,6 +22,8 @@ #include // for ECC_Start() #include +class SavedPaymentsHandler; + class Periodic : public QObject { public: @@ -44,6 +46,8 @@ private: bool m_haveFiatFeed = false; bool m_walletInitFinished = false; + + SavedPaymentsHandler *m_savedPaymentsHandler = nullptr; }; #endif diff --git a/src/SavedPaymentsHandler.h b/src/SavedPaymentsHandler.h index c76a52f..1e9f71b 100644 --- a/src/SavedPaymentsHandler.h +++ b/src/SavedPaymentsHandler.h @@ -157,12 +157,17 @@ public: explicit SavedPaymentsHandler(QObject *parent = nullptr); + // returns all enabled and valid payments. QList payments() const; void setPayments(const QList &newPayments); + // returns all payments that are due for very soon or in the past. + // this is a subest of payments() QList scheduled() const; void setScheduled(const QList &newScheduled); + // return a list of payments that are less than a week in the future + // and are not in the scheduled() list. QList soon() const; void setSoon(const QList &newSoon); diff --git a/src/headless.cpp b/src/headless.cpp index 945436b..178f032 100644 --- a/src/headless.cpp +++ b/src/headless.cpp @@ -23,5 +23,5 @@ int main(int, char **) { } class ModuleManager; -// no modules in the periodic, so lets not load any +// no modules in the headless app, so lets not load any void load_all_modules(ModuleManager*) { } -- 2.54.0 From e0b3bbebd591f8451b3710d2eb9d2c96955da804 Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 9 Jul 2025 16:35:54 +0200 Subject: [PATCH 1413/1428] Add default comment --- guis/mobile/PayWithQR.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index b1af6c8..d63e35f 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -45,6 +45,7 @@ Page { enabled: payment.isValid onTriggered: { var repeatingCopy = payment.copyToRepeating(root); + repeatingCopy.payment.userComment = qsTr("Scheduled Payment") // try to reuse string from RepeatPaymentDetails.qml thePile.push("RepeatPaymentDetails.qml", { "savedPayment": repeatingCopy, "withScheduleButton": true, "acceptHandler": function handler() { -- 2.54.0 From 39b4aafcd177aaef328552ed26cf357aa6c15db1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Jul 2025 12:27:17 +0200 Subject: [PATCH 1414/1428] Show the background running popup for repeat payments --- src/FloweePay.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/FloweePay.cpp b/src/FloweePay.cpp index 3cc7364..5e88540 100644 --- a/src/FloweePay.cpp +++ b/src/FloweePay.cpp @@ -628,14 +628,24 @@ void FloweePay::maybeShowBackgroundCard() if (!hasContent) return; + // Repeat payments need the background running, so be much more insistent + // if repeat payments are in use but background running is not enabled. + bool upcomingPayment = false; + for (const auto &repeatPayment : std::as_const(m_repeatPayments)) { + if (repeatPayment->repeatDetails()->preApproveOne()) { + upcomingPayment = true; + break; + } + } + // Don't bother the user too often with this. QSettings appConfig; appConfig.beginGroup(BACKGROUND_GROUP); const QDateTime lastReminder = appConfig.value(BG_LAST_REMINDER, QDateTime()).toDateTime(); const int numLaunches = appConfig.value(BG_NUM_LAUNCHES, 100).toInt(); - if (lastReminder.isValid() && lastReminder.daysTo(QDateTime::currentDateTimeUtc()) < 6) + if (lastReminder.isValid() && lastReminder.daysTo(QDateTime::currentDateTimeUtc()) < (upcomingPayment ? 2 : 6)) return; - if (numLaunches < 10) + if (!upcomingPayment && numLaunches < 10) return; appConfig.setValue(BG_LAST_REMINDER, QDateTime::currentDateTimeUtc()); appConfig.setValue(BG_NUM_LAUNCHES, 0); @@ -1090,6 +1100,11 @@ void FloweePay::addRepeatPayment(Payment *payment) auto copy = std::make_shared(data, payment->currentAccount()->wallet()); m_repeatPayments.append(copy); emit repeatPaymentsChanged(); + + // notify user on first add if the background running is not on yet. + if (!m_backgroundUpdates && m_repeatPayments.size() == 1) { + setShowBGSyncCard(true); + } } bool FloweePay::externalNotifications() const -- 2.54.0 From db629ba3cb47f505e78e32d0e13980d1a71187f1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Jul 2025 12:28:25 +0200 Subject: [PATCH 1415/1428] Add expected override keyword. --- src/TxInfoObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TxInfoObject.h b/src/TxInfoObject.h index a78cab8..c339e71 100644 --- a/src/TxInfoObject.h +++ b/src/TxInfoObject.h @@ -61,7 +61,7 @@ public: TxInfoObject(Wallet *wallet, const Tx &tx, int walletTxIndex = -1); // broadcastTxData interface. - void offeredVia(const std::shared_ptr &peer); + void offeredVia(const std::shared_ptr &peer) override; void sentVia(const std::shared_ptr &peer) override; void txRejected(int connectionId, RejectReason reason, const std::string &message) override; uint16_t privSegment() const override; -- 2.54.0 From 927d0c14b64ab460e9b5163f56d5862abdcbccdb Mon Sep 17 00:00:00 2001 From: TomZ Date: Thu, 10 Jul 2025 16:40:06 +0200 Subject: [PATCH 1416/1428] New month --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 46d64fd..5243562 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="42" android:versionName="2025.07.0"> diff --git a/src/main.cpp b/src/main.cpp index ea30a2c..76b9905 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,7 +103,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.06.0"); + qapp.setApplicationVersion("2025.07.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 63de3060136516448500eb15249c7cfc0342e59e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 14:08:38 +0200 Subject: [PATCH 1417/1428] Handle archived wallets. --- src/SavedPaymentsHandler.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/SavedPaymentsHandler.cpp b/src/SavedPaymentsHandler.cpp index 98f484c..8a3bfb0 100644 --- a/src/SavedPaymentsHandler.cpp +++ b/src/SavedPaymentsHandler.cpp @@ -16,6 +16,7 @@ * along with this program. If not, see . */ #include "SavedPaymentsHandler.h" +#include "AccountInfo.h" #include "FloweePay.h" #include "PriceDataProvider.h" #include "RepeatPaymentDetails.h" @@ -264,7 +265,13 @@ void SavedPayment::startSend() { if (m_payment->txPrepared()) // don't do it twice. return; - // TODO skip payments for wallets that are archived + // The handler should never have made an instance of a SavedPayment + // for a wallet that is archived. + // but just in case the wallet got archived later, we check it here and + // refuse to send anything. Rationale: an archived wallet likely is behind + // and is missing relevant transactions that change state. + if (m_payment->currentAccount()->isArchived()) + return; auto *fp = FloweePay::instance(); auto *prices = fp->prices(); @@ -439,11 +446,12 @@ void SavedPaymentsHandler::map() QList scheduled; QList soon; - // TODO filter out payments that belong to wallets which are hidden. const QDateTime nowIsh = QDateTime::currentDateTimeUtc().addSecs(3500); const QDateTime soonIsh = nowIsh.addDays(7); const auto set = FloweePay::instance()->repeatPayments(); for (const auto &payment : set) { + if (payment->currentAccount()->isArchived()) + continue; const QDateTime firstPaymentDate = calcNext(payment); if (firstPaymentDate.isValid()) { SavedPayment *sp = nullptr; -- 2.54.0 From 8d114c4e128b794835f755132bbbe518986608dd Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 16:05:38 +0200 Subject: [PATCH 1418/1428] Fix warning --- guis/mobile/ActivityTab.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index 9080972..63a0d0c 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -99,7 +99,7 @@ Item { orientation: Qt.Horizontal width: parent.width height: { - if (!visible || count <= 1) + if (!visible || count <= 1 || currentItem === null) return 0; return currentItem.height; } -- 2.54.0 From 466cc5a53c7d389a6132c1134a412f780de972ce Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 16:06:17 +0200 Subject: [PATCH 1419/1428] Cleanup --- src/Periodic.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Periodic.cpp b/src/Periodic.cpp index 1cd6ecd..2d95225 100644 --- a/src/Periodic.cpp +++ b/src/Periodic.cpp @@ -116,10 +116,8 @@ void Periodic::run2() assert (!m_savedPaymentsHandler); m_savedPaymentsHandler = new SavedPaymentsHandler(this); for (auto *payment : m_savedPaymentsHandler->scheduled()) { - if (payment->approved()) { + if (payment->approved()) payment->startSend(); - auto *rp = payment->rawPayment(); - } } } -- 2.54.0 From 36db99fb0846ae39dff102db691545d71a124ce1 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 16:08:07 +0200 Subject: [PATCH 1420/1428] Disable creation of repeating payments for now. To allow a bugfix release to be sent, lets remove the one action that can open the entire path to repeating payments for now. Allowing more time to be used for actually making repeat payments first public release more smooth. --- guis/mobile/PayWithQR.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guis/mobile/PayWithQR.qml b/guis/mobile/PayWithQR.qml index d63e35f..ddfd1a3 100644 --- a/guis/mobile/PayWithQR.qml +++ b/guis/mobile/PayWithQR.qml @@ -79,7 +79,7 @@ Page { else items.push(editPrice); items.push(showTargetAddress); - items.push(makeRepeating); + // items.push(makeRepeating); } return items; } -- 2.54.0 From 906f129687f3145999083361fd5bff72d8aea91e Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 16:56:08 +0200 Subject: [PATCH 1421/1428] Move gradient on Home tab for better UX. As the current wallet value is the same color, a the default gradient in the page header looks bad. So have one at the bottom of that balance area. --- guis/mobile/ActivityTab.qml | 12 ++++++++++++ guis/mobile/MainView.qml | 1 + guis/mobile/MainViewBase.qml | 34 ++++++++++++++++++++++------------ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index 63a0d0c..734aa44 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -32,11 +32,22 @@ Item { } Rectangle { + id: headerBg anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.bottom: tabBar.bottom + anchors.bottomMargin: -6 color: palette.window + property var headerGradient: Gradient { + GradientStop { position: 0.9; color: headerBg.color } + GradientStop { position: 1; color: { + let c = headerBg.color; + return Qt.rgba(c.r, c.g, c.b, 0); + } + } + } + gradient: tabBar.visible ? undefined: headerGradient } Column { @@ -160,6 +171,7 @@ Item { ListView { id: activityTabs anchors.top: tabBar.bottom + anchors.topMargin: tabBar.visible ? 0 : 6 anchors.bottom: parent.bottom width: parent.width model: tabBar.model diff --git a/guis/mobile/MainView.qml b/guis/mobile/MainView.qml index e615f17..2b85d3e 100644 --- a/guis/mobile/MainView.qml +++ b/guis/mobile/MainView.qml @@ -24,6 +24,7 @@ MainViewBase { ActivityTab { id: activityTab anchors.fill: parent + property bool showHeaderGradient: false PopupOverlay { id: popupOverlay } Component { id: filterPopup diff --git a/guis/mobile/MainViewBase.qml b/guis/mobile/MainViewBase.qml index 5a52d0b..fa1d4e4 100644 --- a/guis/mobile/MainViewBase.qml +++ b/guis/mobile/MainViewBase.qml @@ -34,20 +34,13 @@ QQC2.Control { default property alias content: stack.children property int currentIndex: 0 property bool showFilterIcon: false + /// the gradient at the bottom of the header + property bool showHeaderGradient: true signal filterIconClicked; + onCurrentIndexChanged: stack.newCurrentItem() + Component.onCompleted: stack.newCurrentItem() - onCurrentIndexChanged: setOpacities() - Component.onCompleted: setOpacities() - - function setOpacities() { - for (let i = 0; i < stack.children.length; ++i) { - let on = i === currentIndex; - let child = stack.children[i]; - child.visible = on; - } - takeFocus(); - } function switchToTab(index) { currentIndex = index } @@ -220,7 +213,7 @@ QQC2.Control { onSelectedAccountChanged: portfolio.current = selectedAccount } - gradient: Gradient { + property var headerGradient: Gradient { GradientStop { position: 0.9; color: header.color } GradientStop { position: 1; color: { let c = header.color; @@ -228,11 +221,28 @@ QQC2.Control { } } } + gradient: root.showHeaderGradient ? headerGradient : undefined } Item { id: stack width: root.width anchors.top: header.bottom; anchors.bottom: tabbar.top + function newCurrentItem() { + var current = root.currentIndex; + for (let i = 0; i < stack.children.length; ++i) { + let on = i === current + let child = stack.children[i]; + child.visible = on; + } + root.takeFocus(); + + var page = stack.children[current]; + var showGradient = true; + if (page !== null && typeof(page.showHeaderGradient) == "boolean") { + showGradient = page.showHeaderGradient; + } + root.showHeaderGradient = showGradient; + } } Rectangle { -- 2.54.0 From 2192d3c1b4e593708c3096003921a99097651690 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 12 Jul 2025 14:09:19 +0200 Subject: [PATCH 1422/1428] WIP; new build env versions --- android/docker/Dockerfile | 2 +- android/docker/build-docker.sh | 2 +- android/docker/scripts/aurs.sh | 15 +++++---------- android/docker/scripts/buildQt.sh | 2 +- android/docker/scripts/buildZXing.sh | 2 +- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index dbb6607..682e925 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG QtVersion=v6.8.2 +ARG QtVersion=v6.8.3 FROM archlinux:latest ARG QtVersion diff --git a/android/docker/build-docker.sh b/android/docker/build-docker.sh index 309ae62..78c6070 100755 --- a/android/docker/build-docker.sh +++ b/android/docker/build-docker.sh @@ -14,7 +14,7 @@ if test "$1" != "force"; then fi fi -QtVersion=v6.8.2 +QtVersion=v6.8.3 cd `dirname $0` docker build . --tag flowee/buildenv-android:$QtVersion --build-arg QtVersion=$QtVersion diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index 0c04b77..a2e5c3b 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -25,16 +25,11 @@ function makeAur( ) -# r27 -makeAur android-ndk 658ef36823525853a1338d51020320a9fb1b9cbd -# 35.0.2 -makeAur android-sdk-platform-tools ac481561ad3ab25cd17a4a62571159176ab6f584 -# r34.0.0-2 -makeAur android-sdk-build-tools ce7b51fed9ef7e0db9ee681883204adde1ef3808 -# 16.0 -makeAur android-sdk-cmdline-tools-latest 91763061af0ee3d077246c18bb4b6fe55fc7c566 -# 35_r01 -makeAur android-platform 5506c16537f770278bcb694451800ceb07e92de8 +makeAur android-ndk +makeAur android-sdk-platform-tools +makeAur android-sdk-build-tools +makeAur android-sdk-cmdline-tools-latest +makeAur android-platform pacman -U --noconfirm /usr/local/cache/*zst diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 4ed8cfe..03f0a5a 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -45,7 +45,7 @@ QTBASE_FLAGS="-no-widgets \ checkout qtbase cd ~builduser -patch -d qtbase -p1 < /usr/local/bin/qtbase-revert-qtranslator.patch +#patch -d qtbase -p1 < /usr/local/bin/qtbase-revert-qtranslator.patch mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ diff --git a/android/docker/scripts/buildZXing.sh b/android/docker/scripts/buildZXing.sh index 7506be7..66a6432 100755 --- a/android/docker/scripts/buildZXing.sh +++ b/android/docker/scripts/buildZXing.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION=2.2.0 +VERSION=2.3.0 echo "Based on zxing version $VERSION" >> /etc/versions source /etc/profile -- 2.54.0 From 288f92723941800675ddb599d8cfa6648c2fd386 Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 1 Aug 2025 18:28:47 +0200 Subject: [PATCH 1423/1428] Fix positioning --- guis/mobile/ActivityTab.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/guis/mobile/ActivityTab.qml b/guis/mobile/ActivityTab.qml index 734aa44..752efc1 100644 --- a/guis/mobile/ActivityTab.qml +++ b/guis/mobile/ActivityTab.qml @@ -55,7 +55,6 @@ Item { property int leeway: 30; Behavior on leeway { NumberAnimation {} } width: parent.width - x: 10 y: leeway Flowee.BitcoinAmountLabel { -- 2.54.0 From 085ce54a0b6616b9f49a8a606b94cbad3ec786fd Mon Sep 17 00:00:00 2001 From: TomZ Date: Fri, 1 Aug 2025 18:42:37 +0200 Subject: [PATCH 1424/1428] Make smaller --- guis/mobile/images/receive.svg | 150 +++++---------------------------- 1 file changed, 20 insertions(+), 130 deletions(-) diff --git a/guis/mobile/images/receive.svg b/guis/mobile/images/receive.svg index 889b646..80006e9 100644 --- a/guis/mobile/images/receive.svg +++ b/guis/mobile/images/receive.svg @@ -1,132 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + -- 2.54.0 From fa600058db7bb899e3a70c972521f62346362305 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 2 Aug 2025 13:45:24 +0200 Subject: [PATCH 1425/1428] Update build env to latest Qt and Android NDK/SDK This upgrades to the latest stable Qt (6.8.3) to my surprise the translator patch still is required and they didn't fix that bug for 2 bugfix releases now. --- android/build.gradle | 4 ++-- android/cmake/FindBoost.cmake | 6 +----- android/docker/scripts/aurs.sh | 15 ++++++++++----- android/docker/scripts/buildBoost.sh | 9 ++------- android/docker/scripts/buildOpenSsl.sh | 2 +- android/docker/scripts/buildQt.sh | 2 +- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 08e141c..b2aaac1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.6.0' + classpath 'com.android.tools.build:gradle:8.10.0' } } @@ -77,7 +77,7 @@ android { defaultConfig { resConfig "en" minSdkVersion qtMinSdkVersion - targetSdkVersion 34 + targetSdkVersion 35 ndk.abiFilters = qtTargetAbiList.split(",") } } diff --git a/android/cmake/FindBoost.cmake b/android/cmake/FindBoost.cmake index ce1c58b..27724df 100644 --- a/android/cmake/FindBoost.cmake +++ b/android/cmake/FindBoost.cmake @@ -20,17 +20,13 @@ set(Boost_LIBRARY_DIRS /opt/android-boost/lib) set(Boost_LIBRARIES ${Boost_LIBRARY_DIRS}/libboost_filesystem.a ${Boost_LIBRARY_DIRS}/libboost_system.a - ${Boost_LIBRARY_DIRS}/libboost_chrono.a ${Boost_LIBRARY_DIRS}/libboost_iostreams.a - ${Boost_LIBRARY_DIRS}/libboost_program_options.a ${Boost_LIBRARY_DIRS}/libboost_thread.a ) set(Boost_FILESYSTEM_FOUND ON) set(Boost_SYSTEM_FOUND ON) -set(Boost_chrono_FOUND ON) set(Boost_iostreams_FOUND ON) -set(Boost_program_options_FOUND ON) set(Boost_thread_FOUND ON) -set(Boost_VERSION_STRING 1.67.0) +set(Boost_VERSION_STRING 1.83.0) include_directories(${Boost_INCLUDE_DIRS}) diff --git a/android/docker/scripts/aurs.sh b/android/docker/scripts/aurs.sh index a2e5c3b..068eba6 100755 --- a/android/docker/scripts/aurs.sh +++ b/android/docker/scripts/aurs.sh @@ -25,11 +25,16 @@ function makeAur( ) -makeAur android-ndk -makeAur android-sdk-platform-tools -makeAur android-sdk-build-tools -makeAur android-sdk-cmdline-tools-latest -makeAur android-platform +# 28c +makeAur android-ndk 0bea9627ace780a7911d27d8aa226aa41fad255e +# 36.0.0 +makeAur android-sdk-platform-tools 22b4fc7b2ad0525b69a872b9ae0818dc5dd71506 +# r35 +makeAur android-sdk-build-tools cae0bf43f31d3d40bceb9b46f1eac7a825f58db1 +# r35 +makeAur android-sdk-cmdline-tools-latest cae0bf43f31d3d40bceb9b46f1eac7a825f58db1 +# r36-r02 +makeAur android-platform 0be325f50ffdb121de4a247faf31738f7574cea8 pacman -U --noconfirm /usr/local/cache/*zst diff --git a/android/docker/scripts/buildBoost.sh b/android/docker/scripts/buildBoost.sh index d8dea5e..fc489c8 100755 --- a/android/docker/scripts/buildBoost.sh +++ b/android/docker/scripts/buildBoost.sh @@ -19,7 +19,7 @@ export PATH="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" ./bootstrap.sh --without-icu \ --with-toolset=clang \ - --with-libraries=chrono,filesystem,iostreams,program_options,system,thread \ + --with-libraries=filesystem,iostreams,system,thread \ --prefix=/opt/android-boost echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang++ : \"-std=c++17 -DBOOST_FILESYSTEM_DISABLE_STATX -DBOOST_FILESYSTEM_DISABLE_GETRANDOM -fvisibility=hidden -fPIC\" \"-fPIC\" ;" > user-config.jam @@ -35,11 +35,6 @@ echo "using clang : arm : /opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64 variant=release \ link=static \ threading=multi \ + debug-symbols=off \ install > ~builduser/boost.build.log 2>&1 -# toolset=clang-arm \ -# runtime-link=shared \ -# -sNO_BZIP2=1 \ -# -sNO_ZLIB=1 \ -# -# debug-symbols=off diff --git a/android/docker/scripts/buildOpenSsl.sh b/android/docker/scripts/buildOpenSsl.sh index 65ef506..24ab014 100755 --- a/android/docker/scripts/buildOpenSsl.sh +++ b/android/docker/scripts/buildOpenSsl.sh @@ -14,7 +14,7 @@ cd ~builduser export PATH="/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" tar xf /usr/local/cache/openssl-$VERSION.tar.gz cd openssl-$VERSION -./Configure shared android-arm64 -D__ANDROID_API__=21 --prefix=/opt/android-ssl +./Configure shared android-arm64 --prefix=/opt/android-ssl # don't compress these two into one, 'install' doesn't like -j make -j`nproc` 2>&1 >/dev/null diff --git a/android/docker/scripts/buildQt.sh b/android/docker/scripts/buildQt.sh index 03f0a5a..4ed8cfe 100755 --- a/android/docker/scripts/buildQt.sh +++ b/android/docker/scripts/buildQt.sh @@ -45,7 +45,7 @@ QTBASE_FLAGS="-no-widgets \ checkout qtbase cd ~builduser -#patch -d qtbase -p1 < /usr/local/bin/qtbase-revert-qtranslator.patch +patch -d qtbase -p1 < /usr/local/bin/qtbase-revert-qtranslator.patch mkdir -p ~builduser/build/qtbase cd ~builduser/build/qtbase ~builduser/qtbase/configure \ -- 2.54.0 From f1b4fccbb57738e10829f455b6e502729e647a36 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 2 Aug 2025 14:32:31 +0200 Subject: [PATCH 1426/1428] Move to new build env docker --- android/build-pay.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build-pay.sh b/android/build-pay.sh index f4e2a0e..057bc29 100755 --- a/android/build-pay.sh +++ b/android/build-pay.sh @@ -48,7 +48,7 @@ if test "$_ok" -eq 0; then fi if test -z "$_docker_name_"; then - _docker_name_="bitcoincashcode.org/flowee/buildenv-android:v6.8.2" + _docker_name_="bitcoincashcode.org/flowee/buildenv-android:v6.8.3" fi mkdir -p imports -- 2.54.0 From c9a1d89aa62ea9977856ad2358c8387073847c17 Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 2 Aug 2025 14:32:56 +0200 Subject: [PATCH 1427/1428] Update version to new month --- android/AndroidManifest.xml | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 5243562..c351265 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="43" android:versionName="2025.08.0"> diff --git a/src/main.cpp b/src/main.cpp index 76b9905..ab974da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,7 +103,7 @@ int main(int argc, char *argv[]) QGuiApplication qapp(argc, argv); qapp.setOrganizationName("flowee"); qapp.setApplicationName("pay"); - qapp.setApplicationVersion("2025.07.0"); + qapp.setApplicationVersion("2025.08.0"); qapp.setWindowIcon(QIcon(":/FloweePay.png")); qapp.setDesktopFileName("org.flowee.pay"); -- 2.54.0 From 3ce0c4d8bc066a61f277376b4a8f3b164f5301ea Mon Sep 17 00:00:00 2001 From: TomZ Date: Sat, 2 Aug 2025 15:04:47 +0200 Subject: [PATCH 1428/1428] Add labels and docs --- android/build.gradle | 7 +++++-- android/docker/Dockerfile | 13 +++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b2aaac1..62aafb8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:8.10.0' + //noinspection AndroidGradlePluginVersion + classpath 'com.android.tools.build:gradle:8.6.0' } } @@ -14,10 +15,12 @@ repositories { mavenCentral() } -apply plugin: 'com.android.application' +apply plugin: qtGradlePluginType dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) + //noinspection GradleDependency + // implementation 'androidx.core:core:1.13.1' } android { diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 682e925..ef56a59 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -2,8 +2,17 @@ ARG QtVersion=v6.8.3 FROM archlinux:latest ARG QtVersion -LABEL maintainer="Tom Flowee " -LABEL qtversion="${QtVersion}" +LABEL org.opencontainers.image.title=Flowee Android Build Environment +LABEL org.opencontainers.image.description=Official containerd package of all tools and libraries needed to build for Android aarch64 devices. Uses ArchLinux base with all the libraries needed for Flowee Pay. +LABEL org.opencontainers.image.authors=Tom +LABEL org.opencontainers.image.url=http://bitcoincashcode.org/Flowee/pay/src/branch/master/android/docker/README.md +LABEL org.opencontainers.image.documentation= +LABEL org.opencontainers.image.source=http://bitcoincashcode.org/Flowee/pay/src/branch/master/android/docker/README.md +LABEL org.opencontainers.image.licenses=GPL-3.0-or-later +LABEL org.opencontainers.image.version="${QtVersion}" +#unset variables set by the one we inherit +LABEL org.opencontainers.image.revision= +LABEL org.opencontainers.image.created= ENTRYPOINT ["su", "-", "builduser", "-c"] add scripts /usr/local/bin -- 2.54.0